changeset 7626:19af3fef3b34 stable

merge default to stable for 0.4.0
author Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>
date Sun, 31 Mar 2019 21:28:56 +0200
parents fdee9a036bee (current diff) 42383fe2533a (diff)
children 19086c5de05f
files .travis.yml development.ini docs/changelog.rst docs/contributing.rst docs/images/.img docs/installation.rst docs/installation_iis.rst docs/installation_win.rst docs/installation_win_old.rst docs/usage/email.rst docs/usage/performance.rst docs/usage/vcs_support.rst kallithea/bin/kallithea_config.py kallithea/bin/rebranddb.py kallithea/bin/template.ini.mako kallithea/config/deployment.ini_tmpl kallithea/controllers/admin/notifications.py kallithea/controllers/admin/settings.py kallithea/controllers/bookmarks.py kallithea/controllers/branches.py kallithea/controllers/tags.py kallithea/i18n/be/LC_MESSAGES/kallithea.po kallithea/i18n/cs/LC_MESSAGES/kallithea.po kallithea/i18n/en/LC_MESSAGES/kallithea.mo kallithea/i18n/hu/LC_MESSAGES/kallithea.po kallithea/i18n/ja/LC_MESSAGES/kallithea.po kallithea/i18n/kallithea.pot kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po kallithea/i18n/pl/LC_MESSAGES/kallithea.po kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po kallithea/i18n/ru/LC_MESSAGES/kallithea.po kallithea/i18n/sk/LC_MESSAGES/kallithea.po kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po kallithea/lib/celerypylons/commands.py kallithea/lib/celerypylons/loader.py kallithea/lib/dbmigrate/__init__.py kallithea/lib/dbmigrate/migrate.cfg kallithea/lib/dbmigrate/migrate/__init__.py kallithea/lib/dbmigrate/migrate/changeset/__init__.py kallithea/lib/dbmigrate/migrate/changeset/ansisql.py kallithea/lib/dbmigrate/migrate/changeset/constraint.py kallithea/lib/dbmigrate/migrate/changeset/databases/__init__.py kallithea/lib/dbmigrate/migrate/changeset/databases/firebird.py kallithea/lib/dbmigrate/migrate/changeset/databases/mysql.py kallithea/lib/dbmigrate/migrate/changeset/databases/oracle.py kallithea/lib/dbmigrate/migrate/changeset/databases/postgres.py kallithea/lib/dbmigrate/migrate/changeset/databases/sqlite.py kallithea/lib/dbmigrate/migrate/changeset/databases/visitor.py kallithea/lib/dbmigrate/migrate/changeset/schema.py kallithea/lib/dbmigrate/migrate/exceptions.py kallithea/lib/dbmigrate/migrate/versioning/__init__.py kallithea/lib/dbmigrate/migrate/versioning/api.py kallithea/lib/dbmigrate/migrate/versioning/cfgparse.py kallithea/lib/dbmigrate/migrate/versioning/config.py kallithea/lib/dbmigrate/migrate/versioning/genmodel.py kallithea/lib/dbmigrate/migrate/versioning/migrate_repository.py kallithea/lib/dbmigrate/migrate/versioning/pathed.py kallithea/lib/dbmigrate/migrate/versioning/repository.py kallithea/lib/dbmigrate/migrate/versioning/schema.py kallithea/lib/dbmigrate/migrate/versioning/schemadiff.py kallithea/lib/dbmigrate/migrate/versioning/script/__init__.py kallithea/lib/dbmigrate/migrate/versioning/script/base.py kallithea/lib/dbmigrate/migrate/versioning/script/py.py kallithea/lib/dbmigrate/migrate/versioning/script/sql.py kallithea/lib/dbmigrate/migrate/versioning/shell.py kallithea/lib/dbmigrate/migrate/versioning/template.py kallithea/lib/dbmigrate/migrate/versioning/templates/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/repository/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/README kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/script/__init__.py kallithea/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/default.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/pylons.py_tmpl kallithea/lib/dbmigrate/migrate/versioning/util/__init__.py kallithea/lib/dbmigrate/migrate/versioning/util/importpath.py kallithea/lib/dbmigrate/migrate/versioning/util/keyedinstance.py kallithea/lib/dbmigrate/migrate/versioning/version.py kallithea/lib/dbmigrate/schema/__init__.py kallithea/lib/dbmigrate/schema/db_1_1_0.py kallithea/lib/dbmigrate/schema/db_1_2_0.py kallithea/lib/dbmigrate/schema/db_1_3_0.py kallithea/lib/dbmigrate/schema/db_1_4_0.py kallithea/lib/dbmigrate/schema/db_1_5_0.py kallithea/lib/dbmigrate/schema/db_1_5_2.py kallithea/lib/dbmigrate/schema/db_1_6_0.py kallithea/lib/dbmigrate/schema/db_1_7_0.py kallithea/lib/dbmigrate/schema/db_1_8_0.py kallithea/lib/dbmigrate/schema/db_2_0_0.py kallithea/lib/dbmigrate/schema/db_2_0_1.py kallithea/lib/dbmigrate/schema/db_2_0_2.py kallithea/lib/dbmigrate/schema/db_2_1_0.py kallithea/lib/dbmigrate/schema/db_2_2_0.py kallithea/lib/dbmigrate/schema/db_2_2_3.py kallithea/lib/dbmigrate/versions/001_initial_release.py kallithea/lib/dbmigrate/versions/002_version_1_1_0.py kallithea/lib/dbmigrate/versions/003_version_1_2_0.py kallithea/lib/dbmigrate/versions/004_version_1_3_0.py kallithea/lib/dbmigrate/versions/005_version_1_3_0.py kallithea/lib/dbmigrate/versions/006_version_1_4_0.py kallithea/lib/dbmigrate/versions/007_version_1_4_0.py kallithea/lib/dbmigrate/versions/008_version_1_5_0.py kallithea/lib/dbmigrate/versions/009_version_1_5_1.py kallithea/lib/dbmigrate/versions/010_version_1_5_2.py kallithea/lib/dbmigrate/versions/011_version_1_6_0.py kallithea/lib/dbmigrate/versions/012_version_1_7_0.py kallithea/lib/dbmigrate/versions/013_version_1_7_0.py kallithea/lib/dbmigrate/versions/014_version_1_7_1.py kallithea/lib/dbmigrate/versions/015_version_1_8_0.py kallithea/lib/dbmigrate/versions/016_version_2_0_0.py kallithea/lib/dbmigrate/versions/017_version_2_0_0.py kallithea/lib/dbmigrate/versions/018_version_2_0_0.py kallithea/lib/dbmigrate/versions/019_version_2_0_0.py kallithea/lib/dbmigrate/versions/020_version_2_0_1.py kallithea/lib/dbmigrate/versions/021_version_2_0_2.py kallithea/lib/dbmigrate/versions/022_version_2_0_2.py kallithea/lib/dbmigrate/versions/023_version_2_1_0.py kallithea/lib/dbmigrate/versions/024_version_2_1_0.py kallithea/lib/dbmigrate/versions/025_version_2_1_0.py kallithea/lib/dbmigrate/versions/026_version_2_2_0.py kallithea/lib/dbmigrate/versions/027_version_2_2_0.py kallithea/lib/dbmigrate/versions/028_version_2_2_3.py kallithea/lib/dbmigrate/versions/029_version_2_2_3.py kallithea/lib/dbmigrate/versions/030_version_2_2_3.py kallithea/lib/dbmigrate/versions/031_version_2_2_3.py kallithea/lib/dbmigrate/versions/__init__.py kallithea/lib/hooks.py kallithea/lib/indexers/__init__.py kallithea/lib/middleware/pygrack.py kallithea/lib/middleware/simplegit.py kallithea/lib/paster_commands/__init__.py kallithea/lib/paster_commands/cache_keys.py kallithea/lib/paster_commands/cleanup.py kallithea/lib/paster_commands/install_iis.py kallithea/lib/paster_commands/ishell.py kallithea/lib/paster_commands/make_index.py kallithea/lib/paster_commands/make_rcextensions.py kallithea/lib/paster_commands/repo_scan.py kallithea/lib/paster_commands/setup_db.py kallithea/lib/paster_commands/update_repoinfo.py kallithea/lib/profiler.py kallithea/lib/vcs/utils/baseui_config.py kallithea/lib/vcs/utils/diffs.py kallithea/model/notification.py kallithea/model/scm.py kallithea/model/user.py kallithea/public/codemirror/LICENSE kallithea/public/codemirror/lib/codemirror.css kallithea/public/codemirror/lib/codemirror.js kallithea/public/codemirror/mode/apl/apl.js kallithea/public/codemirror/mode/asterisk/asterisk.js kallithea/public/codemirror/mode/clike/clike.js kallithea/public/codemirror/mode/clojure/clojure.js kallithea/public/codemirror/mode/cobol/cobol.js kallithea/public/codemirror/mode/coffeescript/coffeescript.js kallithea/public/codemirror/mode/commonlisp/commonlisp.js kallithea/public/codemirror/mode/css/css.js kallithea/public/codemirror/mode/css/less_test.js kallithea/public/codemirror/mode/css/scss_test.js kallithea/public/codemirror/mode/cypher/cypher.js kallithea/public/codemirror/mode/d/d.js kallithea/public/codemirror/mode/diff/diff.js kallithea/public/codemirror/mode/django/django.js kallithea/public/codemirror/mode/dtd/dtd.js kallithea/public/codemirror/mode/dylan/dylan.js kallithea/public/codemirror/mode/ecl/ecl.js kallithea/public/codemirror/mode/eiffel/eiffel.js kallithea/public/codemirror/mode/erlang/erlang.js kallithea/public/codemirror/mode/fortran/fortran.js kallithea/public/codemirror/mode/gas/gas.js kallithea/public/codemirror/mode/gfm/gfm.js kallithea/public/codemirror/mode/gherkin/gherkin.js kallithea/public/codemirror/mode/go/go.js kallithea/public/codemirror/mode/groovy/groovy.js kallithea/public/codemirror/mode/haml/haml.js kallithea/public/codemirror/mode/haskell/haskell.js kallithea/public/codemirror/mode/haxe/haxe.js kallithea/public/codemirror/mode/htmlembedded/htmlembedded.js kallithea/public/codemirror/mode/htmlmixed/htmlmixed.js kallithea/public/codemirror/mode/http/http.js kallithea/public/codemirror/mode/jade/jade.js kallithea/public/codemirror/mode/javascript/javascript.js kallithea/public/codemirror/mode/jinja2/jinja2.js kallithea/public/codemirror/mode/julia/julia.js kallithea/public/codemirror/mode/kotlin/kotlin.js kallithea/public/codemirror/mode/livescript/livescript.js kallithea/public/codemirror/mode/lua/lua.js kallithea/public/codemirror/mode/markdown/markdown.js kallithea/public/codemirror/mode/meta.js kallithea/public/codemirror/mode/mirc/mirc.js kallithea/public/codemirror/mode/mllike/mllike.js kallithea/public/codemirror/mode/modelica/modelica.js kallithea/public/codemirror/mode/nginx/nginx.js kallithea/public/codemirror/mode/ntriples/ntriples.js kallithea/public/codemirror/mode/octave/octave.js kallithea/public/codemirror/mode/pascal/pascal.js kallithea/public/codemirror/mode/pegjs/pegjs.js kallithea/public/codemirror/mode/perl/perl.js kallithea/public/codemirror/mode/php/php.js kallithea/public/codemirror/mode/pig/pig.js kallithea/public/codemirror/mode/properties/properties.js kallithea/public/codemirror/mode/puppet/puppet.js kallithea/public/codemirror/mode/python/python.js kallithea/public/codemirror/mode/q/q.js kallithea/public/codemirror/mode/r/r.js kallithea/public/codemirror/mode/rpm/rpm.js kallithea/public/codemirror/mode/rst/rst.js kallithea/public/codemirror/mode/ruby/ruby.js kallithea/public/codemirror/mode/rust/rust.js kallithea/public/codemirror/mode/sass/sass.js kallithea/public/codemirror/mode/scheme/scheme.js kallithea/public/codemirror/mode/shell/shell.js kallithea/public/codemirror/mode/sieve/sieve.js kallithea/public/codemirror/mode/slim/slim.js kallithea/public/codemirror/mode/smalltalk/smalltalk.js kallithea/public/codemirror/mode/smarty/smarty.js kallithea/public/codemirror/mode/smartymixed/smartymixed.js kallithea/public/codemirror/mode/solr/solr.js kallithea/public/codemirror/mode/sparql/sparql.js kallithea/public/codemirror/mode/sql/sql.js kallithea/public/codemirror/mode/stex/stex.js kallithea/public/codemirror/mode/tcl/tcl.js kallithea/public/codemirror/mode/textile/textile.js kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.css kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.js kallithea/public/codemirror/mode/tiki/tiki.css kallithea/public/codemirror/mode/tiki/tiki.js kallithea/public/codemirror/mode/toml/toml.js kallithea/public/codemirror/mode/tornado/tornado.js kallithea/public/codemirror/mode/turtle/turtle.js kallithea/public/codemirror/mode/vb/vb.js kallithea/public/codemirror/mode/vbscript/vbscript.js kallithea/public/codemirror/mode/velocity/velocity.js kallithea/public/codemirror/mode/verilog/verilog.js kallithea/public/codemirror/mode/xml/xml.js kallithea/public/codemirror/mode/xquery/xquery.js kallithea/public/codemirror/mode/yaml/yaml.js kallithea/public/codemirror/mode/z80/z80.js kallithea/public/css/bootstrap.css kallithea/public/css/contextbar.css kallithea/public/css/pygments.css kallithea/public/css/style.css kallithea/public/images/kallithea-logo.png kallithea/public/js/bootstrap.js kallithea/public/js/jquery-1.11.1.min.js kallithea/public/js/mousetrap.js kallithea/public/js/native.history.js kallithea/public/js/select2/select2-bootstrap.css kallithea/public/js/select2/select2-spinner.gif kallithea/public/js/select2/select2.css kallithea/public/js/select2/select2.js kallithea/public/js/select2/select2.png kallithea/public/js/select2/select2x2.png kallithea/public/js/yui.2.9.js kallithea/public/js/yui.flot.js kallithea/templates/admin/notifications/notifications.html kallithea/templates/admin/notifications/notifications_data.html kallithea/templates/admin/notifications/show_notification.html kallithea/templates/admin/repos/repo_edit_fork.html kallithea/templates/bookmarks/bookmarks.html kallithea/templates/bookmarks/bookmarks_data.html kallithea/templates/branches/branches.html kallithea/templates/branches/branches_data.html kallithea/templates/changelog/changelog_summary_data.html kallithea/templates/changeset/changeset_range.html kallithea/templates/email_templates/main.txt kallithea/templates/switch_to_list.html kallithea/templates/tags/tags.html kallithea/templates/tags/tags_data.html kallithea/tests/functional/test_admin_notifications.py kallithea/tests/functional/test_branches.py kallithea/tests/functional/test_changeset_comments.py kallithea/tests/functional/test_tags.py kallithea/tests/models/test_user_permissions_on_repos.py kallithea/tests/other/manual_test_vcs_operations.py kallithea/tests/parameterized.py kallithea/tests/test.ini kallithea/websetup.py scripts/manifest
diffstat 719 files changed, 85837 insertions(+), 166357 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.coveragerc	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,33 @@
+[run]
+omit =
+    # the bin scripts are not part of the Kallithea web app
+    kallithea/bin/*
+    # we ship with no active extensions
+    kallithea/config/rcextensions/*
+    # dbmigrate is not a part of the Kallithea web app
+    kallithea/lib/dbmigrate/*
+    # the tests themselves should not be part of the coverage report
+    kallithea/tests/*
+    # the scm hooks are not run in the kallithea process
+    kallithea/config/post_receive_tmpl.py
+    kallithea/config/pre_receive_tmpl.py
+
+# same omit lines should be present in sections 'run' and 'report'
+[report]
+omit =
+    # the bin scripts are not part of the Kallithea web app
+    kallithea/bin/*
+    # we ship with no active extensions
+    kallithea/config/rcextensions/*
+    # dbmigrate is not a part of the Kallithea web app
+    kallithea/lib/dbmigrate/*
+    # the tests themselves should not be part of the coverage report
+    kallithea/tests/*
+    # the scm hooks are not run in the kallithea process
+    kallithea/config/post_receive_tmpl.py
+    kallithea/config/pre_receive_tmpl.py
+
+[paths]
+source =
+    kallithea/
+    **/workspace/*/kallithea
--- a/.hgignore	Sun Mar 03 21:36:25 2019 +0100
+++ b/.hgignore	Sun Mar 31 21:28:56 2019 +0200
@@ -16,12 +16,31 @@
 ^docs/build/
 ^docs/_build/
 ^data$
-^kallithea/tests/data$
 ^sql_dumps/
 ^\.settings$
 ^\.project$
 ^\.pydevproject$
 ^\.coverage$
+^kallithea/front-end/node_modules$
+^kallithea/front-end/package-lock\.json$
+^kallithea/front-end/tmp$
+^kallithea/public/codemirror$
+^kallithea/public/css/select2-spinner\.gif$
+^kallithea/public/css/select2\.png$
+^kallithea/public/css/select2x2\.png$
+^kallithea/public/css/style\.css$
+^kallithea/public/css/style\.css\.map$
+^kallithea/public/js/bootstrap\.js$
+^kallithea/public/js/dataTables\.bootstrap\.js$
+^kallithea/public/js/jquery\.atwho\.min\.js$
+^kallithea/public/js/jquery\.caret\.min\.js$
+^kallithea/public/js/jquery\.dataTables\.js$
+^kallithea/public/js/jquery\.flot\.js$
+^kallithea/public/js/jquery\.flot\.selection\.js$
+^kallithea/public/js/jquery\.flot\.time\.js$
+^kallithea/public/js/jquery\.min\.js$
+^kallithea/public/js/select2\.js$
+^theme\.less$
 ^kallithea\.db$
 ^test\.db$
 ^Kallithea\.egg-info$
@@ -29,3 +48,5 @@
 ^fabfile.py
 ^\.idea$
 ^\.cache$
+^\.pytest_cache$
+/__pycache__$
--- a/.hgtags	Sun Mar 03 21:36:25 2019 +0100
+++ b/.hgtags	Sun Mar 31 21:28:56 2019 +0200
@@ -69,3 +69,5 @@
 4cca4cc6a0a97f4c4763317184cd41aca4297630 0.3.5
 082c9b8f0f17bd34740eb90c69bdc4c80d4b5b31 0.3.6
 a18445b85d407294da0b7f1d8be3bedef5ffdea6 0.3.7
+8db761c407685e7b08b800c947890035b0d67025 0.4.0rc1
+60f726162fd6c515bd819feb423be73cad01d7d3 0.4.0rc2
--- a/.travis.yml	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-language: python
-python:
-  - "2.6"
-  - "2.7"
-
-env:  
-  - TEST_DB=sqlite:////tmp/kallithea_test.sqlite
-  - TEST_DB=mysql://root@127.0.0.1/kallithea_test
-  - TEST_DB=postgresql://postgres@127.0.0.1/kallithea_test
-
-services:
-  - mysql
-  - postgresql
-
-# command to install dependencies
-before_script:
-  - mysql -e 'create database kallithea_test;'
-  - psql -c 'create database kallithea_test;' -U postgres
-  - git --version
-
-before_install:
-  - sudo apt-get remove git
-  - sudo add-apt-repository ppa:pdoes/ppa -y
-  - sudo apt-get update -y
-  - sudo apt-get install git -y
-
-install:
-  - pip install mysql-python psycopg2 mock unittest2
-  - pip install . --use-mirrors
-
-# command to run tests
-script: nosetests
-
-notifications:
-    email:
-        - ci@kallithea-scm.org
-    irc: "irc.freenode.org#kallithea"
-
-branches:
-  only:
-    - master
--- a/CONTRIBUTORS	Sun Mar 03 21:36:25 2019 +0100
+++ b/CONTRIBUTORS	Sun Mar 31 21:28:56 2019 +0200
@@ -1,28 +1,51 @@
 List of contributors to Kallithea project:
 
+    Andrej Shadura <andrew@shadura.me> 2012 2014-2017 2019
     Thomas De Schampheleire <thomas.de_schampheleire@nokia.com> 2014-2019
+    Étienne Gilli <etienne.gilli@gmail.com> 2015-2017 2019
     Mads Kiilerich <mads@kiilerich.com> 2016-2019
+    Allan Nordhøy <epost@anotheragency.no> 2017-2019
+    Danni Randeris <danniranderis@gmail.com> 2019
+    Edmund Wong <ewong@crazy-cat.org> 2019
+    Manuel Jacob <me@manueljacob.de> 2019
+    Dominik Ruf <dominikruf@gmail.com> 2012 2014-2018
     Michal Čihař <michal@cihar.com> 2014-2015 2018
     Branko Majic <branko@majic.rs> 2015 2018
+    Chris Rule <crule@aegistg.com> 2018
+    Jesús Sánchez <jsanchezfdz95@gmail.com> 2018
+    Patrick Vane <patrick_vane@lowentry.com> 2018
+    Pheng Heong Tan <phtan90@gmail.com> 2018
     ssantos <ssantos@web.de> 2018
     Максим Якимчук <xpinovo@gmail.com> 2018
     Марс Ямбар <mjambarmeta@gmail.com> 2018
     Mads Kiilerich <madski@unity3d.com> 2012-2017
     Unity Technologies 2012-2017
-    Andrew Shadura <andrew@shadura.me> 2012 2014-2017
-    Dominik Ruf <dominikruf@gmail.com> 2012 2014 2016-2017
-    Étienne Gilli <etienne.gilli@gmail.com> 2015 2017
+    Søren Løvborg <sorenl@unity3d.com> 2015-2017
     Sam Jaques <sam.jaques@me.com> 2015 2017
-    Allan Nordhøy <epost@anotheragency.no> 2017
+    Asterios Dimitriou <steve@pci.gr> 2016-2017
+    Alessandro Molina <alessandro.molina@axant.it> 2017
+    Anton Schur <tonich.sh@gmail.com> 2017
     Ching-Chen Mao <mao@lins.fju.edu.tw> 2017
+    Eivind Tagseth <eivindt@gmail.com> 2017
     FUJIWARA Katsunori <foozy@lares.dti.ne.jp> 2017
+    Holger Schramm <info@schramm.by> 2017
+    Karl Goetz <karl@kgoetz.id.au> 2017
+    Lars Kruse <devel@sumpfralle.de> 2017
+    Marko Semet <markosemet@googlemail.com> 2017
     Viktar Vauchkevich <victorenator@gmail.com> 2017
     Takumi IINO <trot.thunder@gmail.com> 2012-2016
-    Søren Løvborg <sorenl@unity3d.com> 2015-2016
+    Jan Heylen <heyleke@gmail.com> 2015-2016
+    Robert Martinez <ntttq@inboxen.org> 2015-2016
+    Robert Rauch <mail@robertrauch.de> 2015-2016
+    Angel Ezquerra <angel.ezquerra@gmail.com> 2016
     Anton Shestakov <av6@dwimlabs.net> 2016
     Brandon Jones <bjones14@gmail.com> 2016
+    Kateryna Musina <kateryna@unity3d.com> 2016
     Konstantin Veretennicov <kveretennicov@gmail.com> 2016
+    Oscar Curero <oscar@naiandei.net> 2016
     Robert James Dennington <tinytimrob@googlemail.com> 2016
+    timeless@gmail.com 2016
+    YFdyh000 <yfdyh000@gmail.com> 2016
     Aras Pranckevičius <aras@unity3d.com> 2012-2013 2015
     Sean Farley <sean.michael.farley@gmail.com> 2013-2015
     Christian Oyarzun <oyarzun@gmail.com> 2014-2015
@@ -37,7 +60,7 @@
     duanhongyi <duanhongyi@doopai.com> 2015
     EriCSN Chang <ericsning@gmail.com> 2015
     Grzegorz Krason <grzegorz.krason@gmail.com> 2015
-    Jan Heylen <heyleke@gmail.com> 2015
+    Jiří Suchan <yed@vanyli.net> 2015
     Kazunari Kobayashi <kobanari@nifty.com> 2015
     Kevin Bullock <kbullock@ringworld.org> 2015
     kobanari <kobanari@nifty.com> 2015
@@ -50,11 +73,10 @@
     Nick High <nick@silverchip.org> 2015
     Niemand Jedermann <predatorix@web.de> 2015
     Peter Vitt <petervitt@web.de> 2015
-    Robert Martinez <ntttq@inboxen.org> 2015
-    Robert Rauch <mail@robertrauch.de> 2015
     Ronny Pfannschmidt <opensource@ronnypfannschmidt.de> 2015
     Tuux <tuxa@galaxie.eu.org> 2015
     Viktar Palstsiuk <vipals@gmail.com> 2015
+    Ante Ilic <ante@unity3d.com> 2014
     Bradley M. Kuhn <bkuhn@sfconservancy.org> 2014
     Calinou <calinou@opmbx.org> 2014
     Daniel Anderson <daniel@dattrix.com> 2014
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Jenkinsfile	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,212 @@
+def createvirtualenv = ''
+def activatevirtualenv = ''
+
+node {
+    properties([[$class: 'BuildDiscarderProperty',
+                  strategy: [$class: 'LogRotator',
+                              artifactDaysToKeepStr: '',
+                              artifactNumToKeepStr: '10',
+                              daysToKeepStr: '',
+                              numToKeepStr: '']]]);
+    if (isUnix()) {
+        createvirtualenv = 'rm -r $JENKINS_HOME/venv/$JOB_NAME || true && virtualenv $JENKINS_HOME/venv/$JOB_NAME'
+        activatevirtualenv = '. $JENKINS_HOME/venv/$JOB_NAME/bin/activate'
+    } else {
+        createvirtualenv = 'rmdir /s /q %JENKINS_HOME%\\venv\\%JOB_NAME% || true && virtualenv %JENKINS_HOME%\\venv\\%JOB_NAME%'
+        activatevirtualenv = 'call %JENKINS_HOME%\\venv\\%JOB_NAME%\\Scripts\\activate.bat'
+    }
+
+    stage('checkout') {
+        checkout scm
+        if (isUnix()) {
+            sh 'hg --config extensions.purge= purge --all'
+        } else {
+            bat 'hg --config extensions.purge= purge --all'
+        }
+    }
+    stage('virtual env') {
+        def virtualenvscript = """$createvirtualenv
+            $activatevirtualenv
+            python -m pip install --upgrade pip
+            pip install --upgrade setuptools
+            pip install --upgrade pylint
+            pip install --upgrade pytest-cov
+            """
+        if (isUnix()) {
+            virtualenvscript += """
+                pip install --upgrade python-ldap
+                pip install --upgrade python-pam
+                """
+            sh virtualenvscript
+        } else {
+            bat virtualenvscript
+        }
+    }
+    stage('setup') {
+        def virtualenvscript = """$activatevirtualenv
+            pip install --upgrade -e .
+            pip install -r dev_requirements.txt
+            python setup.py compile_catalog
+            """
+        if (isUnix()) {
+            sh virtualenvscript
+        } else {
+            bat virtualenvscript
+        }
+        stash name: 'kallithea', useDefaultExcludes: false
+    }
+    stage('pylint') {
+        sh script: """$activatevirtualenv
+            pylint -j 0 --disable=C -f parseable kallithea > pylint.out
+            """, returnStatus: true
+        archiveArtifacts 'pylint.out'
+        try {
+            step([$class: 'WarningsPublisher', canComputeNew: false, canResolveRelativePaths: false, defaultEncoding: '', excludePattern: '', healthy: '', includePattern: '', messagesPattern: '', parserConfigurations: [[parserName: 'PyLint', pattern: 'pylint.out']], unHealthy: ''])
+        } catch (java.lang.IllegalArgumentException exc) {
+            echo "You need to install the 'Warnings Plug-in' to display the pylint report."
+            currentBuild.result = 'UNSTABLE'
+            echo "Caught: ${exc}"
+        }
+    }
+}
+
+def pytests = [:]
+pytests['sqlite'] = {
+    node {
+        ws {
+            deleteDir()
+            unstash name: 'kallithea'
+            if (isUnix()) {
+                sh script: """$activatevirtualenv
+                    py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_sqlite.xml --cov=kallithea
+                    """, returnStatus: true
+            } else {
+                bat script: """$activatevirtualenv
+                    py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_sqlite.xml --cov=kallithea
+                    """, returnStatus: true
+            }
+            sh 'sed --in-place "s/\\(classname=[\'\\"]\\)/\\1SQLITE./g" pytest_sqlite.xml'
+            archiveArtifacts 'pytest_sqlite.xml'
+            junit 'pytest_sqlite.xml'
+            writeFile(file: '.coverage.sqlite', text: readFile('.coverage'))
+            stash name: 'coverage.sqlite', includes: '.coverage.sqlite'
+        }
+    }
+}
+
+pytests['de'] = {
+    node {
+        if (isUnix()) {
+            ws {
+                deleteDir()
+                unstash name: 'kallithea'
+                withEnv(['LANG=de_DE.UTF-8',
+                    'LANGUAGE=de',
+                    'LC_ADDRESS=de_DE.UTF-8',
+                    'LC_IDENTIFICATION=de_DE.UTF-8',
+                    'LC_MEASUREMENT=de_DE.UTF-8',
+                    'LC_MONETARY=de_DE.UTF-8',
+                    'LC_NAME=de_DE.UTF-8',
+                    'LC_NUMERIC=de_DE.UTF-8',
+                    'LC_PAPER=de_DE.UTF-8',
+                    'LC_TELEPHONE=de_DE.UTF-8',
+                    'LC_TIME=de_DE.UTF-8',
+                ]) {
+                    sh script: """$activatevirtualenv
+                        py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_de.xml --cov=kallithea
+                        """, returnStatus: true
+                }
+                sh 'sed --in-place "s/\\(classname=[\'\\"]\\)/\\1DE./g" pytest_de.xml'
+                archiveArtifacts 'pytest_de.xml'
+                junit 'pytest_de.xml'
+                writeFile(file: '.coverage.de', text: readFile('.coverage'))
+                stash name: 'coverage.de', includes: '.coverage.de'
+            }
+        }
+    }
+}
+pytests['mysql'] = {
+    node {
+        if (isUnix()) {
+            ws {
+                deleteDir()
+                unstash name: 'kallithea'
+                sh """$activatevirtualenv
+                    pip install --upgrade MySQL-python
+                    """
+                withEnv(['TEST_DB=mysql://kallithea:kallithea@jenkins_mysql/kallithea_test?charset=utf8']) {
+                    if (isUnix()) {
+                        sh script: """$activatevirtualenv
+                            py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_mysql.xml --cov=kallithea
+                            """, returnStatus: true
+                    } else {
+                        bat script: """$activatevirtualenv
+                            py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_mysql.xml --cov=kallithea
+                            """, returnStatus: true
+                    }
+                }
+                sh 'sed --in-place "s/\\(classname=[\'\\"]\\)/\\1MYSQL./g" pytest_mysql.xml'
+                archiveArtifacts 'pytest_mysql.xml'
+                junit 'pytest_mysql.xml'
+                writeFile(file: '.coverage.mysql', text: readFile('.coverage'))
+                stash name: 'coverage.mysql', includes: '.coverage.mysql'
+            }
+        }
+    }
+}
+pytests['postgresql'] = {
+    node {
+        if (isUnix()) {
+            ws {
+                deleteDir()
+                unstash name: 'kallithea'
+                sh """$activatevirtualenv
+                    pip install --upgrade psycopg2
+                    """
+                withEnv(['TEST_DB=postgresql://kallithea:kallithea@jenkins_postgresql/kallithea_test']) {
+                    if (isUnix()) {
+                        sh script: """$activatevirtualenv
+                            py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_postgresql.xml --cov=kallithea
+                            """, returnStatus: true
+                    } else {
+                        bat script: """$activatevirtualenv
+                            py.test -p no:sugar --cov-config .coveragerc --junit-xml=pytest_postgresql.xml --cov=kallithea
+                            """, returnStatus: true
+                    }
+                }
+                sh 'sed --in-place "s/\\(classname=[\'\\"]\\)/\\1POSTGRES./g" pytest_postgresql.xml'
+                archiveArtifacts 'pytest_postgresql.xml'
+                junit 'pytest_postgresql.xml'
+                writeFile(file: '.coverage.postgresql', text: readFile('.coverage'))
+                stash name: 'coverage.postgresql', includes: '.coverage.postgresql'
+            }
+        }
+    }
+}
+stage('Tests') {
+    parallel pytests
+    node {
+        unstash 'coverage.sqlite'
+        unstash 'coverage.de'
+        unstash 'coverage.mysql'
+        unstash 'coverage.postgresql'
+        if (isUnix()) {
+            sh script: """$activatevirtualenv
+                coverage combine .coverage.sqlite .coverage.de .coverage.mysql .coverage.postgresql
+                coverage xml
+                """, returnStatus: true
+        } else {
+            bat script: """$activatevirtualenv
+                coverage combine .coverage.sqlite .coverage.de .coverage.mysql .coverage.postgresql
+                coverage xml
+                """, returnStatus: true
+        }
+        try {
+            step([$class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage.xml', failNoReports: false, failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, zoomCoverageChart: false])
+        } catch (java.lang.IllegalArgumentException exc) {
+            echo "You need to install the pipeline compatible 'CoberturaPublisher Plug-in' to display the coverage report."
+            currentBuild.result = 'UNSTABLE'
+            echo "Caught: ${exc}"
+        }
+    }
+}
--- a/LICENSE.md	Sun Mar 03 21:36:25 2019 +0100
+++ b/LICENSE.md	Sun Mar 31 21:28:56 2019 +0200
@@ -22,27 +22,39 @@
 of Kallithea.
 
 
+Alembic
+-------
+
+Kallithea incorporates an [Alembic](http://alembic.zzzcomputing.com/en/latest/)
+"migration environment" in `kallithea/alembic`, portions of which is:
+
+Copyright &copy; 2009-2016 by Michael Bayer.
+Alembic is a trademark of Michael Bayer.
+
+and licensed under the MIT-permissive license, which is
+[included in this distribution](MIT-Permissive-License.txt).
+
 
 Bootstrap
 ---------
 
-Kallithea incorporates parts of the Javascript system called
+Kallithea uses the web framework called
 [Bootstrap](http://getbootstrap.com/), which is:
 
-Copyright &copy; 2012 Twitter, Inc.
+Copyright &copy; 2011-2016 Twitter, Inc.
 
-and licensed under
-[the Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
+and licensed under the MIT-permissive license, which is
+[included in this distribution](MIT-Permissive-License.txt).
 
-A copy of the Apache License 2.0 is also included in this distribution in its
-entirety in the file Apache-License-2.0.txt
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
 
 
 Codemirror
 ----------
 
-Kallithea incorporates parts of the Javascript system called
+Kallithea uses the Javascript system called
 [Codemirror](http://codemirror.net/), version 4.7.0, which is primarily:
 
 Copyright &copy; 2013-2014 by Marijn Haverbeke <marijnh@gmail.com>
@@ -51,40 +63,70 @@
 [included in this distribution](MIT-Permissive-License.txt).
 
 Additional files from upstream Codemirror are copyrighted by various authors
-and licensed under other permissive licenses.  The sub-directories under
-[.../public/codemirror](kallithea/public/codemirror) include the copyright and
-license notice and information as they appeared in Codemirror's upstream
-release.
+and licensed under other permissive licenses.
+
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
 
 
 jQuery
 ------
 
-Kallithea incorporates the Javascript system called
-[jQuery](http://jquery.org/),
-[herein](kallithea/public/js/jquery-1.11.1.min.js), and the Corresponding
-Source can be found in https://github.com/jquery/jquery at tag 1.11.1
-(mirrored at https://kallithea-scm.org/repos/mirror/jquery/files/1.11.1/ ).
+Kallithea uses the Javascript system called
+[jQuery](http://jquery.org/).
 
 It is Copyright 2013 jQuery Foundation and other contributors http://jquery.com/ and is under an
 [MIT-permissive license](MIT-Permissive-License.txt).
 
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
+
+
+
+At.js
+-----
+
+Kallithea uses the Javascript system called
+[At.js](http://ichord.github.com/At.js),
+which can be found together with its Corresponding Source in
+https://github.com/ichord/At.js at tag v1.5.4.
+
+It is Copyright 2013 chord.luo@gmail.com and is under an
+[MIT-permissive license](MIT-Permissive-License.txt).
+
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
+
 
 
-Mousetrap
----------
+Caret.js
+--------
 
-Kallithea incorporates parts of the Javascript system called
-[Mousetrap](http://craig.is/killing/mice/), which is:
+Kallithea uses the Javascript system called
+[Caret.js](http://ichord.github.com/Caret.js/),
+which can be found together with its Corresponding Source in
+https://github.com/ichord/Caret.js at tag v0.3.1.
+
+It is Copyright 2013 chord.luo@gmail.com and is under an
+[MIT-permissive license](MIT-Permissive-License.txt).
+
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
-   Copyright 2013 Craig Campbell
+
+
+DataTables
+----------
 
-and licensed under
-[the Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
+Kallithea uses the Javascript system called
+[DataTables](http://www.datatables.net/).
 
-A [copy of the Apache License 2.0](Apache-License-2.0.txt) is also included
-in this distribution.
+It is Copyright 2008-2015 SpryMedia Ltd. and is under an
+[MIT-permissive license](MIT-Permissive-License.txt).
+
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
 
 
@@ -103,7 +145,7 @@
 Select2
 -------
 
-Kallithea incorporates parts of the Javascript system called
+Kallithea uses the Javascript system called
 [Select2](http://ivaynberg.github.io/select2/), which is:
 
 Copyright 2012 Igor Vaynberg (and probably others)
@@ -122,12 +164,15 @@
 Kallithea will take the Apache license fork of the dual license, since
 Kallithea is GPLv3'd.
 
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
+
 
 
 Select2-Bootstrap-CSS
 ---------------------
 
-Kallithea incorporates some CSS from a system called
+Kallithea uses some CSS from a system called
 [Select2-bootstrap-css](https://github.com/t0m/select2-bootstrap-css), which
 is:
 
@@ -136,122 +181,43 @@
 and licensed under the MIT-permissive license, which is
 [included in this distribution](MIT-Permissive-License.txt).
 
-
-
-History.js
-----------
-
-Kallithea incorporates some CSS from a system called History.js, which is
-
-Copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice,
-   this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
-
-
-
-YUI
----
-
-Kallithea incorporates parts of the Javascript system called
-[YUI 2 — Yahoo! User Interface Library](http://yui.github.io/yui2/docs/yui_2.9.0_full/),
-which is made available under the [BSD License](http://yuilibrary.com/license/):
-
-Copyright &copy; 2013 Yahoo! Inc. All rights reserved.
-
-Redistribution and use of this software in source and binary forms, with or
-without modification, are permitted provided that the following conditions are
-met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-* Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be
-  used to endorse or promote products derived from this software without
-  specific prior written permission of Yahoo! Inc.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-Kallithea includes a minified version of YUI 2.9. To build yui.2.9.js:
-
-    git clone https://github.com/yui/builder
-    git clone https://github.com/yui/yui2
-    cd yui2/
-    git checkout hudson-yui2-2800
-    ln -sf JumpToPageDropDown.js src/paginator/js/JumpToPageDropdown.js # work around inconsistent casing
-    rm -f tmp.js
-    for m in yahoo event dom connection animation dragdrop element datasource autocomplete container event-delegate json datatable paginator; do
-      rm -f build/\$m/\$m.js
-      ( cd src/\$m && ant build deploybuild ) && sed -e 's,@VERSION@,2.9.0,g' -e 's,@BUILD@,2800,g' build/\$m/\$m.js >> tmp.js
-    done
-    java -jar ../builder/componentbuild/lib/yuicompressor/yuicompressor-2.4.4.jar tmp.js -o yui.2.9.js
-
-In compliance with GPLv3 the Corresponding Source for this Object Code is made
-available on
-[https://kallithea-scm.org/repos/mirror](https://kallithea-scm.org/repos/mirror).
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
 
 
 Flot
 ----
 
-Kallithea incorporates some CSS from a system called
-[Flot](http://code.google.com/p/flot/), which is:
+Kallithea uses some parts of a Javascript system called
+[Flot](http://www.flotcharts.org/), which is:
 
-Copyright 2006 Google Inc.
+Copyright (c) 2007-2014 IOLA and Ole Laursen
 
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-
-A [copy of the Apache License 2.0](Apache-License-2.0.txt) is also included
-in this distribution.
-
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
 
-
-Migrate
--------
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
 
-Kallithea incorporates in kallithea/lib/dbmigrate/migrate parts of the Python
-system called [Migrate or sqlalchemy-migrate](https://github.com/stackforge/sqlalchemy-migrate),
-which is:
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
 
-Copyright (c) 2009 Evan Rosson, Jan Dittberner, Domen Kožar
+It is not distributed with Kallithea, but will be downloaded
+using the ''kallithea-cli front-end-build'' command.
 
-and licensed under the MIT-permissive license, which is
-[included in this distribution](MIT-Permissive-License.txt).
 
 
 Icon fonts
@@ -261,7 +227,7 @@
 [Font Awesome](http://fontawesome.io) and
 [GitHub Octicons](https://octicons.github.com) for icons. Font Awesome is:
 
-Copyright (c) 2012, Dave Gandy
+Copyright (c) 2016, Dave Gandy
 
 Octicons is:
 
--- a/MANIFEST.in	Sun Mar 03 21:36:25 2019 +0100
+++ b/MANIFEST.in	Sun Mar 31 21:28:56 2019 +0200
@@ -1,21 +1,29 @@
+include           .coveragerc
 include           Apache-License-2.0.txt
 include           CONTRIBUTORS
 include           COPYING
+include           Jenkinsfile
 include           LICENSE-MERGELY.html
 include           LICENSE.md
 include           MIT-Permissive-License.txt
 include           README.rst
+include           dev_requirements.txt
 include           development.ini
+include           pytest.ini
+include           requirements.txt
+include           tox.ini
 recursive-include docs *
 recursive-include init.d *
+recursive-include kallithea/alembic *
 include           kallithea/bin/ldap_sync.conf
-include           kallithea/bin/template.ini.mako
-include           kallithea/config/deployment.ini_tmpl
+include           kallithea/lib/paster_commands/template.ini.mako
+recursive-include kallithea/front-end *
 recursive-include kallithea/i18n *
-recursive-include kallithea/lib/dbmigrate *.py_tmpl README migrate.cfg
 recursive-include kallithea/public *
 recursive-include kallithea/templates *
 recursive-include kallithea/tests/fixtures *
 recursive-include kallithea/tests/scripts *
-include           kallithea/tests/test.ini
+include           kallithea/tests/models/test_dump_html_mails.ref.html
+include           kallithea/tests/performance/test_vcs.py
 include           kallithea/tests/vcs/aconfig
+recursive-include scripts *
--- a/README.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/README.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -162,76 +162,14 @@
           install it via the command: ``pip install sphinx`` .
 
 
-Converting from RhodeCode
--------------------------
-
-Currently, you have two options for working with an existing RhodeCode
-database:
-
-- keep the database unconverted (intended for testing and evaluation)
-- convert the database in a one-time step
-
-Maintaining interoperability
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Interoperability with RhodeCode 2.2.X installations is provided so you don't
-have to immediately commit to switching to Kallithea. This option will most
-likely go away once the two projects have diverged significantly.
-
-To run Kallithea on a RhodeCode database, run::
-
-   echo "BRAND = 'rhodecode'" > kallithea/brand.py
-
-This location will depend on where you installed Kallithea. If you installed
-via::
-
-   python2 setup.py install
-
-then you will find this location at
-``$VIRTUAL_ENV/lib/python2.7/site-packages/Kallithea-0.1-py2.7.egg/kallithea``.
-
-One-time conversion
-~~~~~~~~~~~~~~~~~~~
+Migrating from RhodeCode
+------------------------
 
-Alternatively, if you would like to convert the database for good, you can use
-a helper script provided by Kallithea. This script will operate directly on the
-database, using the database string you can find in your ``production.ini`` (or
-``development.ini``) file. For example, if using SQLite::
-
-   cd /path/to/kallithea
-   cp /path/to/rhodecode/rhodecode.db kallithea.db
-   pip install sqlalchemy-migrate
-   python2 kallithea/bin/rebranddb.py sqlite:///kallithea.db
-
-.. Note::
-
-   If you started out using the branding interoperability approach mentioned
-   above, watch out for stray brand.pyc after removing brand.py.
-
-Git hooks
-~~~~~~~~~
-
-After switching to Kallithea, it will be necessary to update the Git_ hooks in
-your repositories. If not, the Git_ hooks from RhodeCode will still be called,
-which will cause ``git push`` to fail every time.
-
-If you do not have any custom Git_ hooks deployed, perform the following steps
-(this may take some time depending on the number and size of repositories you
-have):
-
-1. Log-in as an administrator.
-
-2. Open page *Admin > Settings > Remap and Rescan*.
-
-3. Turn on the option **Install Git Hooks**.
-
-4. Turn on the option **Overwrite existing Git hooks**.
-
-5. Click on the button **Rescan Repositories**.
-
-If you do have custom hooks, you will need to merge those changes manually. In
-order to get sample hooks from Kallithea, the easiest way is to create a new Git_
-repository, and have a look at the hooks deployed there.
+Kallithea 0.3.2 and earlier supports migrating from an existing RhodeCode
+installation. To migrate, install Kallithea 0.3.2 and follow the
+instructions in the 0.3.2 README to perform a one-time conversion of the
+database from RhodeCode to Kallithea, before upgrading to this version
+of Kallithea.
 
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev_requirements.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,8 @@
+pytest >= 3.3.0, < 3.8
+pytest-runner < 4.3
+pytest-sugar >= 0.7.0, < 0.10
+pytest-benchmark < 3.2
+pytest-localserver < 0.5
+mock < 2.1
+Sphinx < 1.8
+WebTest < 2.1
--- a/development.ini	Sun Mar 03 21:36:25 2019 +0100
+++ b/development.ini	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,12 @@
 ################################################################################
 ################################################################################
-# Kallithea - Development config:                                              #
-# listening on *:5000                                                          #
-# sqlite and kallithea.db                                                      #
-# initial_repo_scan = true                                                     #
-# set debug = true                                                             #
-# verbose and colorful logging                                                 #
+# Kallithea - config file generated with kallithea-config                      #
 #                                                                              #
 # The %(here)s variable will be replaced with the parent directory of this file#
 ################################################################################
 ################################################################################
 
 [DEFAULT]
-debug = true
-pdebug = false
 
 ################################################################################
 ## Email settings                                                             ##
@@ -39,43 +32,39 @@
 #email_prefix = [Kallithea]
 
 ## Recipients for error emails and fallback recipients of application mails.
-## Multiple addresses can be specified, space-separated.
+## Multiple addresses can be specified, comma-separated.
 ## Only addresses are allowed, do not add any name part.
 ## Default:
 #email_to =
 ## Examples:
 #email_to = admin@example.com
-#email_to = admin@example.com another_admin@example.com
+#email_to = admin@example.com,another_admin@example.com
+email_to =
 
 ## 'From' header for error emails. You can optionally add a name.
-## Default:
-#error_email_from = pylons@yourapp.com
+## Default: (none)
 ## Examples:
 #error_email_from = Kallithea Errors <kallithea-noreply@example.com>
-#error_email_from = paste_error@example.com
+#error_email_from = kallithea_errors@example.com
+error_email_from =
 
 ## SMTP server settings
-## Only smtp_server is mandatory. All other settings take the specified default
-## values.
-#smtp_server = smtp.example.com
+## If specifying credentials, make sure to use secure connections.
+## Default: Send unencrypted unauthenticated mails to the specified smtp_server.
+## For "SSL", use smtp_use_ssl = true and smtp_port = 465.
+## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.
+smtp_server =
 #smtp_username =
 #smtp_password =
-#smtp_port = 25
-#smtp_use_tls = false
+smtp_port =
 #smtp_use_ssl = false
-## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
-## If empty, use any of the authentication parameters supported by the server.
-#smtp_auth =
+#smtp_use_tls = false
 
+## Entry point for 'gearbox serve'
 [server:main]
-## PASTE ##
-#use = egg:Paste#http
-## nr of worker threads to spawn
-#threadpool_workers = 1
-## max request before thread respawn
-#threadpool_max_requests = 100
-## option to use threads of process
-#use_threadpool = true
+#host = 127.0.0.1
+host = 0.0.0.0
+port = 5000
 
 ## WAITRESS ##
 use = egg:waitress#main
@@ -87,85 +76,6 @@
 ## windows systems.
 #asyncore_use_poll = True
 
-## GUNICORN ##
-#use = egg:gunicorn#main
-## number of process workers. You must set `instance_id = *` when this option
-## is set to more than one worker
-#workers = 1
-## process name
-#proc_name = kallithea
-## type of worker class, one of sync, eventlet, gevent, tornado
-## recommended for bigger setup is using of of other than sync one
-#worker_class = sync
-#max_requests = 1000
-## ammount of time a worker can handle request before it gets killed and
-## restarted
-#timeout = 3600
-
-## UWSGI ##
-## run with uwsgi --ini-paste-logged <inifile.ini>
-#[uwsgi]
-#socket = /tmp/uwsgi.sock
-#master = true
-#http = 127.0.0.1:5000
-
-## set as deamon and redirect all output to file
-#daemonize = ./uwsgi_kallithea.log
-
-## master process PID
-#pidfile = ./uwsgi_kallithea.pid
-
-## stats server with workers statistics, use uwsgitop
-## for monitoring, `uwsgitop 127.0.0.1:1717`
-#stats = 127.0.0.1:1717
-#memory-report = true
-
-## log 5XX errors
-#log-5xx = true
-
-## Set the socket listen queue size.
-#listen = 256
-
-## Gracefully Reload workers after the specified amount of managed requests
-## (avoid memory leaks).
-#max-requests = 1000
-
-## enable large buffers
-#buffer-size = 65535
-
-## socket and http timeouts ##
-#http-timeout = 3600
-#socket-timeout = 3600
-
-## Log requests slower than the specified number of milliseconds.
-#log-slow = 10
-
-## Exit if no app can be loaded.
-#need-app = true
-
-## Set lazy mode (load apps in workers instead of master).
-#lazy = true
-
-## scaling ##
-## set cheaper algorithm to use, if not set default will be used
-#cheaper-algo = spare
-
-## minimum number of workers to keep at all times
-#cheaper = 1
-
-## number of workers to spawn at startup
-#cheaper-initial = 1
-
-## maximum number of workers that can be spawned
-#workers = 4
-
-## how many workers should be spawned at a time
-#cheaper-step = 1
-
-## COMMON ##
-host = 0.0.0.0
-port = 5000
-
 ## middleware for hosting the WSGI application under a URL prefix
 #[filter:proxy-prefix]
 #use = egg:PasteDeploy#prefix
@@ -178,29 +88,26 @@
 
 full_stack = true
 static_files = true
-## Available Languages:
-## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
-lang =
+
+## Internationalization (see setup documentation for details)
+## By default, the language requested by the browser is used if available.
+#i18n.enable = false
+## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):
+i18n.lang =
+
 cache_dir = %(here)s/data
 index_dir = %(here)s/data/index
 
-## perform a full repository scan on each server start, this should be
-## set to false after first startup, to allow faster server restarts.
-#initial_repo_scan = false
-initial_repo_scan = true
-
 ## uncomment and set this path to use archive download cache
 archive_cache_dir = %(here)s/tarballcache
 
 ## change this to unique ID for security
+#app_instance_uuid = VERY-SECRET
 app_instance_uuid = development-not-secret
 
 ## cut off limit for large diffs (size in bytes)
 cut_off_limit = 256000
 
-## use cache version of scm repo everywhere
-vcs_full_cache = true
-
 ## force https in Kallithea, fixes https redirects, assumes it's always https
 force_https = false
 
@@ -226,6 +133,11 @@
 show_sha_length = 12
 show_revision_number = false
 
+## Canonical URL to use when creating full URLs in UI and texts.
+## Useful when the site is available under different names or protocols.
+## Defaults to what is provided in the WSGI environment.
+#canonical_url = https://kallithea.example.com/repos
+
 ## gist URL alias, used to create nicer urls for gist. This should be an
 ## url that does rewrites to _admin/gists/<gistid>.
 ## example: http://gist.example.com/{gistid}. Empty means use the internal
@@ -245,46 +157,53 @@
 #    FilesController:archivefile
 
 ## default encoding used to convert from and to unicode
-## can be also a comma seperated list of encoding in case of mixed encodings
-default_encoding = utf8
+## can be also a comma separated list of encoding in case of mixed encodings
+default_encoding = utf-8
+
+## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea
+hgencoding = utf-8
 
 ## issue tracker for Kallithea (leave blank to disable, absent for default)
 #bugtracker = https://bitbucket.org/conservancy/kallithea/issues
 
-## issue tracking mapping for commits messages
-## comment out issue_pat, issue_server, issue_prefix to enable
+## issue tracking mapping for commit messages, comments, PR descriptions, ...
+## Refer to the documentation ("Integration with issue trackers") for more details.
 
-## pattern to get the issues from commit messages
-## default one used here is #<numbers> with a regex passive group for `#`
-## {id} will be all groups matched from this pattern
+## regular expression to match issue references
+## This pattern may/should contain parenthesized groups, that can
+## be referred to in issue_server_link or issue_sub using Python backreferences
+## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.
+## To require mandatory whitespace before the issue pattern, use:
+## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace
+## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.
 
-issue_pat = (?:\s*#)(\d+)
+issue_pat = #(\d+)
 
-## server url to the issue, each {id} will be replaced with match
-## fetched from the regex and {repo} is replaced with full repository name
-## including groups {repo_name} is replaced with just name of repo
-
-issue_server_link = https://issues.example.com/{repo}/issue/{id}
+## server url to the issue
+## This pattern may/should contain backreferences to parenthesized groups in issue_pat.
+## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group
+## called 'groupname' in issue_pat.
+## The special token {repo} is replaced with the full repository name
+## including repository groups, while {repo_name} is replaced with just
+## the name of the repository.
 
-## prefix to add to link to indicate it's an url
-## #314 will be replaced by <issue_prefix><id>
+issue_server_link = https://issues.example.com/{repo}/issue/\1
 
-issue_prefix = #
+## substitution pattern to use as the link text
+## If issue_sub is empty, the text matched by issue_pat is retained verbatim
+## for the link text. Otherwise, the link text is that of issue_sub, with any
+## backreferences to groups in issue_pat replaced.
 
-## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
+issue_sub =
+
+## issue_pat, issue_server_link and issue_sub can have suffixes to specify
 ## multiple patterns, to other issues server, wiki or others
 ## below an example how to create a wiki pattern
 # wiki-some-id -> https://wiki.example.com/some-id
 
-#issue_pat_wiki = (?:wiki-)(.+)
-#issue_server_link_wiki = https://wiki.example.com/{id}
-#issue_prefix_wiki = WIKI-
-
-## instance-id prefix
-## a prefix key for this instance used for cache invalidation when running
-## multiple instances of kallithea, make sure it's globally unique for
-## all running kallithea instances. Leave empty if you don't use it
-instance_id =
+#issue_pat_wiki = wiki-(\S+)
+#issue_server_link_wiki = https://wiki.example.com/\1
+#issue_sub_wiki = WIKI-\1
 
 ## alternative return HTTP header for failed authentication. Default HTTP
 ## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
@@ -301,19 +220,29 @@
 ## allows to setup custom hooks in settings page
 allow_custom_hooks_settings = True
 
+## extra extensions for indexing, space separated and without the leading '.'.
+# index.extensions =
+#    gemfile
+#    lock
+
+## extra filenames for indexing, space separated
+# index.filenames =
+#    .dockerignore
+#    .editorconfig
+#    INSTALL
+#    CHANGELOG
+
 ####################################
 ###        CELERY CONFIG        ####
 ####################################
 
 use_celery = false
-broker.host = localhost
-broker.vhost = rabbitmqhost
-broker.port = 5672
-broker.user = rabbitmq
-broker.password = qweqwe
+
+## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:
+broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
 
 celery.imports = kallithea.lib.celerylib.tasks
-
+celery.accept.content = pickle
 celery.result.backend = amqp
 celery.result.dburi = amqp://
 celery.result.serialier = json
@@ -322,11 +251,9 @@
 #celery.amqp.task.result.expires = 18000
 
 celeryd.concurrency = 2
-#celeryd.log.file = celeryd.log
-celeryd.log.level = DEBUG
 celeryd.max.tasks.per.child = 1
 
-## tasks will never be sent to the queue, but executed locally instead.
+## If true, tasks will never be sent to the queue, but executed locally instead.
 celery.always.eager = false
 
 ####################################
@@ -363,6 +290,7 @@
 beaker.session.timeout = 2592000
 
 ## Server secret used with HMAC to ensure integrity of cookies.
+#beaker.session.secret = VERY-SECRET
 beaker.session.secret = development-not-secret
 ## Further, encrypt the data with AES.
 #beaker.session.encrypt_key = <key_for_encryption>
@@ -382,90 +310,13 @@
 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
 #beaker.session.table_name = db_session
 
-############################
-## ERROR HANDLING SYSTEMS ##
-############################
-
-####################
-### [appenlight] ###
-####################
-
-## AppEnlight is tailored to work with Kallithea, see
-## http://appenlight.com for details how to obtain an account
-## you must install python package `appenlight_client` to make it work
-
-## appenlight enabled
-appenlight = false
-
-appenlight.server_url = https://api.appenlight.com
-appenlight.api_key = YOUR_API_KEY
-
-## TWEAK AMOUNT OF INFO SENT HERE
-
-## enables 404 error logging (default False)
-appenlight.report_404 = false
-
-## time in seconds after request is considered being slow (default 1)
-appenlight.slow_request_time = 1
-
-## record slow requests in application
-## (needs to be enabled for slow datastore recording and time tracking)
-appenlight.slow_requests = true
-
-## enable hooking to application loggers
-#appenlight.logging = true
-
-## minimum log level for log capture
-#appenlight.logging.level = WARNING
-
-## send logs only from erroneous/slow requests
-## (saves API quota for intensive logging)
-appenlight.logging_on_error = false
-
-## list of additonal keywords that should be grabbed from environ object
-## can be string with comma separated list of words in lowercase
-## (by default client will always send following info:
-## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
-## start with HTTP* this list be extended with additional keywords here
-appenlight.environ_keys_whitelist =
-
-## list of keywords that should be blanked from request object
-## can be string with comma separated list of words in lowercase
-## (by default client will always blank keys that contain following words
-## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
-## this list be extended with additional keywords set here
-appenlight.request_keys_blacklist =
-
-## list of namespaces that should be ignores when gathering log entries
-## can be string with comma separated list of namespaces
-## (by default the client ignores own entries: appenlight_client.client)
-appenlight.log_namespace_blacklist =
-
-################
-### [sentry] ###
-################
-
-## sentry is a alternative open source error aggregator
-## you must install python packages `sentry` and `raven` to enable
-
-sentry.dsn = YOUR_DNS
-sentry.servers =
-sentry.name =
-sentry.key =
-sentry.public_key =
-sentry.secret_key =
-sentry.project =
-sentry.site =
-sentry.include_paths =
-sentry.exclude_paths =
-
 ################################################################################
-## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
+## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT*              ##
 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
 ## execute malicious code after an exception is raised.                       ##
 ################################################################################
-#set debug = false
-set debug = true
+#debug = false
+debug = true
 
 ##################################
 ###       LOGVIEW CONFIG       ###
@@ -480,26 +331,25 @@
 #########################################################
 
 # SQLITE [default]
-sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
-
-# POSTGRESQL
-#sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
-
-# MySQL
-#sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea
+sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
 
 # see sqlalchemy docs for others
 
-sqlalchemy.db1.echo = false
-sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.db1.convert_unicode = true
+sqlalchemy.pool_recycle = 3600
+
+################################
+### ALEMBIC CONFIGURATION   ####
+################################
+
+[alembic]
+script_location = kallithea:alembic
 
 ################################
 ### LOGGING CONFIGURATION   ####
 ################################
 
 [loggers]
-keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
+keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
 
 [handlers]
 keys = console, console_sql
@@ -516,6 +366,7 @@
 handlers = console
 
 [logger_routes]
+#level = WARN
 level = DEBUG
 handlers =
 qualname = routes.middleware
@@ -523,35 +374,65 @@
 propagate = 1
 
 [logger_beaker]
+#level = WARN
 level = DEBUG
 handlers =
 qualname = beaker.container
 propagate = 1
 
 [logger_templates]
+#level = WARN
 level = INFO
 handlers =
 qualname = pylons.templating
 propagate = 1
 
 [logger_kallithea]
+#level = WARN
 level = DEBUG
 handlers =
 qualname = kallithea
 propagate = 1
 
+[logger_tg]
+#level = WARN
+level = DEBUG
+handlers =
+qualname = tg
+propagate = 1
+
+[logger_gearbox]
+#level = WARN
+level = DEBUG
+handlers =
+qualname = gearbox
+propagate = 1
+
 [logger_sqlalchemy]
-level = INFO
+level = WARN
 handlers = console_sql
 qualname = sqlalchemy.engine
 propagate = 0
 
 [logger_whoosh_indexer]
+#level = WARN
 level = DEBUG
 handlers =
 qualname = whoosh_indexer
 propagate = 1
 
+[logger_werkzeug]
+level = WARN
+handlers =
+qualname = werkzeug
+propagate = 1
+
+[logger_backlash]
+level = WARN
+handlers =
+qualname = backlash
+propagate = 1
+
 ##############
 ## HANDLERS ##
 ##############
@@ -559,17 +440,13 @@
 [handler_console]
 class = StreamHandler
 args = (sys.stderr,)
-#level = INFO
 #formatter = generic
-level = DEBUG
 formatter = color_formatter
 
 [handler_console_sql]
 class = StreamHandler
 args = (sys.stderr,)
-#level = WARN
 #formatter = generic
-level = DEBUG
 formatter = color_formatter_sql
 
 ################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/administrator_guide/auth.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,370 @@
+.. _authentication:
+
+====================
+Authentication setup
+====================
+
+Users can be authenticated in different ways. By default, Kallithea
+uses its internal user database. Alternative authentication
+methods include LDAP, PAM, Crowd, and container-based authentication.
+
+.. _ldap-setup:
+
+
+LDAP Authentication
+-------------------
+
+Kallithea supports LDAP authentication. In order
+to use LDAP, you have to install the python-ldap_ package. This package is
+available via PyPI, so you can install it by running::
+
+    pip install python-ldap
+
+.. note:: ``python-ldap`` requires some libraries to be installed on
+          your system, so before installing it check that you have at
+          least the ``openldap`` and ``sasl`` libraries.
+
+Choose *Admin > Authentication*, click the ``kallithea.lib.auth_modules.auth_ldap`` button
+and then *Save*, to enable the LDAP plugin and configure its settings.
+
+Here's a typical LDAP setup::
+
+ Connection settings
+ Enable LDAP          = checked
+ Host                 = host.example.com
+ Account              = <account>
+ Password             = <password>
+ Connection Security  = LDAPS
+ Certificate Checks   = DEMAND
+
+ Search settings
+ Base DN              = CN=users,DC=host,DC=example,DC=org
+ LDAP Filter          = (&(objectClass=user)(!(objectClass=computer)))
+ LDAP Search Scope    = SUBTREE
+
+ Attribute mappings
+ Login Attribute      = uid
+ First Name Attribute = firstName
+ Last Name Attribute  = lastName
+ Email Attribute      = mail
+
+If your user groups are placed in an Organisation Unit (OU) structure, the Search Settings configuration differs::
+
+ Search settings
+ Base DN              = DC=host,DC=example,DC=org
+ LDAP Filter          = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
+ LDAP Search Scope    = SUBTREE
+
+.. _enable_ldap:
+
+Enable LDAP : required
+    Whether to use LDAP for authenticating users.
+
+.. _ldap_host:
+
+Host : required
+    LDAP server hostname or IP address. Can be also a comma separated
+    list of servers to support LDAP fail-over.
+
+.. _Port:
+
+Port : optional
+    Defaults to 389 for PLAIN un-encrypted LDAP and START_TLS.
+    Defaults to 636 for LDAPS.
+
+.. _ldap_account:
+
+Account : optional
+    Only required if the LDAP server does not allow anonymous browsing of
+    records.  This should be a special account for record browsing.  This
+    will require `LDAP Password`_ below.
+
+.. _LDAP Password:
+
+Password : optional
+    Only required if the LDAP server does not allow anonymous browsing of
+    records.
+
+.. _Enable LDAPS:
+
+Connection Security : required
+    Defines the connection to LDAP server
+
+    PLAIN
+        Plain unencrypted LDAP connection.
+        This will by default use `Port`_ 389.
+
+    LDAPS
+        Use secure LDAPS connections according to `Certificate
+        Checks`_ configuration.
+        This will by default use `Port`_ 636.
+
+    START_TLS
+        Use START TLS according to `Certificate Checks`_ configuration on an
+        apparently "plain" LDAP connection.
+        This will by default use `Port`_ 389.
+
+.. _Certificate Checks:
+
+Certificate Checks : optional
+    How SSL certificates verification is handled -- this is only useful when
+    `Enable LDAPS`_ is enabled.  Only DEMAND or HARD offer full SSL security
+    with mandatory certificate validation, while the other options are
+    susceptible to man-in-the-middle attacks.
+
+    NEVER
+        A serve certificate will never be requested or checked.
+
+    ALLOW
+        A server certificate is requested.  Failure to provide a
+        certificate or providing a bad certificate will not terminate the
+        session.
+
+    TRY
+        A server certificate is requested.  Failure to provide a
+        certificate does not halt the session; providing a bad certificate
+        halts the session.
+
+    DEMAND
+        A server certificate is requested and must be provided and
+        authenticated for the session to proceed.
+
+    HARD
+        The same as DEMAND.
+
+.. _Custom CA Certificates:
+
+Custom CA Certificates : optional
+    Directory used by OpenSSL to find CAs for validating the LDAP server certificate.
+    Python 2.7.10 and later default to using the system certificate store, and
+    this should thus not be necessary when using certificates signed by a CA
+    trusted by the system.
+    It can be set to something like `/etc/openldap/cacerts` on older systems or
+    if using self-signed certificates.
+
+.. _Base DN:
+
+Base DN : required
+    The Distinguished Name (DN) where searches for users will be performed.
+    Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
+
+.. _LDAP Filter:
+
+LDAP Filter : optional
+    A LDAP filter defined by RFC 2254.  This is more useful when `LDAP
+    Search Scope`_ is set to SUBTREE.  The filter is useful for limiting
+    which LDAP objects are identified as representing Users for
+    authentication.  The filter is augmented by `Login Attribute`_ below.
+    This can commonly be left blank.
+
+.. _LDAP Search Scope:
+
+LDAP Search Scope : required
+    This limits how far LDAP will search for a matching object.
+
+    BASE
+        Only allows searching of `Base DN`_ and is usually not what you
+        want.
+
+    ONELEVEL
+        Searches all entries under `Base DN`_, but not Base DN itself.
+
+    SUBTREE
+        Searches all entries below `Base DN`_, but not Base DN itself.
+        When using SUBTREE `LDAP Filter`_ is useful to limit object
+        location.
+
+.. _Login Attribute:
+
+Login Attribute : required
+    The LDAP record attribute that will be matched as the USERNAME or
+    ACCOUNT used to connect to Kallithea.  This will be added to `LDAP
+    Filter`_ for locating the User object.  If `LDAP Filter`_ is specified as
+    "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
+    connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
+    ::
+
+        (&(LDAPFILTER)(uid=jsmith))
+
+.. _ldap_attr_firstname:
+
+First Name Attribute : required
+    The LDAP record attribute which represents the user's first name.
+
+.. _ldap_attr_lastname:
+
+Last Name Attribute : required
+    The LDAP record attribute which represents the user's last name.
+
+.. _ldap_attr_email:
+
+Email Attribute : required
+    The LDAP record attribute which represents the user's email address.
+
+If all data are entered correctly, and python-ldap_ is properly installed
+users should be granted access to Kallithea with LDAP accounts.  At this
+time user information is copied from LDAP into the Kallithea user database.
+This means that updates of an LDAP user object may not be reflected as a
+user update in Kallithea.
+
+If You have problems with LDAP access and believe You entered correct
+information check out the Kallithea logs, any error messages sent from LDAP
+will be saved there.
+
+Active Directory
+^^^^^^^^^^^^^^^^
+
+Kallithea can use Microsoft Active Directory for user authentication.  This
+is done through an LDAP or LDAPS connection to Active Directory.  The
+following LDAP configuration settings are typical for using Active
+Directory ::
+
+ Base DN              = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
+ Login Attribute      = sAMAccountName
+ First Name Attribute = givenName
+ Last Name Attribute  = sn
+ Email Attribute     = mail
+
+All other LDAP settings will likely be site-specific and should be
+appropriately configured.
+
+
+Authentication by container or reverse-proxy
+--------------------------------------------
+
+Kallithea supports delegating the authentication
+of users to its WSGI container, or to a reverse-proxy server through which all
+clients access the application.
+
+When these authentication methods are enabled in Kallithea, it uses the
+username that the container/proxy (Apache or Nginx, etc.) provides and doesn't
+perform the authentication itself. The authorization, however, is still done by
+Kallithea according to its settings.
+
+When a user logs in for the first time using these authentication methods,
+a matching user account is created in Kallithea with default permissions. An
+administrator can then modify it using Kallithea's admin interface.
+
+It's also possible for an administrator to create accounts and configure their
+permissions before the user logs in for the first time, using the :ref:`create-user` API.
+
+Container-based authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In a container-based authentication setup, Kallithea reads the user name from
+the ``REMOTE_USER`` server variable provided by the WSGI container.
+
+After setting up your container (see :ref:`apache_mod_wsgi`), you'll need
+to configure it to require authentication on the location configured for
+Kallithea.
+
+Proxy pass-through authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In a proxy pass-through authentication setup, Kallithea reads the user name
+from the ``X-Forwarded-User`` request header, which should be configured to be
+sent by the reverse-proxy server.
+
+After setting up your proxy solution (see :ref:`apache_virtual_host_reverse_proxy`,
+:ref:`apache_subdirectory` or :ref:`nginx_virtual_host`), you'll need to
+configure the authentication and add the username in a request header named
+``X-Forwarded-User``.
+
+For example, the following config section for Apache sets a subdirectory in a
+reverse-proxy setup with basic auth:
+
+.. code-block:: apache
+
+    <Location /someprefix>
+      ProxyPass http://127.0.0.1:5000/someprefix
+      ProxyPassReverse http://127.0.0.1:5000/someprefix
+      SetEnvIf X-Url-Scheme https HTTPS=1
+
+      AuthType Basic
+      AuthName "Kallithea authentication"
+      AuthUserFile /srv/kallithea/.htpasswd
+      Require valid-user
+
+      RequestHeader unset X-Forwarded-User
+
+      RewriteEngine On
+      RewriteCond %{LA-U:REMOTE_USER} (.+)
+      RewriteRule .* - [E=RU:%1]
+      RequestHeader set X-Forwarded-User %{RU}e
+    </Location>
+
+Setting metadata in container/reverse-proxy
+"""""""""""""""""""""""""""""""""""""""""""
+When a new user account is created on the first login, Kallithea has no information about
+the user's email and full name. So you can set some additional request headers like in the
+example below. In this example the user is authenticated via Kerberos and an Apache
+mod_python fixup handler is used to get the user information from a LDAP server. But you
+could set the request headers however you want.
+
+.. code-block:: apache
+
+    <Location /someprefix>
+      ProxyPass http://127.0.0.1:5000/someprefix
+      ProxyPassReverse http://127.0.0.1:5000/someprefix
+      SetEnvIf X-Url-Scheme https HTTPS=1
+
+      AuthName "Kerberos Login"
+      AuthType Kerberos
+      Krb5Keytab /etc/apache2/http.keytab
+      KrbMethodK5Passwd off
+      KrbVerifyKDC on
+      Require valid-user
+
+      PythonFixupHandler ldapmetadata
+
+      RequestHeader set X_REMOTE_USER %{X_REMOTE_USER}e
+      RequestHeader set X_REMOTE_EMAIL %{X_REMOTE_EMAIL}e
+      RequestHeader set X_REMOTE_FIRSTNAME %{X_REMOTE_FIRSTNAME}e
+      RequestHeader set X_REMOTE_LASTNAME %{X_REMOTE_LASTNAME}e
+    </Location>
+
+.. code-block:: python
+
+    from mod_python import apache
+    import ldap
+
+    LDAP_SERVER = "ldaps://server.mydomain.com:636"
+    LDAP_USER = ""
+    LDAP_PASS = ""
+    LDAP_ROOT = "dc=mydomain,dc=com"
+    LDAP_FILTER = "sAMAccountName=%s"
+    LDAP_ATTR_LIST = ['sAMAccountName','givenname','sn','mail']
+
+    def fixuphandler(req):
+        if req.user is None:
+            # no user to search for
+            return apache.OK
+        else:
+            try:
+                if('\\' in req.user):
+                    username = req.user.split('\\')[1]
+                elif('@' in req.user):
+                    username = req.user.split('@')[0]
+                else:
+                    username = req.user
+                l = ldap.initialize(LDAP_SERVER)
+                l.simple_bind_s(LDAP_USER, LDAP_PASS)
+                r = l.search_s(LDAP_ROOT, ldap.SCOPE_SUBTREE, LDAP_FILTER % username, attrlist=LDAP_ATTR_LIST)
+
+                req.subprocess_env['X_REMOTE_USER'] = username
+                req.subprocess_env['X_REMOTE_EMAIL'] = r[0][1]['mail'][0].lower()
+                req.subprocess_env['X_REMOTE_FIRSTNAME'] = "%s" % r[0][1]['givenname'][0]
+                req.subprocess_env['X_REMOTE_LASTNAME'] = "%s" % r[0][1]['sn'][0]
+            except Exception, e:
+                apache.log_error("error getting data from ldap %s" % str(e), apache.APLOG_ERR)
+
+            return apache.OK
+
+.. note::
+   If you enable proxy pass-through authentication, make sure your server is
+   only accessible through the proxy. Otherwise, any client would be able to
+   forge the authentication header and could effectively become authenticated
+   using any account of their liking.
+
+
+.. _python-ldap: http://www.python-ldap.org/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/administrator_guide/vcs_setup.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,61 @@
+.. _vcs_setup:
+
+=============================
+Version control systems setup
+=============================
+
+Kallithea supports Git and Mercurial repositories out-of-the-box.
+For Git, you do need the ``git`` command line client installed on the server.
+
+You can always disable Git or Mercurial support by editing the
+file ``kallithea/__init__.py`` and commenting out the backend. For example, to
+disable Git but keep Mercurial enabled:
+
+.. code-block:: python
+
+   BACKENDS = {
+       'hg': 'Mercurial repository',
+       #'git': 'Git repository',
+   }
+
+
+Git-specific setup
+------------------
+
+
+Web server with chunked encoding
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Large Git pushes require an HTTP server with support for
+chunked encoding for POST. The Python web servers waitress_ and
+gunicorn_ (Linux only) can be used. By default, Kallithea uses
+waitress_ for `gearbox serve` instead of the built-in `paste` WSGI
+server.
+
+The web server used by gearbox is controlled in the .ini file::
+
+    use = egg:waitress#main
+
+or::
+
+    use = egg:gunicorn#main
+
+Also make sure to comment out the following options::
+
+    threadpool_workers =
+    threadpool_max_requests =
+    use_threadpool =
+
+Increasing Git HTTP POST buffer size
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If Git pushes fail with HTTP error code 411 (Length Required), you may need to
+increase the Git HTTP POST buffer. Run the following command as the user that
+runs Kallithea to set a global Git variable to this effect::
+
+    git config --global http.postBuffer 524288000
+
+
+.. _waitress: http://pypi.python.org/pypi/waitress
+.. _gunicorn: http://pypi.python.org/pypi/gunicorn
+.. _subrepositories: http://mercurial.aragost.com/kick-start/en/subrepositories/
--- a/docs/api/api.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/api/api.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -9,34 +9,16 @@
 ``<your_server>/_admin/api``.
 
 
-API access for web views
-++++++++++++++++++++++++
-
-API access can also be turned on for each web view in Kallithea that is
-decorated with the ``@LoginRequired`` decorator. Some views use
-``@LoginRequired(api_access=True)`` and are always available. By default only
-RSS/Atom feed views are enabled. Other views are
-only available if they have been whitelisted. Edit the
-``api_access_controllers_whitelist`` option in your .ini file and define views
-that should have API access enabled.
+API keys
+--------
 
-For example, to enable API access to patch/diff, raw file and archive::
-
-    api_access_controllers_whitelist =
-        ChangesetController:changeset_patch,
-        ChangesetController:changeset_raw,
-        FilesController:raw,
-        FilesController:archivefile
-
-After this change, a Kallithea view can be accessed without login by adding a
-GET parameter ``?api_key=<api_key>`` to the URL.
-
-Exposing raw diffs is a good way to integrate with
-third-party services like code review, or build farms that can download archives.
+Every Kallithea user automatically receives an API key, which they can
+view under "My Account". On this page, API keys can also be revoked, and
+additional API keys can be generated.
 
 
 API access
-++++++++++
+----------
 
 Clients must send JSON encoded JSON-RPC requests::
 
@@ -76,7 +58,7 @@
 
 
 API client
-++++++++++
+----------
 
 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
 way to call the JSON-RPC API.
@@ -110,11 +92,11 @@
 
 
 API methods
-+++++++++++
+-----------
 
 
 pull
-----
+^^^^
 
 Pull the given repo from remote location. Can be used to automatically keep
 remote repos up to date.
@@ -136,7 +118,7 @@
     error :  null
 
 rescan_repos
-------------
+^^^^^^^^^^^^
 
 Rescan repositories. If ``remove_obsolete`` is set,
 Kallithea will delete repos that are in the database but not in the filesystem.
@@ -159,7 +141,7 @@
     error :  null
 
 invalidate_cache
-----------------
+^^^^^^^^^^^^^^^^
 
 Invalidate the cache for a repository.
 This command can only be executed using the api_key of a user with admin rights,
@@ -181,7 +163,7 @@
     error :  null
 
 lock
-----
+^^^^
 
 Set the locking state on the given repository by the given user.
 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
@@ -212,7 +194,7 @@
     error :  null
 
 get_ip
-------
+^^^^^^
 
 Return IP address as seen from Kallithea server, together with all
 defined IP addresses for given user.
@@ -244,12 +226,12 @@
     error :  null
 
 get_user
---------
+^^^^^^^^
 
 Get a user by username or userid. The result is empty if user can't be found.
 If userid param is skipped, it is set to id of user who is calling this method.
 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
-Regular users can only speicy their own userid.
+Regular users can only specify their own userid.
 
 INPUT::
 
@@ -288,7 +270,7 @@
     error:  null
 
 get_users
----------
+^^^^^^^^^
 
 List all existing users.
 This command can only be executed using the api_key of a user with admin rights.
@@ -325,7 +307,7 @@
 .. _create-user:
 
 create_user
------------
+^^^^^^^^^^^
 
 Create new user.
 This command can only be executed using the api_key of a user with admin rights.
@@ -371,7 +353,7 @@
     kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
 
 update_user
------------
+^^^^^^^^^^^
 
 Update the given user if such user exists.
 This command can only be executed using the api_key of a user with admin rights.
@@ -415,7 +397,7 @@
     error:  null
 
 delete_user
------------
+^^^^^^^^^^^
 
 Delete the given user if such a user exists.
 This command can only be executed using the api_key of a user with admin rights.
@@ -439,7 +421,7 @@
     error:  null
 
 get_user_group
---------------
+^^^^^^^^^^^^^^
 
 Get an existing user group.
 This command can only be executed using the api_key of a user with admin rights.
@@ -481,7 +463,7 @@
     error : null
 
 get_user_groups
----------------
+^^^^^^^^^^^^^^^
 
 List all existing user groups.
 This command can only be executed using the api_key of a user with admin rights.
@@ -507,7 +489,7 @@
     error : null
 
 create_user_group
------------------
+^^^^^^^^^^^^^^^^^
 
 Create a new user group.
 This command can only be executed using the api_key of a user with admin rights.
@@ -537,7 +519,7 @@
     error:  null
 
 add_user_to_user_group
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
 
 Adds a user to a user group. If the user already is in that group, success will be
 ``false``.
@@ -564,7 +546,7 @@
     error:  null
 
 remove_user_from_user_group
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Remove a user from a user group. If the user isn't in the given group, success will
 be ``false``.
@@ -591,7 +573,7 @@
     error:  null
 
 get_repo
---------
+^^^^^^^^
 
 Get an existing repository by its name or repository_id. Members will contain
 either users_group or users associated to that repository.
@@ -604,7 +586,9 @@
     api_key : "<api_key>"
     method :  "get_repo"
     args:     {
-                "repoid" : "<reponame or repo_id>"
+                "repoid" : "<reponame or repo_id>",
+                "with_revision_names": "<bool> = Optional(False)",
+                "with_pullrequests": "<bool> = Optional(False)",
               }
 
 OUTPUT::
@@ -630,7 +614,7 @@
                                        "raw_id":   "<raw_id>",
                                        "revision": "<numeric_revision>",
                                        "short_id": "<short_id>"
-                                     }
+                                     },
                 "owner":             "<repo_owner>",
                 "fork_of":           "<name_of_fork_parent>",
                 "members" :     [
@@ -658,7 +642,7 @@
                                     "permission" : "repository.(read|write|admin)"
                                   },

-                                ]
+                                ],
                  "followers":   [
                                   {
                                     "user_id" :     "<user_id>",
@@ -675,12 +659,74 @@
                                     "last_login":   "<last_login>",
                                   },

-                 ]
+                                ],
+                 <if with_revision_names == True>
+                 "tags": {
+                            "<tagname>": "<raw_id>",
+                            ...
+                         },
+                 "branches": {
+                            "<branchname>": "<raw_id>",
+                            ...
+                         },
+                 "bookmarks": {
+                            "<bookmarkname>": "<raw_id>",
+                            ...
+                         },
+                <if with_pullrequests == True>
+                "pull_requests": [
+                  {
+                    "status": "<pull_request_status>",
+                    "pull_request_id": <pull_request_id>,
+                    "description": "<pull_request_description>",
+                    "title": "<pull_request_title>",
+                    "url": "<pull_request_url>",
+                    "reviewers": [
+                      {
+                        "username": "<user_id>",
+                      },
+                      ...
+                    ],
+                    "org_repo_url": "<repo_url>",
+                    "org_ref_parts": [
+                      "<ref_type>",
+                      "<ref_name>",
+                      "<raw_id>"
+                    ],
+                    "other_ref_parts": [
+                      "<ref_type>",
+                      "<ref_name>",
+                      "<raw_id>"
+                    ],
+                    "comments": [
+                      {
+                        "username": "<user_id>",
+                        "text": "<comment text>",
+                        "comment_id": "<comment_id>",
+                      },
+                      ...
+                    ],
+                    "owner": "<username>",
+                    "statuses": [
+                      {
+                        "status": "<status_of_review>",        # "under_review", "approved" or "rejected"
+                        "reviewer": "<user_id>",
+                        "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
+                      },
+                      ...
+                    ],
+                    "revisions": [
+                      "<raw_id>",
+                      ...
+                    ]
+                  },
+                  ...
+                ]
             }
     error:  null
 
 get_repos
----------
+^^^^^^^^^
 
 List all existing repositories.
 This command can only be executed using the api_key of a user with admin rights,
@@ -717,7 +763,7 @@
     error:  null
 
 get_repo_nodes
---------------
+^^^^^^^^^^^^^^
 
 Return a list of files and directories for a given path at the given revision.
 It is possible to specify ret_type to show only ``files`` or ``dirs``.
@@ -748,7 +794,7 @@
     error:  null
 
 create_repo
------------
+^^^^^^^^^^^
 
 Create a repository. If the repository name contains "/", the repository will be
 created in the repository group indicated by that path. Any such repository
@@ -802,7 +848,7 @@
     error:  null
 
 update_repo
------------
+^^^^^^^^^^^
 
 Update a repository.
 This command can only be executed using the api_key of a user with admin rights,
@@ -862,7 +908,7 @@
     error:  null
 
 fork_repo
----------
+^^^^^^^^^
 
 Create a fork of the given repo. If using Celery, this will
 return success message immediately and a fork will be created
@@ -898,7 +944,7 @@
     error:  null
 
 delete_repo
------------
+^^^^^^^^^^^
 
 Delete a repository.
 This command can only be executed using the api_key of a user with admin rights,
@@ -925,7 +971,7 @@
     error:  null
 
 grant_user_permission
----------------------
+^^^^^^^^^^^^^^^^^^^^^
 
 Grant permission for a user on the given repository, or update the existing one if found.
 This command can only be executed using the api_key of a user with admin rights.
@@ -951,7 +997,7 @@
     error:  null
 
 revoke_user_permission
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
 
 Revoke permission for a user on the given repository.
 This command can only be executed using the api_key of a user with admin rights.
@@ -976,7 +1022,7 @@
     error:  null
 
 grant_user_group_permission
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Grant permission for a user group on the given repository, or update the
 existing one if found.
@@ -1003,7 +1049,7 @@
     error:  null
 
 revoke_user_group_permission
-----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Revoke permission for a user group on the given repository.
 This command can only be executed using the api_key of a user with admin rights.
@@ -1026,3 +1072,235 @@
               "success": true
             }
     error:  null
+
+get_changesets
+^^^^^^^^^^^^^^
+
+Get changesets of a given repository. This command can only be executed using the api_key
+of a user with read permissions to the repository.
+
+INPUT::
+
+    id : <id_for_response>
+    api_key : "<api_key>"
+    method  : "get_changesets"
+    args:     {
+                "repoid" : "<reponame or repo_id>",
+                "start": "<revision number> = Optional(None)",
+                "end": "<revision number> = Optional(None)",
+                "start_date": "<date> = Optional(None)",    # in "%Y-%m-%dT%H:%M:%S" format
+                "end_date": "<date> = Optional(None)",      # in "%Y-%m-%dT%H:%M:%S" format
+                "branch_name": "<branch name filter> = Optional(None)",
+                "reverse": "<bool> = Optional(False)",
+                "with_file_list": "<bool> = Optional(False)"
+              }
+
+OUTPUT::
+
+    id : <id_given_in_input>
+    result: [
+    {
+      "raw_id": "<raw_id>",
+      "short_id": "short_id": "<short_id>",
+      "author": "<full_author>",
+      "date": "<date_time_of_commit>",
+      "message": "<commit_message>",
+      "revision": "<numeric_revision>",
+      <if with_file_list == True>
+      "added": [<list of added files>],
+      "changed": [<list of changed files>],
+      "removed": [<list of removed files>]
+    },
+    ...
+    ]
+    error:  null
+
+get_changeset
+^^^^^^^^^^^^^
+
+Get information and review status for a given changeset. This command can only
+be executed using the api_key of a user with read permissions to the
+repository.
+
+INPUT::
+
+    id : <id_for_response>
+    api_key : "<api_key>"
+    method  : "get_changeset"
+    args:     {
+                "repoid" : "<reponame or repo_id>",
+                "raw_id" : "<raw_id>",
+                "with_reviews": "<bool> = Optional(False)"
+              }
+
+OUTPUT::
+
+    id : <id_given_in_input>
+    result: {
+              "author":   "<full_author>",
+              "date":     "<date_time_of_commit>",
+              "message":  "<commit_message>",
+              "raw_id":   "<raw_id>",
+              "revision": "<numeric_revision>",
+              "short_id": "<short_id>",
+              "reviews": [{
+                    "reviewer":   "<username>",
+                    "modified_at": "<date_time_of_review>",  # iso 8601 date, server's timezone
+                    "status":   "<status_of_review>",        # "under_review", "approved" or "rejected"
+                 },
+                 ...
+              ]
+            }
+    error:  null
+
+Example output::
+
+    {
+      "id" : 1,
+      "error" : null,
+      "result" : {
+        "author" : {
+          "email" : "user@example.com",
+          "name" : "Kallithea Admin"
+        },
+        "changed" : [],
+        "short_id" : "e1022d3d28df",
+        "date" : "2017-03-28T09:09:03",
+        "added" : [
+          "README.rst"
+        ],
+        "removed" : [],
+        "revision" : 0,
+        "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
+        "message" : "Added file via Kallithea",
+        "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
+        "reviews" : [
+          {
+            "status" : "under_review",
+            "modified_at" : "2017-03-28T09:17:08.618",
+            "reviewer" : "user"
+          }
+        ]
+      }
+    }
+
+get_pullrequest
+^^^^^^^^^^^^^^^
+
+Get information and review status for a given pull request. This command can only be executed
+using the api_key of a user with read permissions to the original repository.
+
+INPUT::
+
+    id : <id_for_response>
+    api_key : "<api_key>"
+    method  : "get_pullrequest"
+    args:     {
+                "pullrequest_id" : "<pullrequest_id>",
+              }
+
+OUTPUT::
+
+    id : <id_given_in_input>
+    result: {
+        "status": "<pull_request_status>",
+        "pull_request_id": <pull_request_id>,
+        "description": "<pull_request_description>",
+        "title": "<pull_request_title>",
+        "url": "<pull_request_url>",
+        "reviewers": [
+          {
+            "username": "<user_name>",
+          },
+          ...
+        ],
+        "org_repo_url": "<repo_url>",
+        "org_ref_parts": [
+          "<ref_type>",
+          "<ref_name>",
+          "<raw_id>"
+        ],
+        "other_ref_parts": [
+          "<ref_type>",
+          "<ref_name>",
+          "<raw_id>"
+        ],
+        "comments": [
+          {
+            "username": "<user_name>",
+            "text": "<comment text>",
+            "comment_id": "<comment_id>",
+          },
+          ...
+        ],
+        "owner": "<username>",
+        "statuses": [
+          {
+            "status": "<status_of_review>",        # "under_review", "approved" or "rejected"
+            "reviewer": "<user_name>",
+            "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
+          },
+          ...
+        ],
+        "revisions": [
+          "<raw_id>",
+          ...
+        ]
+    },
+    error:  null
+
+comment_pullrequest
+^^^^^^^^^^^^^^^^^^^
+
+Add comment, change status or close a given pull request. This command can only be executed
+using the api_key of a user with read permissions to the original repository.
+
+INPUT::
+
+    id : <id_for_response>
+    api_key : "<api_key>"
+    method  : "comment_pullrequest"
+    args:     {
+                "pull_request_id":  "<pull_request_id>",
+                "comment_msg":      Optional(''),
+                "status":           Optional(None),     # "under_review", "approved" or "rejected"
+                "close_pr":         Optional(False)",
+              }
+
+OUTPUT::
+
+    id : <id_given_in_input>
+    result: True
+    error:  null
+
+
+API access for web views
+------------------------
+
+API access can also be turned on for each web view in Kallithea that is
+decorated with the ``@LoginRequired`` decorator. Some views use
+``@LoginRequired(api_access=True)`` and are always available. By default only
+RSS/Atom feed views are enabled. Other views are
+only available if they have been whitelisted. Edit the
+``api_access_controllers_whitelist`` option in your .ini file and define views
+that should have API access enabled.
+
+For example, to enable API access to patch/diff, raw file and archive::
+
+    api_access_controllers_whitelist =
+        ChangesetController:changeset_patch,
+        ChangesetController:changeset_raw,
+        FilesController:raw,
+        FilesController:archivefile
+
+After this change, a Kallithea view can be accessed without login using
+bearer authentication, by including this header with the request::
+
+    Authentication: Bearer <api_key>
+
+Alternatively, the API key can be passed in the URL query string using
+``?api_key=<api_key>``, though this is not recommended due to the increased
+risk of API key leaks, and support will likely be removed in the future.
+
+Exposing raw diffs is a good way to integrate with
+third-party services like code review, or build farms that can download archives.
--- a/docs/api/models.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/api/models.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -10,9 +10,6 @@
 .. automodule:: kallithea.model.comment
    :members:
 
-.. automodule:: kallithea.model.notification
-   :members:
-
 .. automodule:: kallithea.model.permission
    :members:
 
--- a/docs/changelog.rst	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-.. _changelog:
-
-=========
-Changelog
-=========
-
-Kallithea project doesn't keep its changelog here.  We refer you to our `Mercurial logs`__.
-
-
-.. __: https://kallithea-scm.org/repos/kallithea/changelog
--- a/docs/contributing.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/contributing.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -28,62 +28,179 @@
 Getting started
 ---------------
 
-To get started with development::
+To get started with Kallithea development::
 
         hg clone https://kallithea-scm.org/repos/kallithea
         cd kallithea
         virtualenv ../kallithea-venv
         source ../kallithea-venv/bin/activate
-        pip install --upgrade pip "setuptools<34"
-        pip install -e .
-        paster make-config Kallithea my.ini
-        paster setup-db my.ini --user=user --email=user@example.com --password=password --repos=/tmp
-        paster serve my.ini --reload &
+        pip install --upgrade pip setuptools
+        pip install --upgrade -e .
+        pip install --upgrade -r dev_requirements.txt
+        kallithea-cli config-create my.ini
+        kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
+        kallithea-cli front-end-build
+        gearbox serve -c my.ini --reload &
         firefox http://127.0.0.1:5000/
 
-You can also start out by forking https://bitbucket.org/conservancy/kallithea
-on Bitbucket_ and create a local clone of your own fork.
+If you plan to use Bitbucket_ for sending contributions, you can also fork
+Kallithea on Bitbucket_ first (https://bitbucket.org/conservancy/kallithea) and
+then replace the clone step above by a clone of your fork. In this case, please
+see :ref:`contributing-guidelines` below for configuring your fork correctly.
+
+
+Contribution flow
+-----------------
+
+Starting from an existing Kallithea clone, make sure it is up to date with the
+latest upstream changes::
+
+        hg pull
+        hg update
+
+Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`.
+
+If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners
+Guide`_ on the Mercurial wiki.
+
+Now, make some changes and test them (see :ref:`contributing-tests`). Don't
+forget to add new tests to cover new functionality or bug fixes.
+
+For documentation changes, run ``make html`` from the ``docs`` directory to
+generate the HTML result, then review them in your browser.
+
+Before submitting any changes, run the cleanup script::
+
+        ./scripts/run-all-cleanup
+
+When you are completely ready, you can send your changes to the community for
+review and inclusion. Most commonly used methods are sending patches to the
+mailing list (via ``hg email``) or by creating a pull request on Bitbucket_.
+
+.. _contributing-tests:
 
 
 Running tests
 -------------
 
-After finishing your changes make sure all tests pass cleanly. You can run
-the testsuite running ``nosetests`` from the project root, or if you use tox
-run ``tox`` for Python 2.6--2.7 with multiple database test.
+After finishing your changes make sure all tests pass cleanly. Run the testsuite
+by invoking ``py.test`` from the project root::
+
+    py.test
+
+Note that testing on Python 2.6 also requires ``unittest2``.
 
-When running tests, Kallithea uses `kallithea/tests/test.ini` and populates the
-SQLite database specified there.
+Note that on unix systems, the temporary directory (``/tmp`` or where
+``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
+and the test suite creates repositories in the temporary directory. Linux
+systems with /tmp mounted noexec will thus fail.
+
+You can also use ``tox`` to run the tests with all supported Python versions
+(currently Python 2.6--2.7).
+
+When running tests, Kallithea generates a `test.ini` based on template values
+in `kallithea/tests/conftest.py` and populates the SQLite database specified
+there.
 
 It is possible to avoid recreating the full test database on each invocation of
 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
 
-    paster serve kallithea/tests/test.ini --pid-file=test.pid --daemon
-    KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 nosetests
+    gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon
+    KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
     kill -9 $(cat test.pid)
 
-You can run individual tests by specifying their path as argument to nosetests.
-nosetests also has many more options, see `nosetests -h`. Some useful options
+In these commands, the following variables are used::
+
+    KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
+    KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
+
+You can run individual tests by specifying their path as argument to py.test.
+py.test also has many more options, see `py.test -h`. Some useful options
 are::
 
-    -x, --stop            Stop running tests after the first error or failure
-    -s, --nocapture       Don't capture stdout (any stdout output will be
-                          printed immediately) [NOSE_NOCAPTURE]
-    --failed              Run the tests that failed in the last test run.
+    -k EXPRESSION         only run tests which match the given substring
+                          expression. An expression is a python evaluable
+                          expression where all names are substring-matched
+                          against test names and their parent classes. Example:
+    -x, --exitfirst       exit instantly on first error or failed test.
+    --lf                  rerun only the tests that failed at the last run (or
+                          all if none failed)
+    --ff                  run all tests but run the last failures first. This
+                          may re-order tests and thus lead to repeated fixture
+                          setup/teardown
+    --pdb                 start the interactive Python debugger on errors.
+    -s, --capture=no      don't capture stdout (any stdout output will be
+                          printed immediately)
+
+Performance tests
+^^^^^^^^^^^^^^^^^
+
+A number of performance tests are present in the test suite, but they are
+not run in a standard test run. These tests are useful to
+evaluate the impact of certain code changes with respect to performance.
+
+To run these tests::
+
+    env TEST_PERFORMANCE=1 py.test kallithea/tests/performance
+
+To analyze performance, you could install pytest-profiling_, which enables the
+--profile and --profile-svg options to py.test.
+
+.. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling
+
+.. _contributing-guidelines:
 
 
-Coding/contribution guidelines
-------------------------------
+Contribution guidelines
+-----------------------
 
 Kallithea is GPLv3 and we assume all contributions are made by the
 committer/contributor and under GPLv3 unless explicitly stated. We do care a
 lot about preservation of copyright and license information for existing code
 that is brought into the project.
 
+Contributions will be accepted in most formats -- such as pull requests on
+Bitbucket, something hosted on your own Kallithea instance, or patches sent by
+email to the `kallithea-general`_ mailing list.
+
+When contributing via Bitbucket, please make your fork of
+https://bitbucket.org/conservancy/kallithea/ `non-publishing`_ -- it is one of
+the settings on "Repository details" page. This ensures your commits are in
+"draft" phase and makes it easier for you to address feedback and for project
+maintainers to integrate your changes.
+
+.. _non-publishing: https://www.mercurial-scm.org/wiki/Phases#Publishing_Repository
+
+Make sure to test your changes both manually and with the automatic tests
+before posting.
+
+We care about quality and review and keeping a clean repository history. We
+might give feedback that requests polishing contributions until they are
+"perfect". We might also rebase and collapse and make minor adjustments to your
+changes when we apply them.
+
+We try to make sure we have consensus on the direction the project is taking.
+Everything non-sensitive should be discussed in public -- preferably on the
+mailing list.  We aim at having all non-trivial changes reviewed by at least
+one other core developer before pushing. Obvious non-controversial changes will
+be handled more casually.
+
+There is a main development branch ("default") which is generally stable so that
+it can be (and is) used in production. There is also a "stable" branch that is
+almost exclusively reserved for bug fixes or trivial changes. Experimental
+changes should live elsewhere (for example in a pull request) until they are
+ready.
+
+.. _coding-guidelines:
+
+
+Coding guidelines
+-----------------
+
 We don't have a formal coding/formatting standard. We are currently using a mix
 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
-consistency with existing code. Run whitespacecleanup.sh to avoid stupid
-whitespace noise in your patches.
+consistency with existing code. Run ``scripts/run-all-cleanup`` before
+committing to ensure some basic code formatting consistency.
 
 We support both Python 2.6.x and 2.7.x and nothing else. For now we don't care
 about Python 3 compatibility.
@@ -112,30 +229,66 @@
 
 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
 
-Contributions will be accepted in most formats -- such as pull requests on
-bitbucket, something hosted on your own Kallithea instance, or patches sent by
-email to the `kallithea-general`_ mailing list.
+Template helpers (that is, everything in ``kallithea.lib.helpers``)
+should only be referenced from templates. If you need to call a
+helper from the Python code, consider moving the function somewhere
+else (e.g. to the model).
+
+Notes on the SQLAlchemy session
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each HTTP request runs inside an independent SQLAlchemy session (as well
+as in an independent database transaction). ``Session`` is the session manager
+and factory. ``Session()`` will create a new session on-demand or return the
+current session for the active thread. Many database operations are methods on
+such session instances - only ``Session.remove()`` should be called directly on
+the manager.
 
-Make sure to test your changes both manually and with the automatic tests
-before posting.
+Database model objects
+(almost) always belong to a particular SQLAlchemy session, which means
+that SQLAlchemy will ensure that they're kept in sync with the database
+(but also means that they cannot be shared across requests).
 
-We care about quality and review and keeping a clean repository history. We
-might give feedback that requests polishing contributions until they are
-"perfect". We might also rebase and collapse and make minor adjustments to your
-changes when we apply them.
+Objects can be added to the session using ``Session().add``, but this is
+rarely needed:
+
+* When creating a database object by calling the constructor directly,
+  it must explicitly be added to the session.
+
+* When creating an object using a factory function (like
+  ``create_repo``), the returned object has already (by convention)
+  been added to the session, and should not be added again.
 
-We try to make sure we have consensus on the direction the project is taking.
-Everything non-sensitive should be discussed in public -- preferably on the
-mailing list.  We aim at having all non-trivial changes reviewed by at least
-one other core developer before pushing. Obvious non-controversial changes will
-be handled more casually.
+* When getting an object from the session (via ``Session().query`` or
+  any of the utility functions that look up objects in the database),
+  it's already part of the session, and should not be added again.
+  SQLAlchemy monitors attribute modifications automatically for all
+  objects it knows about and syncs them to the database.
+
+SQLAlchemy also flushes changes to the database automatically; manually
+calling ``Session().flush`` is usually only necessary when the Python
+code needs the database to assign an "auto-increment" primary key ID to
+a freshly created model object (before flushing, the ID attribute will
+be ``None``).
+
+TurboGears2 DebugBar
+^^^^^^^^^^^^^^^^^^^^
 
-For now we just have one official branch ("default") and will keep it so stable
-that it can be (and is) used in production. Experimental changes should live
-elsewhere (for example in a pull request) until they are ready.
+It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
+over the Kallithea web interface, allowing you to see:
+
+* timing information of the current request, including profiling information
+* request data, including GET data, POST data, cookies, headers and environment
+  variables
+* a list of executed database queries, including timing and result values
 
-.. _translations:
-.. include:: ./../kallithea/i18n/how_to
+DebugBar is only activated when ``debug = true`` is set in the configuration
+file. This is important, because the DebugBar toolbar will be visible for all
+users, and allow them to see information they should not be allowed to see. Like
+is anyway the case for ``debug = true``, do not use this in production!
+
+To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
+``pip``) and restart Kallithea (in debug mode).
 
 
 "Roadmap"
@@ -158,3 +311,6 @@
 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
 .. _wiki: https://bitbucket.org/conservancy/kallithea/wiki/Home
+.. _DebugBar: https://github.com/TurboGears/tgext.debugbar
+.. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart
+.. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/dev/dbmigrations.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,74 @@
+=======================
+Database schema changes
+=======================
+
+Kallithea uses Alembic for :ref:`database migrations <upgrade_db>`
+(upgrades and downgrades).
+
+If you are developing a Kallithea feature that requires database schema
+changes, you should make a matching Alembic database migration script:
+
+1. :ref:`Create a Kallithea configuration and database <setup>` for testing
+   the migration script, or use existing ``development.ini`` setup.
+
+   Ensure that this database is up to date with the latest database
+   schema *before* the changes you're currently developing. (Do not
+   create the database while your new schema changes are applied.)
+
+2. Create a separate throwaway configuration for iterating on the actual
+   database changes::
+
+    kallithea-cli config-create temp.ini
+
+   Edit the file to change database settings. SQLite is typically fine,
+   but make sure to change the path to e.g. ``temp.db``, to avoid
+   clobbering any existing database file.
+
+3. Make your code changes (including database schema changes in ``db.py``).
+
+4. After every database schema change, recreate the throwaway database
+   to test the changes::
+
+    rm temp.db
+    kallithea-cli db-create -c temp.ini --repos=/var/repos --user=doe --email doe@example.com --password=123456 --no-public-access --force-yes
+    kallithea-cli repo-scan -c temp.ini
+
+5. Once satisfied with the schema changes, auto-generate a draft Alembic
+   script using the development database that has *not* been upgraded.
+   (The generated script will upgrade the database to match the code.)
+
+   ::
+
+    alembic -c development.ini revision -m "area: add cool feature" --autogenerate
+
+6. Edit the script to clean it up and fix any problems.
+
+   Note that for changes that simply add columns, it may be appropriate
+   to not remove them in the downgrade script (and instead do nothing),
+   to avoid the loss of data. Unknown columns will simply be ignored by
+   Kallithea versions predating your changes.
+
+7. Run ``alembic -c development.ini upgrade head`` to apply changes to
+   the (non-throwaway) database, and test the upgrade script. Also test
+   downgrades.
+
+   The included ``development.ini`` has full SQL logging enabled. If
+   you're using another configuration file, you may want to enable it
+   by setting ``level = DEBUG`` in section ``[handler_console_sql]``.
+
+The Alembic migration script should be committed in the same revision as
+the database schema (``db.py``) changes.
+
+See the `Alembic documentation`__ for more information, in particular
+the tutorial and the section about auto-generating migration scripts.
+
+.. __: http://alembic.zzzcomputing.com/en/latest/
+
+
+Troubleshooting
+---------------
+
+* If ``alembic --autogenerate`` responds "Target database is not up to
+  date", you need to either first use Alembic to upgrade the database
+  to the most recent version (before your changes), or recreate the
+  database from scratch (without your schema changes applied).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/dev/translation.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,2 @@
+.. _translations:
+.. include:: ./../../kallithea/i18n/how_to
--- a/docs/index.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/index.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -4,14 +4,23 @@
 Kallithea Documentation
 #######################
 
-**Readme**
+* :ref:`genindex`
+* :ref:`search`
+
+
+Readme
+******
 
 .. toctree::
    :maxdepth: 1
 
    readme
 
-**Installation**
+
+Administrator guide
+*******************
+
+**Installation and upgrade**
 
 .. toctree::
    :maxdepth: 1
@@ -21,52 +30,53 @@
    installation_win
    installation_win_old
    installation_iis
+   installation_puppet
+   upgrade
+
+**Setup and configuration**
+
+.. toctree::
+   :maxdepth: 1
+
    setup
-   installation_puppet
+   administrator_guide/auth
+   administrator_guide/vcs_setup
+   usage/email
+   usage/customization
+
+**Maintenance**
 
-**Usage**
+.. toctree::
+   :maxdepth: 1
+
+   usage/backup
+   usage/performance
+   usage/debugging
+   usage/troubleshooting
+
+
+User guide
+**********
 
 .. toctree::
    :maxdepth: 1
 
    usage/general
-   usage/vcs_support
+   usage/vcs_notes
    usage/locking
    usage/statistics
-
-**Administrator's guide**
+   api/api
 
-.. toctree::
-   :maxdepth: 1
 
-   usage/email
-   usage/performance
-   usage/backup
-   usage/debugging
-   usage/troubleshooting
-
-**Development**
+Developer guide
+***************
 
 .. toctree::
    :maxdepth: 1
 
    contributing
-   changelog
-
-**API**
-
-.. toctree::
-   :maxdepth: 1
-
-   api/api
-   api/models
-
-
-Other topics
-------------
-
-* :ref:`genindex`
-* :ref:`search`
+   dev/translation
+   dev/dbmigrations
 
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
--- a/docs/installation.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/installation.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -26,6 +26,22 @@
   have to remove its dependencies manually and make sure that they are not
   needed by other packages.
 
+Regardless of the installation method you may need to make sure you have
+appropriate development packages installed, as installation of some of the
+Kallithea dependencies requires a working C compiler and libffi library
+headers. Depending on your configuration, you may also need to install
+Git and development packages for the database of your choice.
+
+For Debian and Ubuntu, the following command will ensure that a reasonable
+set of dependencies is installed::
+
+    sudo apt-get install build-essential git python-pip python-virtualenv libffi-dev python-dev
+
+For Fedora and RHEL-derivatives, the following command will ensure that a
+reasonable set of dependencies is installed::
+
+    sudo yum install gcc git python-pip python-virtualenv libffi-devel python-devel
+
 .. _installation-source:
 
 
@@ -38,16 +54,13 @@
         hg clone https://kallithea-scm.org/repos/kallithea -u stable
         cd kallithea
         virtualenv ../kallithea-venv
-        source ../kallithea-venv/bin/activate
-        pip install --upgrade pip "setuptools<34"
-        pip install -e .
+        . ../kallithea-venv/bin/activate
+        pip install --upgrade pip setuptools
+        pip install --upgrade -e .
         python2 setup.py compile_catalog   # for translation of the UI
 
 You can now proceed to :ref:`setup`.
 
-To upgrade, simply update the repository with ``hg pull -u`` and restart the
-server.
-
 .. _installation-virtualenv:
 
 
@@ -68,8 +81,8 @@
 - Activate the virtualenv_ in your current shell session and make sure the
   basic requirements are up-to-date by running::
 
-    source /srv/kallithea/venv/bin/activate
-    pip install --upgrade pip "setuptools<34"
+    . /srv/kallithea/venv/bin/activate
+    pip install --upgrade pip setuptools
 
 .. note:: You can't use UNIX ``sudo`` to source the ``virtualenv`` script; it
    will "activate" a shell that terminates immediately. It is also perfectly
@@ -78,8 +91,8 @@
 .. note:: Some dependencies are optional. If you need them, install them in
    the virtualenv too::
 
-     pip install psycopg2
-     pip install python-ldap
+     pip install --upgrade psycopg2
+     pip install --upgrade python-ldap
 
    This might require installation of development packages using your
    distribution's package manager.
@@ -91,15 +104,15 @@
 
 - Go into the created directory and run this command to install Kallithea::
 
-    pip install kallithea
+    pip install --upgrade kallithea
 
   Alternatively, download a .tar.gz from http://pypi.python.org/pypi/Kallithea,
   extract it and run::
 
-    pip install .
+    pip install --upgrade .
 
-- This will install Kallithea together with pylons_ and all other required
-  python libraries into the activated virtualenv.
+- This will install Kallithea together with all other required
+  Python libraries into the activated virtualenv.
 
 You can now proceed to :ref:`setup`.
 
@@ -123,90 +136,4 @@
 You can now proceed to :ref:`setup`.
 
 
-Upgrading Kallithea from Python Package Index (PyPI)
-----------------------------------------------------
-
-.. note::
-   It is strongly recommended that you **always** perform a database and
-   configuration backup before doing an upgrade.
-
-   These directions will use '{version}' to note that this is the version of
-   Kallithea that these files were used with.  If backing up your Kallithea
-   instance from version 0.1 to 0.2, the ``my.ini`` file could be
-   backed up to ``my.ini.0-1``.
-
-If using a SQLite database, stop the Kallithea process/daemon/service, and
-then make a copy of the database file::
-
- service kallithea stop
- cp kallithea.db kallithea.db.{version}
-
-Back up your configuration file::
-
- cp my.ini my.ini.{version}
-
-Ensure that you are using the Python virtual environment that you originally
-installed Kallithea in by running::
-
- pip freeze
-
-This will list all packages installed in the current environment.  If
-Kallithea isn't listed, activate the correct virtual environment::
-
- source /srv/kallithea/venv/bin/activate
-
-Once you have verified the environment you can upgrade Kallithea with::
-
- pip install --upgrade kallithea
-
-Then run the following command from the installation directory::
-
- paster make-config Kallithea my.ini
-
-This will display any changes made by the new version of Kallithea to your
-current configuration. It will try to perform an automerge. It is recommended
-that you recheck the content after the automerge.
-
-.. note::
-   Please always make sure your .ini files are up to date. Errors can
-   often be caused by missing parameters added in new versions.
-
-It is also recommended that you rebuild the whoosh index after upgrading since
-the new whoosh version could introduce some incompatible index changes. Please
-read the changelog to see if there were any changes to whoosh.
-
-The final step is to upgrade the database. To do this simply run::
-
- paster upgrade-db my.ini
-
-This will upgrade the schema and update some of the defaults in the database,
-and will always recheck the settings of the application, if there are no new
-options that need to be set.
-
-.. note::
-   The DB schema upgrade library has some limitations and can sometimes fail if you try to
-   upgrade from older major releases. In such a case simply run upgrades sequentially, e.g.,
-   upgrading from 0.1.X to 0.3.X should be done like this: 0.1.X. > 0.2.X > 0.3.X
-   You can always specify what version of Kallithea you want to install for example in pip
-   `pip install Kallithea==0.2`
-
-You may find it helpful to clear out your log file so that new errors are
-readily apparent::
-
- echo > kallithea.log
-
-Once that is complete, you may now start your upgraded Kallithea Instance::
-
- service kallithea start
-
-Or::
-
- paster serve /srv/kallithea/my.ini
-
-.. note::
-   If you're using Celery, make sure you restart all instances of it after
-   upgrade.
-
-
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
-.. _pylons: http://www.pylonsproject.org/
--- a/docs/installation_iis.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/installation_iis.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -39,7 +39,7 @@
 own virtual folder will be noted where appropriate.
 
 Application pool
-................
+^^^^^^^^^^^^^^^^
 
 Make sure that there is a unique application pool for the Kallithea application
 with an identity that has read access to the Kallithea distribution.
@@ -55,11 +55,11 @@
     as long as the Kallithea requirements are met by the existing pool.
 
 ISAPI handler
-.............
+^^^^^^^^^^^^^
 
 The ISAPI handler can be generated using::
 
-    paster install-iis my.ini --virtualdir=/
+    kallithea-cli iis-install -c my.ini --virtualdir=/
 
 This will generate a ``dispatch.py`` file in the current directory that contains
 the necessary components to finalize an installation into IIS. Once this file
@@ -74,12 +74,12 @@
 
 The ISAPI handler is registered to all file extensions, so it will automatically
 be the one handling all requests to the specified virtual directory. When the website starts
-the ISAPI handler, it will start a thread pool managed wrapper around the paster
+the ISAPI handler, it will start a thread pool managed wrapper around the
 middleware WSGI handler that Kallithea runs within and each HTTP request to the
 site will be processed through this logic henceforth.
 
 Authentication with Kallithea using IIS authentication modules
-..............................................................
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 The recommended way to handle authentication with Kallithea using IIS is to let
 IIS handle all the authentication and just pass it to Kallithea.
@@ -111,7 +111,7 @@
 ---------------
 
 Typically, any issues in this setup will either be entirely in IIS or entirely
-in Kallithea (or Kallithea's WSGI/paster middleware). Consequently, two
+in Kallithea (or Kallithea's WSGI middleware). Consequently, two
 different options for finding issues exist: IIS' failed request tracking which
 is great at finding issues until they exist inside Kallithea, at which point the
 ISAPI-WSGI wrapper above uses ``win32traceutil``, which is part of ``pywin32``.
--- a/docs/installation_puppet.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/installation_puppet.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -90,7 +90,7 @@
 parameter in the :ref:`example above <simple_manifest>`, but there are more.
 For example, you can specify the installation directory, the name of the user
 under which Kallithea gets installed, the initial admin password, etc.
-Notably, you can provide arbitrary modifications to Kallitheas configuration
+Notably, you can provide arbitrary modifications to Kallithea's configuration
 file by means of the ``config_hash`` parameter.
 
 Parameters, which have not been set explicitly, will be set to default values,
--- a/docs/installation_win.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/installation_win.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -1,12 +1,12 @@
 .. _installation_win:
 
-================================================================
-Installation and upgrade on Windows (7/Server 2008 R2 and newer)
-================================================================
+====================================================
+Installation on Windows (7/Server 2008 R2 and newer)
+====================================================
 
 
 First time install
-::::::::::::::::::
+------------------
 
 Target OS: Windows 7 and newer or Windows Server 2008 R2 and newer
 
@@ -15,7 +15,7 @@
 To install on an older version of Windows, see `<installation_win_old.html>`_
 
 Step 1 -- Install Python
-------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^
 
 Install Python 2.x.y (x = 6 or 7). Latest version is recommended. If you need another version, they can run side by side.
 
@@ -31,7 +31,7 @@
 be needed in the next step. In this case, it is "2.7".
 
 Step 2 -- Python BIN
---------------------
+^^^^^^^^^^^^^^^^^^^^
 
 Add Python BIN folder to the path. This can be done manually (editing
 "PATH" environment variable) or by using Windows Support Tools that
@@ -45,7 +45,7 @@
 path. Typically this is ``C:\\Python27``.
 
 Step 3 -- Install pywin32 extensions
-------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Download pywin32 from:
 http://sourceforge.net/projects/pywin32/files/
@@ -61,7 +61,7 @@
   (Win32)
 
 Step 4 -- Install pip
----------------------
+^^^^^^^^^^^^^^^^^^^^^
 
 pip is a package management system for Python. You will need it to install Kallithea and its dependencies.
 
@@ -85,7 +85,7 @@
   SETX PATH "%PATH%;[your-python-path]\Scripts" /M
 
 Step 5 -- Kallithea folder structure
-------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Create a Kallithea folder structure.
 
@@ -102,7 +102,7 @@
   C:\Kallithea\Repos
 
 Step 6 -- Install virtualenv
-----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 .. note::
    A python virtual environment will allow for isolation between the Python packages of your system and those used for Kallithea.
@@ -119,7 +119,7 @@
   virtualenv C:\Kallithea\Env
 
 Step 7 -- Install Kallithea
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In order to install Kallithea, you need to be able to run "pip install kallithea". It will use pip to install the Kallithea Python package and its dependencies.
 Some Python packages use managed code and need to be compiled.
@@ -134,7 +134,7 @@
 
   cd C:\Kallithea\Env\Scripts
   activate
-  pip install --upgrade pip "setuptools<34"
+  pip install --upgrade pip setuptools
 
 The prompt will change into "(Env) C:\\Kallithea\\Env\\Scripts" or similar
 (depending of your folder structure). Then type::
@@ -145,17 +145,19 @@
           complete. Some warnings will appear. Don't worry, they are
           normal.
 
-Step 8 -- Install git (optional)
---------------------------------
+Step 8 -- Install Git (optional)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Mercurial being a python package, was installed automatically when doing ``pip install kallithea``.
 
-Mercurial being a python package, it was installed automatically when doing "pip install kallithea".
-
-You need to install git manually if you want Kallithea to be able to host git repositories.
-
+You need to install Git manually if you want Kallithea to be able to host Git repositories.
 See http://git-scm.com/book/en/v2/Getting-Started-Installing-Git#Installing-on-Windows for instructions.
+The location of the Git binaries (like ``c:\path\to\git\bin``) must be
+added to the ``PATH`` environment variable so ``git.exe`` and other tools like
+``gzip.exe`` are available.
 
 Step 9 -- Configuring Kallithea
--------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Steps taken from `<setup.html>`_
 
@@ -164,9 +166,9 @@
 one). When ready, type::
 
   cd C:\Kallithea\Bin
-  paster make-config Kallithea production.ini
+  kallithea-cli config-create my.ini
 
-Then you must edit production.ini to fit your needs (IP address, IP
+Then you must edit my.ini to fit your needs (IP address, IP
 port, mail settings, database, etc.). `NotePad++`__ or a similar text
 editor is recommended to properly handle the newline character
 differences between Unix and Windows.
@@ -175,11 +177,11 @@
 
 For the sake of simplicity, run it with the default settings. After your edits (if any) in the previous command prompt, type::
 
-  paster setup-db production.ini
+  kallithea-cli db-create -c my.ini
 
 .. warning:: This time a *new* database will be installed. You must
-             follow a different step to later *upgrade* to a newer
-             Kallithea version)
+             follow a different process to later :ref:`upgrade <upgrade>`
+             to a newer Kallithea version.
 
 The script will ask you for confirmation about creating a new database, answer yes (y)
 
@@ -191,14 +193,14 @@
 
 If you make a mistake and the script doesn't end, don't worry: start it again.
 
-If you decided not to install git, you will get errors about it that you can ignore.
+If you decided not to install Git, you will get errors about it that you can ignore.
 
 Step 10 -- Running Kallithea
-----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In the previous command prompt, being in the C:\\Kallithea\\Bin folder, type::
 
-  paster serve production.ini
+  gearbox serve -c my.ini
 
 Open your web server, and go to http://127.0.0.1:5000
 
@@ -219,27 +221,3 @@
 - Using Apache. You can investigate here:
 
   - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4
-
-
-Upgrading
-:::::::::
-
-Stop running Kallithea
-Open a CommandPrompt like in Step 7 (cd to C:\Kallithea\Env\Scripts and activate) and type::
-
-  pip install kallithea --upgrade
-  cd \Kallithea\Bin
-
-Backup your production.ini file now.
-
-Then run::
-
-  paster make-config Kallithea production.ini
-
-Look for changes and update your production.ini accordingly.
-
-Next, update the database::
-
-  paster upgrade-db production.ini
-
-More details can be found in `<upgrade.html>`_.
--- a/docs/installation_win_old.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/installation_win_old.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -1,12 +1,12 @@
 .. _installation_win_old:
 
-======================================================================
-Installation and upgrade on Windows (XP/Vista/Server 2003/Server 2008)
-======================================================================
+==========================================================
+Installation on Windows (XP/Vista/Server 2003/Server 2008)
+==========================================================
 
 
 First-time install
-::::::::::::::::::
+------------------
 
 Target OS: Windows XP SP3 32-bit English (Clean installation)
 + All Windows Updates until 24-may-2012
@@ -24,7 +24,7 @@
    - http://bugs.python.org/issue7511
 
 Step 1 -- Install Visual Studio 2008 Express
---------------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Optional: You can also install MinGW, but VS2008 installation is easier.
 
@@ -58,7 +58,7 @@
    Copy C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat to C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat
 
 Step 2 -- Install Python
-------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^
 
 Install Python 2.x.y (x = 6 or 7) x86 version (32-bit). DO NOT USE A 3.x version.
 Download Python 2.x.y from:
@@ -74,7 +74,7 @@
    64-bit: Just download and install the 64-bit version of python.
 
 Step 3 -- Install Win32py extensions
-------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Download pywin32 from:
 http://sourceforge.net/projects/pywin32/files/
@@ -93,7 +93,7 @@
      http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download
 
 Step 4 -- Python BIN
---------------------
+^^^^^^^^^^^^^^^^^^^^
 
 Add Python BIN folder to the path
 
@@ -120,7 +120,7 @@
   Typically: C:\\Python27
 
 Step 5 -- Kallithea folder structure
-------------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Create a Kallithea folder structure
 
@@ -137,7 +137,7 @@
   C:\Kallithea\Repos
 
 Step 6 -- Install virtualenv
-----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Install Virtual Env for Python
 
@@ -157,7 +157,7 @@
 to include it)
 
 Step 7 -- Install Kallithea
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Finally, install Kallithea
 
@@ -183,7 +183,7 @@
 
   cd C:\Kallithea\Env\Scripts (or similar)
   activate
-  pip install --upgrade pip "setuptools<34"
+  pip install --upgrade pip setuptools
 
 The prompt will change into "(Env) C:\\Kallithea\\Env\\Scripts" or similar
 (depending of your folder structure). Then type::
@@ -195,7 +195,7 @@
 Some warnings will appear, don't worry as they are normal.
 
 Step 8 -- Configuring Kallithea
--------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 steps taken from http://packages.python.org/Kallithea/setup.html
 
@@ -204,9 +204,9 @@
 "activate" one). When ready, just type::
 
   cd C:\Kallithea\Bin
-  paster make-config Kallithea production.ini
+  kallithea-cli config-create my.ini
 
-Then, you must edit production.ini to fit your needs (network address and
+Then, you must edit my.ini to fit your needs (network address and
 port, mail settings, database, whatever). I recommend using NotePad++
 (free) or similar text editor, as it handles well the EndOfLine
 character differences between Unix and Windows
@@ -215,10 +215,11 @@
 For the sake of simplicity lets run it with the default settings. After
 your edits (if any), in the previous Command Prompt, type::
 
- paster setup-db production.ini
+  kallithea-cli db-create -c my.ini
 
-(this time a NEW database will be installed, you must follow a different
-step to later UPGRADE to a newer Kallithea version)
+.. warning:: This time a *new* database will be installed. You must
+             follow a different process to later :ref:`upgrade <upgrade>`
+             to a newer Kallithea version.
 
 The script will ask you for confirmation about creating a NEW database,
 answer yes (y)
@@ -233,12 +234,12 @@
 it again.
 
 Step 9 -- Running Kallithea
----------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 In the previous command prompt, being in the C:\\Kallithea\\Bin folder,
 just type::
 
- paster serve production.ini
+ gearbox serve -c my.ini
 
 Open yout web server, and go to http://127.0.0.1:5000
 
@@ -260,23 +261,3 @@
 - Using Apache. You can investigate here:
 
   - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4
-
-
-Upgrading
-:::::::::
-
-Stop running Kallithea
-Open a CommandPrompt like in Step7 (VS2008 path + activate) and type::
-
- easy_install -U kallithea
- cd \Kallithea\Bin
-
-{ backup your production.ini file now} ::
-
- paster make-config Kallithea production.ini
-
-(check changes and update your production.ini accordingly) ::
-
- paster upgrade-db production.ini (update database)
-
-Full steps in http://packages.python.org/Kallithea/upgrade.html
--- a/docs/make.bat	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/make.bat	Sun Mar 31 21:28:56 2019 +0200
@@ -3,153 +3,153 @@
 REM Command file for Sphinx documentation
 
 if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
+    set SPHINXBUILD=sphinx-build
 )
 set BUILDDIR=_build
 set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
 if NOT "%PAPER%" == "" (
-	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+    set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
 )
 
 if "%1" == "" goto help
 
 if "%1" == "help" (
-	:help
-	echo.Please use `make ^<target^>` where ^<target^> is one of
-	echo.  html       to make standalone HTML files
-	echo.  dirhtml    to make HTML files named index.html in directories
-	echo.  singlehtml to make a single large HTML file
-	echo.  pickle     to make pickle files
-	echo.  json       to make JSON files
-	echo.  htmlhelp   to make HTML files and a HTML help project
-	echo.  qthelp     to make HTML files and a qthelp project
-	echo.  devhelp    to make HTML files and a Devhelp project
-	echo.  epub       to make an epub
-	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
-	echo.  text       to make text files
-	echo.  man        to make manual pages
-	echo.  changes    to make an overview over all changed/added/deprecated items
-	echo.  linkcheck  to check all external links for integrity
-	echo.  doctest    to run all doctests embedded in the documentation if enabled
-	goto end
+    :help
+    echo.Please use `make ^<target^>` where ^<target^> is one of
+    echo.  html       to make standalone HTML files
+    echo.  dirhtml    to make HTML files named index.html in directories
+    echo.  singlehtml to make a single large HTML file
+    echo.  pickle     to make pickle files
+    echo.  json       to make JSON files
+    echo.  htmlhelp   to make HTML files and a HTML help project
+    echo.  qthelp     to make HTML files and a qthelp project
+    echo.  devhelp    to make HTML files and a Devhelp project
+    echo.  epub       to make an epub
+    echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+    echo.  text       to make text files
+    echo.  man        to make manual pages
+    echo.  changes    to make an overview over all changed/added/deprecated items
+    echo.  linkcheck  to check all external links for integrity
+    echo.  doctest    to run all doctests embedded in the documentation if enabled
+    goto end
 )
 
 if "%1" == "clean" (
-	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
-	del /q /s %BUILDDIR%\*
-	goto end
+    for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+    del /q /s %BUILDDIR%\*
+    goto end
 )
 
 if "%1" == "html" (
-	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
-	goto end
+    %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+    echo.
+    echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+    goto end
 )
 
 if "%1" == "dirhtml" (
-	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
-	goto end
+    %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+    echo.
+    echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+    goto end
 )
 
 if "%1" == "singlehtml" (
-	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
-	goto end
+    %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+    echo.
+    echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+    goto end
 )
 
 if "%1" == "pickle" (
-	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
-	echo.
-	echo.Build finished; now you can process the pickle files.
-	goto end
+    %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+    echo.
+    echo.Build finished; now you can process the pickle files.
+    goto end
 )
 
 if "%1" == "json" (
-	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
-	echo.
-	echo.Build finished; now you can process the JSON files.
-	goto end
+    %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+    echo.
+    echo.Build finished; now you can process the JSON files.
+    goto end
 )
 
 if "%1" == "htmlhelp" (
-	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
-	echo.
-	echo.Build finished; now you can run HTML Help Workshop with the ^
+    %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+    echo.
+    echo.Build finished; now you can run HTML Help Workshop with the ^
 .hhp project file in %BUILDDIR%/htmlhelp.
-	goto end
+    goto end
 )
 
 if "%1" == "qthelp" (
-	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
-	echo.
-	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+    %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+    echo.
+    echo.Build finished; now you can run "qcollectiongenerator" with the ^
 .qhcp project file in %BUILDDIR%/qthelp, like this:
-	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Kallithea.qhcp
-	echo.To view the help file:
-	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Kallithea.ghc
-	goto end
+    echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Kallithea.qhcp
+    echo.To view the help file:
+    echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Kallithea.ghc
+    goto end
 )
 
 if "%1" == "devhelp" (
-	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
-	echo.
-	echo.Build finished.
-	goto end
+    %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+    echo.
+    echo.Build finished.
+    goto end
 )
 
 if "%1" == "epub" (
-	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
-	echo.
-	echo.Build finished. The epub file is in %BUILDDIR%/epub.
-	goto end
+    %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+    echo.
+    echo.Build finished. The epub file is in %BUILDDIR%/epub.
+    goto end
 )
 
 if "%1" == "latex" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	echo.
-	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
-	goto end
+    %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+    echo.
+    echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+    goto end
 )
 
 if "%1" == "text" (
-	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
-	echo.
-	echo.Build finished. The text files are in %BUILDDIR%/text.
-	goto end
+    %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+    echo.
+    echo.Build finished. The text files are in %BUILDDIR%/text.
+    goto end
 )
 
 if "%1" == "man" (
-	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
-	echo.
-	echo.Build finished. The manual pages are in %BUILDDIR%/man.
-	goto end
+    %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+    echo.
+    echo.Build finished. The manual pages are in %BUILDDIR%/man.
+    goto end
 )
 
 if "%1" == "changes" (
-	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
-	echo.
-	echo.The overview file is in %BUILDDIR%/changes.
-	goto end
+    %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+    echo.
+    echo.The overview file is in %BUILDDIR%/changes.
+    goto end
 )
 
 if "%1" == "linkcheck" (
-	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
-	echo.
-	echo.Link check complete; look for any errors in the above output ^
+    %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+    echo.
+    echo.Link check complete; look for any errors in the above output ^
 or in %BUILDDIR%/linkcheck/output.txt.
-	goto end
+    goto end
 )
 
 if "%1" == "doctest" (
-	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
-	echo.
-	echo.Testing of doctests in the sources finished, look at the ^
+    %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+    echo.
+    echo.Testing of doctests in the sources finished, look at the ^
 results in %BUILDDIR%/doctest/output.txt.
-	goto end
+    goto end
 )
 
 :end
--- a/docs/overview.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/overview.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -69,6 +69,11 @@
   (``pip install kallithea`` from a source tree will do pretty much the same
   but build the Kallithea package itself locally instead of downloading it.)
 
+.. note::
+   Kallithea includes front-end code that needs to be processed first.
+   The tool npm_ is used to download external dependencies and orchestrate the
+   processing. The ``npm`` binary must thus be available.
+
 
 Web server
 ----------
@@ -84,17 +89,17 @@
 
 There are several web server options:
 
-- Kallithea uses the Paste_ tool as command line interface. Paste provides
-  ``paster serve`` as a convenient way to launch a Python WSGI / web server
+- Kallithea uses the Gearbox_ tool as command line interface. Gearbox provides
+  ``gearbox serve`` as a convenient way to launch a Python WSGI / web server
   from the command line. That is perfect for development and evaluation.
   Actual use in production might have different requirements and need extra
   work to make it manageable as a scalable system service.
 
-  Paste comes with its own built-in web server but Kallithea defaults to use
+  Gearbox comes with its own built-in web server but Kallithea defaults to use
   Waitress_. Gunicorn_ is also an option. These web servers have different
   limited feature sets.
 
-  The web server used by ``paster`` is configured in the ``.ini`` file passed
+  The web server used by ``gearbox`` is configured in the ``.ini`` file passed
   to it. The entry point for the WSGI application is configured
   in ``setup.py`` as ``kallithea.config.middleware:make_app``.
 
@@ -113,7 +118,7 @@
   encryption or special authentication or for other security reasons, to
   provide caching of static files, or to provide load balancing or fail-over.
   Nginx_, Varnish_ and HAProxy_ are often used for this purpose, often in front
-  of a ``paster`` server that somehow is wrapped as a service.
+  of a ``gearbox serve`` that somehow is wrapped as a service.
 
 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
@@ -126,7 +131,7 @@
 .. _Gunicorn: http://gunicorn.org/
 .. _Waitress: http://waitress.readthedocs.org/en/latest/
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
-.. _Paste: http://pythonpaste.org/
+.. _Gearbox: http://turbogears.readthedocs.io/en/latest/turbogears/gearbox.html
 .. _PyPI: https://pypi.python.org/pypi
 .. _Apache httpd: http://httpd.apache.org/
 .. _mod_wsgi: https://code.google.com/p/modwsgi/
@@ -136,6 +141,6 @@
 .. _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/
 .. _HAProxy: http://www.haproxy.org/
 .. _Varnish: https://www.varnish-cache.org/
+.. _npm: https://www.npmjs.com/
--- a/docs/setup.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/setup.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -11,12 +11,14 @@
 First, you will need to create a Kallithea configuration file. Run the
 following command to do so::
 
-    paster make-config Kallithea my.ini
+    kallithea-cli config-create my.ini
 
 This will create the file ``my.ini`` in the current directory. This
 configuration file contains the various settings for Kallithea, e.g.
 proxy port, email settings, usage of static files, cache, Celery
-settings, and logging.
+settings, and logging. Extra settings can be specified like::
+
+    kallithea-cli config-create my.ini host=8.8.8.8 "[handler_console]" formatter=color_formatter
 
 Next, you need to create the databases used by Kallithea. It is recommended to
 use PostgreSQL or SQLite (default). If you choose a database other than the
@@ -25,20 +27,20 @@
 PostgreSQL, SQLite and MySQL databases. Create the database by running
 the following command::
 
-    paster setup-db my.ini
+    kallithea-cli db-create -c my.ini
 
 This will prompt you for a "root" path. This "root" path is the location where
 Kallithea will store all of its repositories on the current machine. After
-entering this "root" path ``setup-db`` will also prompt you for a username
-and password for the initial admin account which ``setup-db`` sets
+entering this "root" path ``db-create`` will also prompt you for a username
+and password for the initial admin account which ``db-create`` sets
 up for you.
 
-The ``setup-db`` values can also be given on the command line.
+The ``db-create`` values can also be given on the command line.
 Example::
 
-    paster setup-db my.ini --user=nn --password=secret --email=nn@example.com --repos=/srv/repos
+    kallithea-cli db-create -c my.ini --user=nn --password=secret --email=nn@example.com --repos=/srv/repos
 
-The ``setup-db`` command will create all needed tables and an
+The ``db-create`` command will create all needed tables and an
 admin account. When choosing a root path you can either use a new
 empty location, or a location which already contains existing
 repositories. If you choose a location which contains existing
@@ -52,14 +54,18 @@
           but when trying to do a push it will fail with permission
           denied errors unless it has write access.
 
+Finally, prepare the front-end by running::
+
+    kallithea-cli front-end-build
+
 You are now ready to use Kallithea. To run it simply execute::
 
-    paster serve my.ini
+    gearbox serve -c my.ini
 
 - This command runs the Kallithea server. The web app should be available at
   http://127.0.0.1:5000. The IP address and port is configurable via the
   configuration file created in the previous step.
-- Log in to Kallithea using the admin account created when running ``setup-db``.
+- Log in to Kallithea using the admin account created when running ``db-create``.
 - The default permissions on each repository is read, and the owner is admin.
   Remember to update these if needed.
 - In the admin panel you can toggle LDAP, anonymous, and permissions
@@ -67,22 +73,20 @@
   repositories.
 
 
-Extensions
-----------
-
-Optionally one can create an ``rcextensions`` package that extends Kallithea
-functionality.
-To generate a skeleton extensions package, run::
+Internationalization (i18n support)
+-----------------------------------
 
-    paster make-rcext my.ini
+The Kallithea web interface is automatically displayed in the user's preferred
+language, as indicated by the browser. Thus, different users may see the
+application in different languages. If the requested language is not available
+(because the translation file for that language does not yet exist or is
+incomplete), the language specified in setting ``i18n.lang`` in the Kallithea
+configuration file is used as fallback. If no fallback language is explicitly
+specified, English is used.
 
-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.
-
-See the ``__init__.py`` file inside the generated ``rcextensions`` package
-for more details.
+If you want to disable automatic language detection and instead configure a
+fixed language regardless of user preference, set ``i18n.enabled = false`` and
+set ``i18n.lang`` to the desired language (or leave empty for English).
 
 
 Using Kallithea with SSH
@@ -129,23 +133,23 @@
 
 For an incremental index build, run::
 
-    paster make-index my.ini
+    kallithea-cli index-create -c my.ini
 
 For a full index rebuild, run::
 
-    paster make-index my.ini -f
+    kallithea-cli index-create -c my.ini --full
 
-The ``--repo-location`` option allows the location of the repositories to be overriden;
+The ``--repo-location`` option allows the location of the repositories to be overridden;
 usually, the location is retrieved from the Kallithea database.
 
 The ``--index-only`` option can be used to limit the indexed repositories to a comma-separated list::
 
-    paster make-index my.ini --index-only=vcs,kallithea
+    kallithea-cli index-create -c my.ini --index-only=vcs,kallithea
 
 To keep your index up-to-date it is necessary to do periodic index builds;
 for this, it is recommended to use a crontab entry. Example::
 
-    0  3  *  *  *  /path/to/virtualenv/bin/paster make-index /path/to/kallithea/my.ini
+    0  3  *  *  *  /path/to/virtualenv/bin/kallithea-cli index-create -c /path/to/kallithea/my.ini
 
 When using incremental mode (the default), Whoosh will check the last
 modification date of each file and add it to be reindexed if a newer file is
@@ -155,321 +159,75 @@
 If you want to rebuild the index from scratch, you can use the ``-f`` flag as above,
 or in the admin panel you can check the "build from scratch" checkbox.
 
-.. _ldap-setup:
-
-Setting up LDAP support
------------------------
-
-Kallithea supports LDAP authentication. In order
-to use LDAP, you have to install the python-ldap_ package. This package is
-available via PyPI, so you can install it by running::
-
-    pip install python-ldap
-
-.. note:: ``python-ldap`` requires some libraries to be installed on
-          your system, so before installing it check that you have at
-          least the ``openldap`` and ``sasl`` libraries.
-
-Choose *Admin > Authentication*, click the ``kallithea.lib.auth_modules.auth_ldap`` button
-and then *Save*, to enable the LDAP plugin and configure its settings.
-
-Here's a typical LDAP setup::
-
- Connection settings
- Enable LDAP          = checked
- Host                 = host.example.com
- Port                 = 389
- Account              = <account>
- Password             = <password>
- Connection Security  = LDAPS connection
- Certificate Checks   = DEMAND
-
- Search settings
- Base DN              = CN=users,DC=host,DC=example,DC=org
- LDAP Filter          = (&(objectClass=user)(!(objectClass=computer)))
- LDAP Search Scope    = SUBTREE
-
- Attribute mappings
- Login Attribute      = uid
- First Name Attribute = firstName
- Last Name Attribute  = lastName
- Email Attribute      = mail
-
-If your user groups are placed in an Organisation Unit (OU) structure, the Search Settings configuration differs::
-
- Search settings
- Base DN              = DC=host,DC=example,DC=org
- LDAP Filter          = (&(memberOf=CN=your user group,OU=subunit,OU=unit,DC=host,DC=example,DC=org)(objectClass=user))
- LDAP Search Scope    = SUBTREE
-
-.. _enable_ldap:
-
-Enable LDAP : required
-    Whether to use LDAP for authenticating users.
-
-.. _ldap_host:
-
-Host : required
-    LDAP server hostname or IP address. Can be also a comma separated
-    list of servers to support LDAP fail-over.
-
-.. _Port:
-
-Port : required
-    389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
-
-.. _ldap_account:
-
-Account : optional
-    Only required if the LDAP server does not allow anonymous browsing of
-    records.  This should be a special account for record browsing.  This
-    will require `LDAP Password`_ below.
-
-.. _LDAP Password:
-
-Password : optional
-    Only required if the LDAP server does not allow anonymous browsing of
-    records.
-
-.. _Enable LDAPS:
-
-Connection Security : required
-    Defines the connection to LDAP server
-
-    No encryption
-        Plain non encrypted connection
-
-    LDAPS connection
-        Enable LDAPS connections. It will likely require `Port`_ to be set to
-        a different value (standard LDAPS port is 636). When LDAPS is enabled
-        then `Certificate Checks`_ is required.
-
-    START_TLS on LDAP connection
-        START TLS connection
-
-.. _Certificate Checks:
-
-Certificate Checks : optional
-    How SSL certificates verification is handled -- this is only useful when
-    `Enable LDAPS`_ is enabled.  Only DEMAND or HARD offer full SSL security
-    while the other options are susceptible to man-in-the-middle attacks.  SSL
-    certificates can be installed to /etc/openldap/cacerts so that the
-    DEMAND or HARD options can be used with self-signed certificates or
-    certificates that do not have traceable certificates of authority.
-
-    NEVER
-        A serve certificate will never be requested or checked.
-
-    ALLOW
-        A server certificate is requested.  Failure to provide a
-        certificate or providing a bad certificate will not terminate the
-        session.
-
-    TRY
-        A server certificate is requested.  Failure to provide a
-        certificate does not halt the session; providing a bad certificate
-        halts the session.
-
-    DEMAND
-        A server certificate is requested and must be provided and
-        authenticated for the session to proceed.
-
-    HARD
-        The same as DEMAND.
-
-.. _Base DN:
-
-Base DN : required
-    The Distinguished Name (DN) where searches for users will be performed.
-    Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
-
-.. _LDAP Filter:
-
-LDAP Filter : optional
-    A LDAP filter defined by RFC 2254.  This is more useful when `LDAP
-    Search Scope`_ is set to SUBTREE.  The filter is useful for limiting
-    which LDAP objects are identified as representing Users for
-    authentication.  The filter is augmented by `Login Attribute`_ below.
-    This can commonly be left blank.
-
-.. _LDAP Search Scope:
-
-LDAP Search Scope : required
-    This limits how far LDAP will search for a matching object.
-
-    BASE
-        Only allows searching of `Base DN`_ and is usually not what you
-        want.
-
-    ONELEVEL
-        Searches all entries under `Base DN`_, but not Base DN itself.
-
-    SUBTREE
-        Searches all entries below `Base DN`_, but not Base DN itself.
-        When using SUBTREE `LDAP Filter`_ is useful to limit object
-        location.
-
-.. _Login Attribute:
-
-Login Attribute : required
-    The LDAP record attribute that will be matched as the USERNAME or
-    ACCOUNT used to connect to Kallithea.  This will be added to `LDAP
-    Filter`_ for locating the User object.  If `LDAP Filter`_ is specified as
-    "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
-    connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
-    ::
-
-        (&(LDAPFILTER)(uid=jsmith))
-
-.. _ldap_attr_firstname:
-
-First Name Attribute : required
-    The LDAP record attribute which represents the user's first name.
-
-.. _ldap_attr_lastname:
-
-Last Name Attribute : required
-    The LDAP record attribute which represents the user's last name.
-
-.. _ldap_attr_email:
-
-Email Attribute : required
-    The LDAP record attribute which represents the user's email address.
-
-If all data are entered correctly, and python-ldap_ is properly installed
-users should be granted access to Kallithea with LDAP accounts.  At this
-time user information is copied from LDAP into the Kallithea user database.
-This means that updates of an LDAP user object may not be reflected as a
-user update in Kallithea.
-
-If You have problems with LDAP access and believe You entered correct
-information check out the Kallithea logs, any error messages sent from LDAP
-will be saved there.
-
-Active Directory
-''''''''''''''''
-
-Kallithea can use Microsoft Active Directory for user authentication.  This
-is done through an LDAP or LDAPS connection to Active Directory.  The
-following LDAP configuration settings are typical for using Active
-Directory ::
-
- Base DN              = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
- Login Attribute      = sAMAccountName
- First Name Attribute = givenName
- Last Name Attribute  = sn
- Email Attribute     = mail
-
-All other LDAP settings will likely be site-specific and should be
-appropriately configured.
-
-
-Authentication by container or reverse-proxy
---------------------------------------------
-
-Kallithea supports delegating the authentication
-of users to its WSGI container, or to a reverse-proxy server through which all
-clients access the application.
-
-When these authentication methods are enabled in Kallithea, it uses the
-username that the container/proxy (Apache or Nginx, etc.) provides and doesn't
-perform the authentication itself. The authorization, however, is still done by
-Kallithea according to its settings.
-
-When a user logs in for the first time using these authentication methods,
-a matching user account is created in Kallithea with default permissions. An
-administrator can then modify it using Kallithea's admin interface.
-
-It's also possible for an administrator to create accounts and configure their
-permissions before the user logs in for the first time, using the :ref:`create-user` API.
-
-Container-based authentication
-''''''''''''''''''''''''''''''
-
-In a container-based authentication setup, Kallithea reads the user name from
-the ``REMOTE_USER`` server variable provided by the WSGI container.
-
-After setting up your container (see `Apache with mod_wsgi`_), you'll need
-to configure it to require authentication on the location configured for
-Kallithea.
-
-Proxy pass-through authentication
-'''''''''''''''''''''''''''''''''
-
-In a proxy pass-through authentication setup, Kallithea reads the user name
-from the ``X-Forwarded-User`` request header, which should be configured to be
-sent by the reverse-proxy server.
-
-After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
-`Apache as subdirectory`_ or `Nginx virtual host example`_), you'll need to
-configure the authentication and add the username in a request header named
-``X-Forwarded-User``.
-
-For example, the following config section for Apache sets a subdirectory in a
-reverse-proxy setup with basic auth:
-
-.. code-block:: apache
-
-    <Location /someprefix>
-      ProxyPass http://127.0.0.1:5000/someprefix
-      ProxyPassReverse http://127.0.0.1:5000/someprefix
-      SetEnvIf X-Url-Scheme https HTTPS=1
-
-      AuthType Basic
-      AuthName "Kallithea authentication"
-      AuthUserFile /srv/kallithea/.htpasswd
-      Require valid-user
-
-      RequestHeader unset X-Forwarded-User
-
-      RewriteEngine On
-      RewriteCond %{LA-U:REMOTE_USER} (.+)
-      RewriteRule .* - [E=RU:%1]
-      RequestHeader set X-Forwarded-User %{RU}e
-    </Location>
-
-.. note::
-   If you enable proxy pass-through authentication, make sure your server is
-   only accessible through the proxy. Otherwise, any client would be able to
-   forge the authentication header and could effectively become authenticated
-   using any account of their liking.
-
 
 Integration with issue trackers
 -------------------------------
 
 Kallithea provides a simple integration with issue trackers. It's possible
 to define a regular expression that will match an issue ID in commit messages,
-and have that replaced with a URL to the issue. To enable this simply
-uncomment the following variables in the ini file::
+and have that replaced with a URL to the issue.
+
+This is achieved with following three variables in the ini file::
 
-    issue_pat = (?:^#|\s#)(\w+)
-    issue_server_link = https://issues.example.com/{repo}/issue/{id}
-    issue_prefix = #
+    issue_pat = #(\d+)
+    issue_server_link = https://issues.example.com/{repo}/issue/\1
+    issue_sub =
 
 ``issue_pat`` is the regular expression describing which strings in
-commit messages will be treated as issue references. A match group in
-parentheses should be used to specify the actual issue id.
+commit messages will be treated as issue references. The expression can/should
+have one or more parenthesized groups that can later be referred to in
+``issue_server_link`` and ``issue_sub`` (see below). If you prefer, named groups
+can be used instead of simple parenthesized groups.
 
-The default expression matches issues in the format ``#<number>``, e.g., ``#300``.
+If the pattern should only match if it is preceded by whitespace, add the
+following string before the actual pattern: ``(?:^|(?<=\s))``.
+If the pattern should only match if it is followed by whitespace, add the
+following string after the actual pattern: ``(?:$|(?=\s))``.
+These expressions use lookbehind and lookahead assertions of the Python regular
+expression module to avoid the whitespace to be part of the actual pattern,
+otherwise the link text will also contain that whitespace.
 
 Matched issue references are replaced with the link specified in
-``issue_server_link``. ``{id}`` is replaced with the issue ID, and
-``{repo}`` with the repository name.  Since the # is stripped away,
-``issue_prefix`` is prepended to the link text.  ``issue_prefix`` doesn't
-necessarily need to be ``#``: if you set issue prefix to ``ISSUE-`` this will
-generate a URL in the format:
+``issue_server_link``, in which any backreferences are resolved. Backreferences
+can be ``\1``, ``\2``, ... or for named groups ``\g<groupname>``.
+The special token ``{repo}`` is replaced with the full repository path
+(including repository groups), while token ``{repo_name}`` is replaced with the
+repository name (without repository groups).
+
+The link text is determined by ``issue_sub``, which can be a string containing
+backreferences to the groups specified in ``issue_pat``. If ``issue_sub`` is
+empty, then the text matched by ``issue_pat`` is used verbatim.
+
+The example settings shown above match issues in the format ``#<number>``.
+This will cause the text ``#300`` to be transformed into a link:
 
 .. code-block:: html
 
-  <a href="https://issues.example.com/example_repo/issue/300">ISSUE-300</a>
+  <a href="https://issues.example.com/example_repo/issue/300">#300</a>
+
+The following example transforms a text starting with either of 'pullrequest',
+'pull request' or 'PR', followed by an optional space, then a pound character
+(#) and one or more digits, into a link with the text 'PR #' followed by the
+digits::
+
+    issue_pat = (pullrequest|pull request|PR) ?#(\d+)
+    issue_server_link = https://issues.example.com/\2
+    issue_sub = PR #\2
+
+The following example demonstrates how to require whitespace before the issue
+reference in order for it to be recognized, such that the text ``issue#123`` will
+not cause a match, but ``issue #123`` will::
+
+    issue_pat = (?:^|(?<=\s))#(\d+)
+    issue_server_link = https://issues.example.com/\1
+    issue_sub =
 
 If needed, more than one pattern can be specified by appending a unique suffix to
-the variables. For example::
+the variables. For example, also demonstrating the use of named groups::
 
-    issue_pat_wiki = (?:wiki-)(.+)
-    issue_server_link_wiki = https://wiki.example.com/{id}
-    issue_prefix_wiki = WIKI-
+    issue_pat_wiki = wiki-(?P<pagename>\S+)
+    issue_server_link_wiki = https://wiki.example.com/\g<pagename>
+    issue_sub_wiki = WIKI-\g<pagename>
 
 With these settings, wiki pages can be referenced as wiki-some-id, and every
 such reference will be transformed into:
@@ -478,6 +236,9 @@
 
   <a href="https://wiki.example.com/some-id">WIKI-some-id</a>
 
+Refer to the `Python regular expression documentation`_ for more details about
+the supported syntax in ``issue_pat``, ``issue_server_link`` and ``issue_sub``.
+
 
 Hook management
 ---------------
@@ -502,6 +263,9 @@
 library is installed. If ``chardet`` is detected Kallithea will fallback to it
 when there are encode/decode errors.
 
+The Mercurial encoding is configurable as ``hgencoding``. It is similar to
+setting the ``HGENCODING`` environment variable, but will override it.
+
 
 Celery configuration
 --------------------
@@ -531,7 +295,10 @@
 
 To start the Celery process, run::
 
- paster celeryd <configfile.ini>
+  kallithea-cli celery-run -c my.ini
+
+Extra options to the Celery worker can be passed after ``--`` - see ``-- -h``
+for more info.
 
 .. note::
    Make sure you run this command from the same virtualenv, and with the same
@@ -552,6 +319,8 @@
 - With ``force_https = true`` the default will be ``https``.
 - With ``use_htsts = true``, Kallithea will set ``Strict-Transport-Security`` when using https.
 
+.. _nginx_virtual_host:
+
 
 Nginx virtual host example
 --------------------------
@@ -606,7 +375,7 @@
 
        ## uncomment root directive if you want to serve static files by nginx
        ## requires static_files = false in .ini file
-       #root /path/to/installation/kallithea/public;
+       #root /srv/kallithea/kallithea/kallithea/public;
        include         /etc/nginx/proxy.conf;
        location / {
             try_files $uri @kallithea;
@@ -640,6 +409,8 @@
     client_body_buffer_size     128k;
     large_client_header_buffers 8 64k;
 
+.. _apache_virtual_host_reverse_proxy:
+
 
 Apache virtual host reverse proxy example
 -----------------------------------------
@@ -661,7 +432,7 @@
             </Proxy>
 
             #important !
-            #Directive to properly generate url (clone url) for pylons
+            #Directive to properly generate url (clone url) for Kallithea
             ProxyPreserveHost On
 
             #kallithea instance
@@ -675,6 +446,8 @@
 Additional tutorial
 http://pylonsbook.com/en/1.1/deployment.html#using-apache-to-proxy-requests-to-pylons
 
+.. _apache_subdirectory:
+
 
 Apache as subdirectory
 ----------------------
@@ -683,9 +456,9 @@
 
 .. code-block:: apache
 
-    <Location /<someprefix> >
-      ProxyPass http://127.0.0.1:5000/<someprefix>
-      ProxyPassReverse http://127.0.0.1:5000/<someprefix>
+    <Location /PREFIX >
+      ProxyPass http://127.0.0.1:5000/PREFIX
+      ProxyPassReverse http://127.0.0.1:5000/PREFIX
       SetEnvIf X-Url-Scheme https HTTPS=1
     </Location>
 
@@ -698,9 +471,11 @@
 
     [filter:proxy-prefix]
     use = egg:PasteDeploy#prefix
-    prefix = /<someprefix>
+    prefix = /PREFIX
 
-then change ``<someprefix>`` into your chosen prefix
+then change ``PREFIX`` into your chosen prefix
+
+.. _apache_mod_wsgi:
 
 
 Apache with mod_wsgi
@@ -755,27 +530,21 @@
 directory owned by a different user, use the user and group options to
 WSGIDaemonProcess to set the name of the user and group.
 
-.. note::
-   If running Kallithea in multiprocess mode,
-   make sure you set ``instance_id = *`` in the configuration so each process
-   gets it's own cache invalidation key.
-
 Example WSGI dispatch script:
 
 .. code-block:: python
 
     import os
-    os.environ["HGENCODING"] = "UTF-8"
     os.environ['PYTHON_EGG_CACHE'] = '/srv/kallithea/.egg-cache'
 
-    # sometimes it's needed to set the curent dir
+    # sometimes it's needed to set the current dir
     os.chdir('/srv/kallithea/')
 
     import site
     site.addsitedir("/srv/kallithea/venv/lib/python2.7/site-packages")
 
     ini = '/srv/kallithea/my.ini'
-    from paste.script.util.logging_config import fileConfig
+    from logging.config import fileConfig
     fileConfig(ini)
     from paste.deploy import loadapp
     application = loadapp('config:' + ini)
@@ -791,7 +560,7 @@
     os.environ['HOME'] = '/srv/kallithea'
 
     ini = '/srv/kallithea/kallithea.ini'
-    from paste.script.util.logging_config import fileConfig
+    from logging.config import fileConfig
     fileConfig(ini)
     from paste.deploy import loadapp
     application = loadapp('config:' + ini)
@@ -808,11 +577,11 @@
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
 .. _python: http://www.python.org/
+.. _Python regular expression documentation: https://docs.python.org/2/library/re.html
 .. _Mercurial: https://www.mercurial-scm.org/
 .. _Celery: http://celeryproject.org/
 .. _Celery documentation: http://docs.celeryproject.org/en/latest/getting-started/index.html
 .. _RabbitMQ: http://www.rabbitmq.com/
 .. _Redis: http://redis.io/
-.. _python-ldap: http://www.python-ldap.org/
 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
 .. _PublishingRepositories: https://www.mercurial-scm.org/wiki/PublishingRepositories
--- a/docs/theme/nature/static/nature.css_t	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/theme/nature/static/nature.css_t	Sun Mar 31 21:28:56 2019 +0200
@@ -2,11 +2,11 @@
  * Sphinx stylesheet -- default theme
  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  */
- 
+
 @import url("basic.css");
- 
+
 /* -- page layout ----------------------------------------------------------- */
- 
+
 body {
     font-family: Arial, sans-serif;
     font-size: 100%;
@@ -28,18 +28,18 @@
 hr{
     border: 1px solid #B1B4B6;
 }
- 
+
 div.document {
     background-color: #eee;
 }
- 
+
 div.body {
     background-color: #ffffff;
     color: #3E4349;
     padding: 0 30px 30px 30px;
     font-size: 0.8em;
 }
- 
+
 div.footer {
     color: #555;
     width: 100%;
@@ -47,12 +47,12 @@
     text-align: center;
     font-size: 75%;
 }
- 
+
 div.footer a {
     color: #444;
     text-decoration: underline;
 }
- 
+
 div.related {
     background-color: #577632;
     line-height: 32px;
@@ -60,11 +60,11 @@
     text-shadow: 0px 1px 0 #444;
     font-size: 0.80em;
 }
- 
+
 div.related a {
     color: #E2F3CC;
 }
- 
+
 div.sphinxsidebar {
     font-size: 0.75em;
     line-height: 1.5em;
@@ -73,7 +73,7 @@
 div.sphinxsidebarwrapper{
     padding: 20px 0;
 }
- 
+
 div.sphinxsidebar h3,
 div.sphinxsidebar h4 {
     font-family: Arial, sans-serif;
@@ -89,30 +89,29 @@
 div.sphinxsidebar h4{
     font-size: 1.1em;
 }
- 
+
 div.sphinxsidebar h3 a {
     color: #444;
 }
- 
- 
+
 div.sphinxsidebar p {
     color: #888;
     padding: 5px 20px;
 }
- 
+
 div.sphinxsidebar p.topless {
 }
- 
+
 div.sphinxsidebar ul {
     margin: 10px 20px;
     padding: 0;
     color: #000;
 }
- 
+
 div.sphinxsidebar a {
     color: #444;
 }
- 
+
 div.sphinxsidebar input {
     border: 1px solid #ccc;
     font-family: sans-serif;
@@ -126,19 +125,19 @@
 div.sphinxsidebar input[type=image] {
     border: 0;
 }
- 
+
 /* -- body styles ----------------------------------------------------------- */
- 
+
 a {
     color: #005B81;
     text-decoration: none;
 }
- 
+
 a:hover {
     color: #E32E00;
     text-decoration: underline;
 }
- 
+
 div.body h1,
 div.body h2,
 div.body h3,
@@ -153,30 +152,30 @@
     padding: 5px 0 5px 10px;
     text-shadow: 0px 1px 0 white
 }
- 
+
 div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
 div.body h2 { font-size: 150%; background-color: #C8D5E3; }
 div.body h3 { font-size: 120%; background-color: #D8DEE3; }
 div.body h4 { font-size: 110%; background-color: #D8DEE3; }
 div.body h5 { font-size: 100%; background-color: #D8DEE3; }
 div.body h6 { font-size: 100%; background-color: #D8DEE3; }
- 
+
 a.headerlink {
     color: #c60f0f;
     font-size: 0.8em;
     padding: 0 4px 0 4px;
     text-decoration: none;
 }
- 
+
 a.headerlink:hover {
     background-color: #c60f0f;
     color: white;
 }
- 
+
 div.body p, div.body dd, div.body li {
     line-height: 1.5em;
 }
- 
+
 div.admonition p.admonition-title + p {
     display: inline;
 }
@@ -189,29 +188,29 @@
     background-color: #eee;
     border: 1px solid #ccc;
 }
- 
+
 div.seealso {
     background-color: #ffc;
     border: 1px solid #ff6;
 }
- 
+
 div.topic {
     background-color: #eee;
 }
- 
+
 div.warning {
     background-color: #ffe4e4;
     border: 1px solid #f66;
 }
- 
+
 p.admonition-title {
     display: inline;
 }
- 
+
 p.admonition-title:after {
     content: ":";
 }
- 
+
 pre {
     padding: 10px;
     background-color: White;
@@ -222,7 +221,7 @@
     margin: 1.5em 0 1.5em 0;
     box-shadow: 1px 1px 1px #d8d8d8;
 }
- 
+
 tt {
     background-color: #ecf0f3;
     color: #222;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/upgrade.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,246 @@
+.. _upgrade:
+
+===================
+Upgrading Kallithea
+===================
+
+This describes the process for upgrading Kallithea, independently of the
+Kallithea installation method.
+
+.. note::
+    If you are upgrading from a RhodeCode installation, you must first
+    install Kallithea 0.3.2 and follow the instructions in the 0.3.2
+    README to perform a one-time conversion of the database from
+    RhodeCode to Kallithea, before upgrading to the latest version
+    of Kallithea.
+
+
+1. Stop the Kallithea web application
+-------------------------------------
+
+This step depends entirely on the web server software used to serve
+Kallithea, but in any case, Kallithea should not be running during
+the upgrade.
+
+.. note::
+    If you're using Celery, make sure you stop all instances during the
+    upgrade.
+
+
+2. Create a backup of both database and configuration
+-----------------------------------------------------
+
+You are of course strongly recommended to make backups regularly, but it
+is *especially* important to make a full database and configuration
+backup before performing a Kallithea upgrade.
+
+Back up your configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Make a copy of your Kallithea configuration (``.ini``) file.
+
+If you are using :ref:`rcextensions <customization>`, you should also
+make a copy of the entire ``rcextensions`` directory.
+
+Back up your database
+^^^^^^^^^^^^^^^^^^^^^
+
+If using SQLite, simply make a copy of the Kallithea database (``.db``)
+file.
+
+If using PostgreSQL, please consult the documentation for the ``pg_dump``
+utility.
+
+If using MySQL, please consult the documentation for the ``mysqldump``
+utility.
+
+Look for ``sqlalchemy.url`` in your configuration file to determine
+database type, settings, location, etc. If you were running Kallithea 0.3.x or
+older, this was ``sqlalchemy.db1.url``.
+
+
+3. Activate or recreate the Kallithea virtual environment (if any)
+------------------------------------------------------------------
+
+.. note::
+    If you did not install Kallithea in a virtual environment, skip this step.
+
+For major upgrades, e.g. from 0.3.x to 0.4.x, it is recommended to create a new
+virtual environment, rather than reusing the old. For minor upgrades, e.g.
+within the 0.4.x range, this is not really necessary (but equally fine).
+
+To create a new virtual environment, please refer to the appropriate
+installation page for details. After creating and activating the new virtual
+environment, proceed with the rest of the upgrade process starting from the next
+section.
+
+To reuse the same virtual environment, first activate it, then verify that you
+are using the correct environment by running::
+
+    pip freeze
+
+This will list all packages installed in the current environment. If
+Kallithea isn't listed, deactivate the environment and then activate the correct
+one, or recreate a new environment. See the appropriate installation page for
+details.
+
+
+4. Install new version of Kallithea
+-----------------------------------
+
+Please refer to the instructions for the installation method you
+originally used to install Kallithea.
+
+If you originally installed using pip, it is as simple as::
+
+    pip install --upgrade kallithea
+
+If you originally installed from version control, assuming you did not make
+private changes (in which case you should adapt the instructions accordingly)::
+
+    cd my-kallithea-clone
+    hg parent   # make a note of the original revision
+    hg pull
+    hg update
+    hg parent   # make a note of the new revision
+    pip install --upgrade -e .
+
+.. _upgrade_config:
+
+
+5. Upgrade your configuration
+-----------------------------
+
+Run the following command to create a new configuration (``.ini``) file::
+
+    kallithea-cli config-create new.ini
+
+Then compare it with your old config file and copy over the required
+configuration values from the old to the new file.
+
+.. note::
+    Please always make sure your ``.ini`` files are up to date. Errors
+    can often be caused by missing parameters added in new versions.
+
+.. _upgrade_db:
+
+
+6. Upgrade your database
+------------------------
+
+.. note::
+    If you are *downgrading* Kallithea, you should perform the database
+    migration step *before* installing the older version. (That is,
+    always perform migrations using the most recent of the two versions
+    you're migrating between.)
+
+First, run the following command to see your current database version::
+
+    alembic -c new.ini current
+
+Typical output will be something like "9358dc3d6828 (head)", which is
+the current Alembic database "revision ID". Write down the entire output
+for troubleshooting purposes.
+
+The output will be empty if you're upgrading from Kallithea 0.3.x or
+older. That's expected. If you get an error that the config file was not
+found or has no ``[alembic]`` section, see the next section.
+
+Next, if you are performing an *upgrade*: Run the following command to
+upgrade your database to the current Kallithea version::
+
+    alembic -c new.ini upgrade head
+
+If you are performing a *downgrade*: Run the following command to
+downgrade your database to the given version::
+
+    alembic -c new.ini downgrade 0.4
+
+Alembic will show the necessary migrations (if any) as it executes them.
+If no "ERROR" is displayed, the command was successful.
+
+Should an error occur, the database may be "stranded" half-way
+through the migration, and you should restore it from backup.
+
+Enabling old Kallithea config files for Alembic use
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Kallithea configuration files created before the introduction of Alembic
+(i.e. predating Kallithea 0.4) need to be updated for use with Alembic.
+Without this, Alembic will fail with an error like this::
+
+    FAILED: No config file 'my.ini' found, or file has no '[alembic]' section
+
+.. note::
+    If you followed this upgrade guide correctly, you will have created a
+    new configuration file in section :ref:`Upgrading your configuration
+    <upgrade_config>`. When calling Alembic, make
+    sure to use this new config file. In this case, you should not get any
+    errors and the below manual steps should not be needed.
+
+If Alembic complains specifically about a missing ``alembic.ini``, it is
+likely because you did not specify a config file using the ``-c`` option.
+On the other hand, if the mentioned config file actually exists, you
+need to append the following lines to it::
+
+    [alembic]
+    script_location = kallithea:alembic
+
+Your config file should now work with Alembic.
+
+
+7. Prepare the front-end
+------------------------
+
+Starting with Kallithea 0.4, external front-end dependencies are no longer
+shipped but need to be downloaded and/or generated at installation time. Run the
+following command::
+
+    kallithea-cli front-end-build
+
+
+8. Rebuild the Whoosh full-text index
+-------------------------------------
+
+It is recommended that you rebuild the Whoosh index after upgrading since
+new Whoosh versions can introduce incompatible index changes.
+
+
+9. Start the Kallithea web application
+--------------------------------------
+
+This step once again depends entirely on the web server software used to
+serve Kallithea.
+
+If you were running Kallithea 0.3.x or older and were using ``paster serve
+my.ini`` before, then the corresponding command in Kallithea 0.4 and later is::
+
+    gearbox serve -c new.ini
+
+Before starting the new version of Kallithea, you may find it helpful to
+clear out your log file so that new errors are readily apparent.
+
+.. note::
+    If you're using Celery, make sure you restart all instances of it after
+    upgrade.
+
+
+10. Update Git repository hooks
+-------------------------------
+
+It is possible that an upgrade involves changes to the Git hooks installed by
+Kallithea. As these hooks are created inside the repositories on the server
+filesystem, they are not updated automatically when upgrading Kallithea itself.
+
+To update the hooks of your Git repositories:
+
+* Go to *Admin > Settings > Remap and Rescan*
+* Select the checkbox *Install Git hooks*
+* Click the button *Rescan repositories*
+
+.. note::
+    Kallithea does not use hooks on Mercurial repositories. This step is thus
+    not necessary if you only have Mercurial repositories.
+
+
+.. _virtualenv: http://pypi.python.org/pypi/virtualenv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/usage/customization.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,70 @@
+.. _customization:
+
+=============
+Customization
+=============
+
+There are several ways to customize Kallithea to your needs depending on what
+you want to achieve.
+
+
+HTML/JavaScript/CSS customization
+---------------------------------
+
+To customize the look-and-feel of the web interface (for example to add a
+company banner or some JavaScript widget or to tweak the CSS style definitions)
+you can enter HTML code (possibly with JavaScript and/or CSS) directly via the
+*Admin > Settings > Global > HTML/JavaScript customization
+block*.
+
+
+Style sheet customization with Less
+-----------------------------------
+
+Kallithea uses `Bootstrap 3`_ and Less_ for its style definitions. If you want
+to make some customizations, we recommend to do so by creating a ``theme.less``
+file. When you create a file named ``theme.less`` in the Kallithea root
+directory, you can use this file to override the default style. For example,
+you can use this to override ``@kallithea-theme-main-color``,
+``@kallithea-logo-url`` or other `Bootstrap variables`_.
+
+After creating the ``theme.less`` file, you need to regenerate the CSS files, by
+running::
+
+    kallithea-cli front-end-build --no-install-deps
+
+.. _bootstrap 3: https://getbootstrap.com/docs/3.3/
+.. _bootstrap variables: https://getbootstrap.com/docs/3.3/customize/#less-variables
+.. _less: http://lesscss.org/
+
+
+Behavioral customization: rcextensions
+--------------------------------------
+
+Some behavioral customization can be done in Python using ``rcextensions``, a
+custom Python package that can extend Kallithea functionality.
+
+With ``rcextensions`` it's possible to add additional mappings for Whoosh
+indexing and statistics, to add additional code into the push/pull/create/delete
+repository hooks (for example to send signals to build bots such as Jenkins) and
+even to monkey-patch certain parts of the Kallithea source code (for example
+overwrite an entire function, change a global variable, ...).
+
+To generate a skeleton extensions package, run::
+
+    kallithea-cli extensions-create -c my.ini
+
+This will create an ``rcextensions`` package next to the specified ``ini`` file.
+See the ``__init__.py`` file inside the generated ``rcextensions`` package
+for more details.
+
+
+Behavioral customization: code changes
+--------------------------------------
+
+As Kallithea is open-source software, you can make any changes you like directly
+in the source code.
+
+We encourage you to send generic improvements back to the
+community so that Kallithea can become better. See :ref:`contributing` for more
+details.
--- a/docs/usage/debugging.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/usage/debugging.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -25,7 +25,7 @@
 
 To enable interactive debug mode simply comment out ``set debug = false`` in
 the .ini file. This will trigger an interactive debugger each time
-there is an error in the browser, or send a http link if an error occured in the backend. This
+there is an error in the browser, or send a http link if an error occurred in the backend. This
 is a great tool for fast debugging as you get a handy Python console right
 in the web view.
 
--- a/docs/usage/email.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/usage/email.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -12,8 +12,17 @@
 Before any email can be sent, an SMTP server has to be configured using the
 configuration file setting ``smtp_server``. If required for that server, specify
 a username (``smtp_username``) and password (``smtp_password``), a non-standard
-port (``smtp_port``), encryption settings (``smtp_use_tls`` or ``smtp_use_ssl``)
-and/or specific authentication parameters (``smtp_auth``).
+port (``smtp_port``), whether to use "SSL" when connecting (``smtp_use_ssl``)
+or use STARTTLS (``smtp_use_tls``), and/or specify special ESMTP "auth" features
+(``smtp_auth``).
+
+For example, for sending through gmail, use::
+
+    smtp_server = smtp.gmail.com
+    smtp_username = username
+    smtp_password = password
+    smtp_port = 465
+    smtp_use_ssl = true
 
 
 Application emails
@@ -67,9 +76,8 @@
 
 When an exception occurs in Kallithea -- and unless interactive debugging is
 enabled using ``set debug = true`` in the ``[app:main]`` section of the
-configuration file -- an email with exception details is sent by WebError_'s
-``ErrorMiddleware`` to the addresses specified in ``email_to`` in the
-configuration file.
+configuration file -- an email with exception details is sent by backlash_
+to the addresses specified in ``email_to`` in the configuration file.
 
 Recipients will see these emails originating from the sender specified in the
 ``error_email_from`` setting in the configuration file. This setting can either
@@ -77,10 +85,6 @@
 a name and an address in the following format: `Kallithea Errors
 <kallithea-noreply@example.com>`.
 
-*Note:* The WebError_ package does not respect ``smtp_port`` and assumes the
-standard SMTP port (25). If you have a remote SMTP server with a different port,
-you could set up a local forwarding SMTP server on port 25.
-
 
 References
 ----------
@@ -89,4 +93,4 @@
 - `ErrorHandler (Pylons modules documentation) <http://pylons-webframework.readthedocs.org/en/latest/modules/middleware.html#pylons.middleware.ErrorHandler>`_
 
 
-.. _WebError: https://pypi.python.org/pypi/WebError
+.. _backlash: https://github.com/TurboGears/backlash
--- a/docs/usage/general.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/usage/general.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -8,18 +8,18 @@
 Repository deletion
 -------------------
 
-Currently when an admin or owner deletes a repository, Kallithea does
+When an admin or owner deletes a repository, Kallithea does
 not physically delete said repository from the filesystem, but instead
 renames it in a special way so that it is not possible to push, clone
 or access the repository.
 
 There is a special command for cleaning up such archived repositories::
 
-    paster cleanup-repos --older-than=30d my.ini
+    kallithea-cli repo-purge-deleted -c my.ini --older-than=30d
 
 This command scans for archived repositories that are older than
 30 days, displays them, and asks if you want to delete them (unless given
-the ``--dont-ask`` flag). If you host a large amount of repositories with
+the ``--no-ask`` flag). If you host a large amount of repositories with
 forks that are constantly being deleted, it is recommended that you run this
 command via crontab.
 
@@ -151,7 +151,7 @@
 features that merit further explanation.
 
 Repository extra fields
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^
 
 In the *Visual* tab, there is an option "Use repository extra
 fields", which allows to set custom fields for each repository in the system.
@@ -165,7 +165,7 @@
 Newly created fields are accessible via the API.
 
 Meta tagging
-~~~~~~~~~~~~
+^^^^^^^^^^^^
 
 In the *Visual* tab, option "Stylify recognised meta tags" will cause Kallithea
 to turn certain text fragments in repository and repository group
--- a/docs/usage/performance.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/usage/performance.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -4,59 +4,65 @@
 Optimizing Kallithea performance
 ================================
 
-When serving a large amount of big repositories, Kallithea can start
-performing slower than expected. Because of the demanding nature of handling large
-amounts of data from version control systems, here are some tips on how to get
-the best performance.
+When serving a large amount of big repositories, Kallithea can start performing
+slower than expected. Because of the demanding nature of handling large amounts
+of data from version control systems, here are some tips on how to get the best
+performance.
+
 
-* Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) is
-  usually more important than a fast CPU.
+Fast storage
+------------
 
-* Sluggish loading of the front page can easily be fixed by grouping repositories or by
-  increasing cache size (see below). This includes using the lightweight dashboard
-  option and ``vcs_full_cache`` setting in .ini file.
+Kallithea is often I/O bound, and hence a fast disk (SSD/SAN) and plenty of RAM
+is usually more important than a fast CPU.
+
 
-Follow these few steps to improve performance of Kallithea system.
+Caching
+-------
 
-1. Increase cache
+Tweak beaker cache settings in the ini file. The actual effect of that is
+questionable.
 
-    Tweak beaker cache settings in the ini file. The actual effect of that
-    is questionable.
 
-2. Switch from SQLite to PostgreSQL or MySQL
+Database
+--------
 
-    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 PostgreSQL will result in an immediate
-    performance increase. A tool like SQLAlchemyGrate_ can be used for
-    migrating to another database platform.
+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.
 
-3. Scale Kallithea horizontally
+Switching to MySQL or PostgreSQL will result in an immediate performance
+increase. A tool like SQLAlchemyGrate_ can be used for migrating to another
+database platform.
+
 
-    Scaling horizontally can give huge performance benefits when dealing with
-    large amounts of traffic (many users, CI servers, etc.). Kallithea can be
-    scaled horizontally on one (recommended) or multiple machines.
+Horizontal scaling
+------------------
 
-    It is generally possible to run WSGI applications multithreaded, so that
-    several HTTP requests are served from the same Python process at once. That
-    can in principle give better utilization of internal caches and less
-    process overhead.
+Scaling horizontally means running several Kallithea instances and let them
+share the load. That can give huge performance benefits when dealing with large
+amounts of traffic (many users, CI servers, etc.). Kallithea can be scaled
+horizontally on one (recommended) or multiple machines.
 
-    One danger of running multithreaded is that program execution becomes much
-    more complex; programs must be written to consider all combinations of
-    events and problems might depend on timing and be impossible to reproduce.
+It is generally possible to run WSGI applications multithreaded, so that
+several HTTP requests are served from the same Python process at once. That can
+in principle give better utilization of internal caches and less process
+overhead.
 
-    Kallithea can't promise to be thread-safe, just like the embedded Mercurial
-    backend doesn't make any strong promises when used as Kallithea uses it.
-    Instead, we recommend scaling by using multiple server processes.
+One danger of running multithreaded is that program execution becomes much more
+complex; programs must be written to consider all combinations of events and
+problems might depend on timing and be impossible to reproduce.
 
-    Web servers with multiple worker processes (such as ``mod_wsgi`` with the
-    ``WSGIDaemonProcess`` ``processes`` parameter) will work out of the box.
+Kallithea can't promise to be thread-safe, just like the embedded Mercurial
+backend doesn't make any strong promises when used as Kallithea uses it.
+Instead, we recommend scaling by using multiple server processes.
 
-    In order to scale horizontally on multiple machines, you need to do the
-    following:
+Web servers with multiple worker processes (such as ``mod_wsgi`` with the
+``WSGIDaemonProcess`` ``processes`` parameter) will work out of the box.
 
-    - Each instance needs its own .ini file and unique ``instance_id`` set.
+In order to scale horizontally on multiple machines, you need to do the
+following:
+
     - Each instance's ``data`` storage needs to be configured to be stored on a
       shared disk storage, preferably together with repositories. This ``data``
       dir contains template caches, sessions, whoosh index and is used for
@@ -71,4 +77,42 @@
       servers or build bots.
 
 
+Serve static files directly from the web server
+-----------------------------------------------
+
+With the default ``static_files`` ini setting, the Kallithea WSGI application
+will take care of serving the static files from ``kallithea/public/`` at the
+root of the application URL.
+
+The actual serving of the static files is very fast and unlikely to be a
+problem in a Kallithea setup - the responses generated by Kallithea from
+database and repository content will take significantly more time and
+resources.
+
+To serve static files from the web server, use something like this Apache config
+snippet::
+
+        Alias /images/ /srv/kallithea/kallithea/kallithea/public/images/
+        Alias /css/ /srv/kallithea/kallithea/kallithea/public/css/
+        Alias /js/ /srv/kallithea/kallithea/kallithea/public/js/
+        Alias /codemirror/ /srv/kallithea/kallithea/kallithea/public/codemirror/
+        Alias /fontello/ /srv/kallithea/kallithea/kallithea/public/fontello/
+
+Then disable serving of static files in the ``.ini`` ``app:main`` section::
+
+        static_files = false
+
+If using Kallithea installed as a package, you should be able to find the files
+under ``site-packages/kallithea``, either in your Python installation or in your
+virtualenv. When upgrading, make sure to update the web server configuration
+too if necessary.
+
+It might also be possible to improve performance by configuring the web server
+to compress responses (served from static files or generated by Kallithea) when
+serving them. That might also imply buffering of responses - that is more
+likely to be a problem; large responses (clones or pulls) will have to be fully
+processed and spooled to disk or memory before the client will see any
+response. See the documentation for your web server.
+
+
 .. _SQLAlchemyGrate: https://github.com/shazow/sqlalchemygrate
--- a/docs/usage/troubleshooting.rst	Sun Mar 03 21:36:25 2019 +0100
+++ b/docs/usage/troubleshooting.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -63,7 +63,7 @@
 |
 
 :Q: **Requests hanging on Windows**
-:A: Please try out with disabled Antivirus software, there are some known problems with Eset Anitivirus. Make sure
+:A: Please try out with disabled Antivirus software, there are some known problems with Eset Antivirus. Make sure
     you have installed the latest Windows patches (especially KB2789397).
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/usage/vcs_notes.rst	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,108 @@
+.. _vcs_notes:
+
+===================================
+Version control systems usage notes
+===================================
+
+.. _importing:
+
+
+Importing existing repositories
+-------------------------------
+
+There are two main methods to import repositories in Kallithea: via the web
+interface or via the filesystem. If you have a large number of repositories to
+import, importing them via the filesystem is more convenient.
+
+Importing via web interface
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For a small number of repositories, it may be easier to create the target
+repositories through the Kallithea web interface, via *Admin > Repositories* or
+via the *Add Repository* button on the entry page of the web interface.
+
+Repositories can be nested in repository groups by first creating the group (via
+*Admin > Repository Groups* or via the *Add Repository Group* button on the
+entry page of the web interface) and then selecting the appropriate group when
+adding the repository.
+
+After creation of the (empty) repository, push the existing commits to the
+*Clone URL* displayed on the repository summary page. For Git repositories,
+first add the *Clone URL* as remote, then push the commits to that remote.  The
+specific commands to execute are shown under the *Existing repository?* section
+of the new repository's summary page.
+
+A benefit of this method particular for Git repositories, is that the
+Kallithea-specific Git hooks are installed automatically.  For Mercurial, no
+hooks are required anyway.
+
+Importing via the filesystem
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The alternative method of importing repositories consists of creating the
+repositories in the desired hierarchy on the filesystem and letting Kallithea
+scan that location.
+
+All repositories are stored in a central location on the filesystem. This
+location is specified during installation (via ``db-create``) and can be reviewed
+at *Admin > Settings > VCS > Location of repositories*. Repository groups
+(defined in *Admin > Repository Groups*) are represented by a directory in that
+repository location. Repositories of the repository group are nested under that
+directory.
+
+To import a set of repositories and organize them in a certain repository group
+structure, first place clones in the desired hierarchy at the configured
+repository location.
+These clones should be created without working directory. For Mercurial, this is
+done with ``hg clone -U``, for Git with ``git clone --bare``.
+
+When the repositories are added correctly on the filesystem:
+
+* go to *Admin > Settings > Remap and Rescan* in the Kallithea web interface
+* select the *Install Git hooks* checkbox when importing Git repositories
+* click *Rescan Repositories*
+
+This step will scan the filesystem and create the appropriate repository groups
+and repositories in Kallithea.
+
+*Note*: Once repository groups have been created this way, manage their access
+permissions through the Kallithea web interface.
+
+
+Mercurial-specific notes
+------------------------
+
+
+Working with subrepositories
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This section explains how to use Mercurial subrepositories_ in Kallithea.
+
+Example usage::
+
+    ## init a simple repo
+    hg init mainrepo
+    cd mainrepo
+    echo "file" > file
+    hg add file
+    hg ci --message "initial file"
+
+    # clone subrepo we want to add from Kallithea
+    hg clone http://kallithea.local/subrepo
+
+    ## specify URL to existing repo in Kallithea as subrepository path
+    echo "subrepo = http://kallithea.local/subrepo" > .hgsub
+    hg add .hgsub
+    hg ci --message "added remote subrepo"
+
+In the file list of a clone of ``mainrepo`` you will see a connected
+subrepository at the revision it was cloned with. Clicking on the
+subrepository link sends you to the proper repository in Kallithea.
+
+Cloning ``mainrepo`` will also clone the attached subrepository.
+
+Next we can edit the subrepository data, and push back to Kallithea. This will
+update both repositories.
+
+
+.. _subrepositories: http://mercurial.aragost.com/kick-start/en/subrepositories/
--- a/docs/usage/vcs_support.rst	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-.. _vcs_support:
-
-===============================
-Version control systems support
-===============================
-
-Kallithea supports Git and Mercurial repositories out-of-the-box.
-For Git, you do need the ``git`` command line client installed on the server.
-
-You can always disable Git or Mercurial support by editing the
-file ``kallithea/__init__.py`` and commenting out the backend.
-
-.. code-block:: python
-
-   BACKENDS = {
-       'hg': 'Mercurial repository',
-       #'git': 'Git repository',
-   }
-
-
-Git support
------------
-
-
-Web server with chunked encoding
-````````````````````````````````
-
-Large Git pushes require an HTTP server with support for
-chunked encoding for POST. The Python web servers waitress_ and
-gunicorn_ (Linux only) can be used. By default, Kallithea uses
-waitress_ for `paster serve` instead of the built-in `paste` WSGI
-server.
-
-The paster server is controlled in the .ini file::
-
-    use = egg:waitress#main
-
-or::
-
-    use = egg:gunicorn#main
-
-Also make sure to comment out the following options::
-
-    threadpool_workers =
-    threadpool_max_requests =
-    use_threadpool =
-
-
-Mercurial support
------------------
-
-
-Working with Mercurial subrepositories
-``````````````````````````````````````
-
-This section explains how to use Mercurial subrepositories_ in Kallithea.
-
-Example usage::
-
-    ## init a simple repo
-    hg init mainrepo
-    cd mainrepo
-    echo "file" > file
-    hg add file
-    hg ci --message "initial file"
-
-    # clone subrepo we want to add from Kallithea
-    hg clone http://kallithea.local/subrepo
-
-    ## specify URL to existing repo in Kallithea as subrepository path
-    echo "subrepo = http://kallithea.local/subrepo" > .hgsub
-    hg add .hgsub
-    hg ci --message "added remote subrepo"
-
-In the file list of a clone of ``mainrepo`` you will see a connected
-subrepository at the revision it was cloned with. Clicking on the
-subrepository link sends you to the proper repository in Kallithea.
-
-Cloning ``mainrepo`` will also clone the attached subrepository.
-
-Next we can edit the subrepository data, and push back to Kallithea. This will
-update both repositories.
-
-
-.. _waitress: http://pypi.python.org/pypi/waitress
-.. _gunicorn: http://pypi.python.org/pypi/gunicorn
-.. _subrepositories: http://mercurial.aragost.com/kick-start/en/subrepositories/
--- a/init.d/celeryd-upstart.conf	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/celeryd-upstart.conf	Sun Mar 31 21:28:56 2019 +0200
@@ -2,8 +2,8 @@
 # Change variables/paths as necessary and place file /etc/init/celeryd.conf
 # start/stop/restart as normal upstart job (ie: $ start celeryd)
 
-description	"Celery for Kallithea Mercurial Server"
-author		"Matt Zuba <matt.zuba@goodwillaz.org"
+description     "Celery for Kallithea Mercurial Server"
+author          "Matt Zuba <matt.zuba@goodwillaz.org"
 
 start on starting kallithea
 stop on stopped kallithea
@@ -21,7 +21,7 @@
 # env GROUP=hg
 
 script
-    COMMAND="/var/hg/.virtualenvs/kallithea/bin/paster celeryd $APPINI --pidfile=$PIDFILE"
+    COMMAND="/var/hg/.virtualenvs/kallithea/bin/kallithea-cli celery-run -c $APPINI -- --pidfile=$PIDFILE"
     if [ -z "$GROUP" ]; then
         exec sudo -u $USER $COMMAND
     else
--- a/init.d/kallithea-daemon-arch	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/kallithea-daemon-arch	Sun Mar 31 21:28:56 2019 +0200
@@ -12,7 +12,7 @@
 CONF_NAME="production.ini"
 LOG_FILE="/var/log/$DAEMON.log"
 PID_FILE="/run/daemons/$DAEMON"
-APPL=/usr/bin/paster
+APPL=/usr/bin/gearbox
 RUN_AS="*****"
 
 ARGS="serve --daemon \
@@ -20,7 +20,7 @@
 --group=$RUN_AS \
 --pid-file=$PID_FILE \
 --log-file=$LOG_FILE \
-$APP_PATH/$CONF_NAME"
+-c $APP_PATH/$CONF_NAME"
 
 [ -r /etc/conf.d/$DAEMON ] && . /etc/conf.d/$DAEMON
 
@@ -47,7 +47,7 @@
     ;;
 stop)
     stat_busy "Stopping $DAEMON"
-    [ -n "$PID" ] && kill $PID &>/dev/null 
+    [ -n "$PID" ] && kill $PID &>/dev/null
     if [ $? = 0 ]; then
         rm_daemon $DAEMON
         stat_done
@@ -67,4 +67,4 @@
     ;;
 *)
     echo "usage: $0 {start|stop|restart|status}"
-esac
\ No newline at end of file
+esac
--- a/init.d/kallithea-daemon-debian	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/kallithea-daemon-debian	Sun Mar 31 21:28:56 2019 +0200
@@ -2,9 +2,9 @@
 ########################################
 #### THIS IS A DEBIAN INIT.D SCRIPT ####
 ########################################
- 
+
 ### BEGIN INIT INFO
-# Provides:          kallithea          
+# Provides:          kallithea
 # Required-Start:    $all
 # Required-Stop:     $all
 # Default-Start:     2 3 4 5
@@ -12,29 +12,29 @@
 # Short-Description: starts instance of kallithea
 # Description:       starts instance of kallithea using start-stop-daemon
 ### END INIT INFO
- 
+
 APP_NAME="kallithea"
 APP_HOMEDIR="opt"
 APP_PATH="/$APP_HOMEDIR/$APP_NAME"
- 
+
 CONF_NAME="production.ini"
- 
+
 PID_PATH="$APP_PATH/$APP_NAME.pid"
 LOG_PATH="$APP_PATH/$APP_NAME.log"
- 
+
 PYTHON_PATH="/$APP_HOMEDIR/$APP_NAME-venv"
- 
+
 RUN_AS="root"
- 
-DAEMON="$PYTHON_PATH/bin/paster"
- 
+
+DAEMON="$PYTHON_PATH/bin/gearbox"
+
 DAEMON_OPTS="serve --daemon \
  --user=$RUN_AS \
  --group=$RUN_AS \
  --pid-file=$PID_PATH \
- --log-file=$LOG_PATH  $APP_PATH/$CONF_NAME"
- 
- 
+ --log-file=$LOG_PATH -c $APP_PATH/$CONF_NAME"
+
+
 start() {
   echo "Starting $APP_NAME"
   PYTHON_EGG_CACHE="/tmp" start-stop-daemon -d $APP_PATH \
@@ -43,18 +43,18 @@
       --user $RUN_AS \
       --exec $DAEMON -- $DAEMON_OPTS
 }
- 
+
 stop() {
   echo "Stopping $APP_NAME"
   start-stop-daemon -d $APP_PATH \
       --stop --quiet \
       --pidfile $PID_PATH || echo "$APP_NAME - Not running!"
- 
+
   if [ -f $PID_PATH ]; then
     rm $PID_PATH
   fi
 }
- 
+
 status() {
   echo -n "Checking status of $APP_NAME ... "
   pid=`cat $PID_PATH`
@@ -65,7 +65,7 @@
     echo "NOT running"
   fi
 }
- 
+
 case "$1" in
   status)
    status
@@ -87,4 +87,4 @@
   *)
     echo "Usage: $0 {start|stop|restart}"
     exit 1
-esac
\ No newline at end of file
+esac
--- a/init.d/kallithea-daemon-gentoo	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/kallithea-daemon-gentoo	Sun Mar 31 21:28:56 2019 +0200
@@ -16,13 +16,13 @@
 
 RUN_AS="username"
 
-DAEMON="$PYTHON_PATH/bin/paster"
+DAEMON="$PYTHON_PATH/bin/gearbox"
 
 DAEMON_OPTS="serve --daemon \
 --user=$RUN_AS \
 --group=$RUN_AS \
 --pid-file=$PID_PATH \
---log-file=$LOG_PATH  $APP_PATH/$CONF_NAME"
+--log-file=$LOG_PATH -c $APP_PATH/$CONF_NAME"
 
 #extra options
 opts="${opts} restartdelay"
@@ -56,6 +56,6 @@
     #stop()
     echo "sleep3"
     sleep 3
-    
+
     #start()
 }
--- a/init.d/kallithea-daemon-redhat	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/kallithea-daemon-redhat	Sun Mar 31 21:28:56 2019 +0200
@@ -20,7 +20,7 @@
 CONF_NAME="production.ini"
 
 # write to wherever the PID should be stored, just ensure
-# that the user you run paster as has the appropriate permissions
+# that the user you run gearbox as has the appropriate permissions
 # same goes for the log file
 PID_PATH="/var/run/kallithea/pid"
 LOG_PATH="/var/log/kallithea/kallithea.log"
@@ -31,13 +31,13 @@
 
 RUN_AS="kallithea"
 
-DAEMON="$PYTHON_PATH/bin/paster"
+DAEMON="$PYTHON_PATH/bin/gearbox"
 
 DAEMON_OPTS="serve --daemon \
     --user=$RUN_AS \
     --group=$RUN_AS \
     --pid-file=$PID_PATH \
-    --log-file=$LOG_PATH $APP_PATH/$CONF_NAME"
+    --log-file=$LOG_PATH -c $APP_PATH/$CONF_NAME"
 
 DESC="kallithea-server"
 LOCK_FILE="/var/lock/subsys/$APP_NAME"
@@ -129,4 +129,4 @@
     ;;
 esac
 
-exit $RETVAL
\ No newline at end of file
+exit $RETVAL
--- a/init.d/kallithea-upstart.conf	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/kallithea-upstart.conf	Sun Mar 31 21:28:56 2019 +0200
@@ -2,8 +2,8 @@
 # Change variables/paths as necessary and place file /etc/init/kallithea.conf
 # start/stop/restart as normal upstart job (ie: $ start kallithea)
 
-description	"Kallithea Mercurial Server"
-author		"Matt Zuba <matt.zuba@goodwillaz.org"
+description     "Kallithea Mercurial Server"
+author          "Matt Zuba <matt.zuba@goodwillaz.org"
 
 start on (local-filesystems and runlevel [2345])
 stop on runlevel [!2345]
@@ -19,8 +19,8 @@
 env USER=hg
 env GROUP=hg
 
-exec /var/hg/.virtualenvs/kallithea/bin/paster serve --user=$USER --group=$GROUP --pid-file=$PIDFILE --log-file=$LOGFILE $APPINI
+exec /var/hg/.virtualenvs/kallithea/bin/gearbox serve --user=$USER --group=$GROUP --pid-file=$PIDFILE --log-file=$LOGFILE -c $APPINI
 
 post-stop script
-	rm -f $PIDFILE
+    rm -f $PIDFILE
 end script
--- a/init.d/supervisord.conf	Sun Mar 03 21:36:25 2019 +0100
+++ b/init.d/supervisord.conf	Sun Mar 31 21:28:56 2019 +0200
@@ -45,7 +45,7 @@
 numprocs = 1
 numprocs_start = 5000 # possible should match ports
 directory=/srv/kallithea
-command = /srv/kallithea/venv/bin/paster serve my.ini
+command = /srv/kallithea/venv/bin/gearbox serve -c my.ini
 process_name = %(program_name)s_%(process_num)04d
-redirect_stderr=true 
-stdout_logfile=/%(here)s/kallithea.log 
+redirect_stderr=true
+stdout_logfile=/%(here)s/kallithea.log
--- a/kallithea/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,8 +15,9 @@
 kallithea
 ~~~~~~~~~
 
-Kallithea, a web based repository management based on pylons
-versioning implementation: http://www.python.org/dev/peps/pep-0386/
+Kallithea, a web based repository management system.
+
+Versioning implementation: http://www.python.org/dev/peps/pep-0386/
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -29,7 +30,7 @@
 import sys
 import platform
 
-VERSION = (0, 3, 7)
+VERSION = (0, 4, 0, 'rc2')
 BACKENDS = {
     'hg': 'Mercurial repository',
     'git': 'Git repository',
@@ -38,45 +39,20 @@
 CELERY_ON = False
 CELERY_EAGER = False
 
-# link to config for pylons
 CONFIG = {}
 
 # Linked module for extensions
 EXTENSIONS = {}
 
-# BRAND controls internal references in database and config to the products
-# own name.
-#
-# NOTE: If you want compatibility with a database that was originally created
-#  for use with the RhodeCode software product, change BRAND to "rhodecode",
-#  either by editing here or by creating a new file:
-#  echo "BRAND = 'rhodecode'" > kallithea/brand.py
-
-BRAND = "kallithea"
 try:
-    from kallithea.brand import BRAND
+    import kallithea.brand
 except ImportError:
     pass
-
-# Prefix for the ui and settings table names
-DB_PREFIX = (BRAND + "_") if BRAND != "kallithea" else ""
-
-# Users.extern_type and .extern_name value for local users
-EXTERN_TYPE_INTERNAL = BRAND if BRAND != 'kallithea' else 'internal'
-
-# db_migrate_version.repository_id value, same as kallithea/lib/dbmigrate/migrate.cfg
-DB_MIGRATIONS = BRAND + "_db_migrations"
+else:
+    assert False, 'Database rebranding is no longer supported; see README.'
 
-try:
-    from kallithea.lib import get_current_revision
-    _rev = get_current_revision(quiet=True)
-    if _rev and len(VERSION) > 3:
-        VERSION += (_rev[0],)
-except ImportError:
-    pass
 
-__version__ = ('.'.join((str(each) for each in VERSION[:3])))
-__dbversion__ = 31  # defines current db version for migrations
+__version__ = '.'.join(str(each) for each in VERSION)
 __platform__ = platform.system()
 __license__ = 'GPLv3'
 __py_version__ = sys.version_info
@@ -85,17 +61,3 @@
 
 is_windows = __platform__ in ['Windows']
 is_unix = not is_windows
-
-if len(VERSION) > 3:
-    __version__ += '.'+VERSION[3]
-
-    if len(VERSION) > 4:
-        __version__ += VERSION[4]
-    else:
-        __version__ += '0'
-
-# Hack for making the celery dependency kombu==1.5.1 compatible with Python
-# 2.7.11 which has https://hg.python.org/releases/2.7.11/rev/24bdc4940e81
-import uuid
-if not hasattr(uuid, '_uuid_generate_random'):
-    uuid._uuid_generate_random = None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/env.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,105 @@
+# -*- 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/>.
+
+# Alembic migration environment (configuration).
+
+import logging
+from logging.config import fileConfig
+
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+
+from kallithea.model import db
+
+
+# The alembic.config.Config object, which wraps the current .ini file.
+config = context.config
+
+# Default to use the main Kallithea database string in [app:main].
+# For advanced uses, this can be overridden by specifying an explicit
+# [alembic] sqlalchemy.url.
+database_url = (
+    config.get_main_option('sqlalchemy.url') or
+    config.get_section_option('app:main', 'sqlalchemy.url')
+)
+
+# Configure default logging for Alembic. (This can be overriden by the
+# config file, but usually isn't.)
+logging.getLogger('alembic').setLevel(logging.INFO)
+
+# Setup Python loggers based on the config file provided to the alembic
+# command. If we're being invoked via the Alembic API (presumably for
+# stamping during "kallithea-cli db-create"), config_file_name is not available,
+# and loggers are assumed to already have been configured.
+if config.config_file_name:
+    fileConfig(config.config_file_name, disable_existing_loggers=False)
+
+
+def include_in_autogeneration(object, name, type, reflected, compare_to):
+    """Filter changes subject to autogeneration of migrations. """
+
+    # Don't include changes to sqlite_sequence.
+    if type == 'table' and name == 'sqlite_sequence':
+        return False
+
+    return True
+
+
+def run_migrations_offline():
+    """Run migrations in 'offline' (--sql) mode.
+
+    This produces an SQL script instead of directly applying the changes.
+    Some migrations may not run in offline mode.
+    """
+    context.configure(
+        url=database_url,
+        literal_binds=True,
+    )
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+
+def run_migrations_online():
+    """Run migrations in 'online' mode.
+
+    Connects to the database and directly applies the necessary
+    migrations.
+    """
+    cfg = config.get_section(config.config_ini_section)
+    cfg['sqlalchemy.url'] = database_url
+    connectable = engine_from_config(
+        cfg,
+        prefix='sqlalchemy.',
+        poolclass=pool.NullPool)
+
+    with connectable.connect() as connection:
+        context.configure(
+            connection=connection,
+
+            # Support autogeneration of migration scripts based on "diff" between
+            # current database schema and kallithea.model.db schema.
+            target_metadata=db.Base.metadata,
+            include_object=include_in_autogeneration,
+            render_as_batch=True, # batch mode is needed for SQLite support
+        )
+
+        with context.begin_transaction():
+            context.run_migrations()
+
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/script.py.mako	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,40 @@
+# -*- 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/>.
+
+## Template for creating new Alembic migration scripts.
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+def upgrade():
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+    ${downgrades if downgrades else "pass"}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/versions/9358dc3d6828_drop_sqlalchemy_migrate_support.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,37 @@
+# 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/>.
+
+"""Drop SQLAlchemy Migrate support
+
+Revision ID: 9358dc3d6828
+Revises:
+Create Date: 2016-03-01 15:21:30.896585
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = '9358dc3d6828'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+from alembic import op
+
+
+def upgrade():
+    op.drop_table('db_migrate_version')
+
+
+def downgrade():
+    raise NotImplementedError('cannot revert to SQLAlchemy Migrate')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/alembic/versions/a020f7044fd6_rename_hooks.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,61 @@
+# 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/>.
+
+"""rename hooks
+
+Revision ID: a020f7044fd6
+Revises: 9358dc3d6828
+Create Date: 2017-11-24 13:35:14.374000
+
+"""
+
+# The following opaque hexadecimal identifiers ("revisions") are used
+# by Alembic to track this migration script and its relations to others.
+revision = 'a020f7044fd6'
+down_revision = '9358dc3d6828'
+branch_labels = None
+depends_on = None
+
+from alembic import op
+from kallithea.model.db import Ui
+from sqlalchemy import Table, MetaData
+
+meta = MetaData()
+
+
+def upgrade():
+    meta.bind = op.get_bind()
+    ui = Table(Ui.__tablename__, meta, autoload=True)
+
+    ui.update(values={
+        'ui_key': 'prechangegroup.push_lock_handling',
+        'ui_value': 'python:kallithea.lib.hooks.push_lock_handling',
+    }).where(ui.c.ui_key == 'prechangegroup.pre_push').execute()
+    ui.update(values={
+        'ui_key': 'preoutgoing.pull_lock_handling',
+        'ui_value': 'python:kallithea.lib.hooks.pull_lock_handling',
+    }).where(ui.c.ui_key == 'preoutgoing.pre_pull').execute()
+
+
+def downgrade():
+    meta.bind = op.get_bind()
+    ui = Table(Ui.__tablename__, meta, autoload=True)
+
+    ui.update(values={
+        'ui_key': 'prechangegroup.pre_push',
+        'ui_value': 'python:kallithea.lib.hooks.pre_push',
+    }).where(ui.c.ui_key == 'prechangegroup.push_lock_handling').execute()
+    ui.update(values={
+        'ui_key': 'preoutgoing.pre_pull',
+        'ui_value': 'python:kallithea.lib.hooks.pre_pull',
+    }).where(ui.c.ui_key == 'preoutgoing.pull_lock_handling').execute()
--- a/kallithea/bin/base.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/bin/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -111,8 +111,8 @@
             return True
         return False
 
-    def __eq__(self):
-        return self._conf.__eq__()
+    def __eq__(self, other):
+        return self._conf.__eq__(other)
 
     def __repr__(self):
         return 'RcConf<%s>' % self._conf.__repr__()
@@ -158,7 +158,7 @@
         """
         try:
             with open(self._conf_name, 'rb') as conf:
-                return  json.load(conf)
+                return json.load(conf)
         except IOError as e:
             #sys.stderr.write(str(e) + '\n')
             pass
--- a/kallithea/bin/kallithea_api.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/bin/kallithea_api.py	Sun Mar 31 21:28:56 2019 +0200
@@ -121,5 +121,6 @@
         )
     return 0
 
+
 if __name__ == '__main__':
     sys.exit(main(sys.argv))
--- a/kallithea/bin/kallithea_backup.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/bin/kallithea_backup.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,11 +28,11 @@
 
 import os
 import sys
-
 import logging
 import tarfile
 import datetime
 import subprocess
+import tempfile
 
 logging.basicConfig(level=logging.DEBUG,
                     format="%(asctime)s %(levelname)-5.5s %(message)s")
@@ -47,7 +47,7 @@
         self.repos_path = self.get_repos_path(repos_location)
         self.backup_server = backup_server
 
-        self.backup_file_path = '/tmp'
+        self.backup_file_path = tempfile.gettempdir()
 
         logging.info('starting backup for %s', self.repos_path)
         logging.info('backup target %s', self.backup_file_path)
@@ -86,12 +86,13 @@
                '%(backup_server)s' % params]
 
         subprocess.call(cmd)
-        logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
+        logging.info('Transferred file %s to %s', self.backup_file_name, cmd[4])
 
     def rm_file(self):
         logging.info('Removing file %s', self.backup_file_name)
         os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
 
+
 if __name__ == "__main__":
 
     repo_location = '/home/repo_path'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,27 @@
+# -*- 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/>.
+
+# 'cli' is the main entry point for 'kallithea-cli', specified in setup.py as entry_points console_scripts
+from kallithea.bin.kallithea_cli_base import cli
+
+# import commands (they will add themselves to the 'cli' object)
+import kallithea.bin.kallithea_cli_celery
+import kallithea.bin.kallithea_cli_config
+import kallithea.bin.kallithea_cli_db
+import kallithea.bin.kallithea_cli_extensions
+import kallithea.bin.kallithea_cli_front_end
+import kallithea.bin.kallithea_cli_iis
+import kallithea.bin.kallithea_cli_index
+import kallithea.bin.kallithea_cli_ishell
+import kallithea.bin.kallithea_cli_repo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,55 @@
+# -*- 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/>.
+
+import click
+import functools
+import os
+
+import kallithea
+import logging.config
+import paste.deploy
+
+
+# This placeholder is the main entry point for the kallithea-cli command
+@click.group()
+def cli():
+    """Various commands to manage a Kallithea instance."""
+
+def register_command(config_file=False, config_file_initialize_app=False):
+    """Register a kallithea-cli subcommand.
+
+    If one of the config_file flags are true, a config file must be specified
+    with -c and it is read and logging is configured. The configuration is
+    available in the kallithea.CONFIG dict.
+
+    If config_file_initialize_app is true, Kallithea, TurboGears global state
+    (including tg.config), and database access will also be fully initialized.
+    """
+    cli_command = cli.command()
+    if config_file or config_file_initialize_app:
+        def annotator(annotated):
+            @click.option('--config_file', '-c', help="Path to .ini file with app configuration.",
+                type=click.Path(dir_okay=False, exists=True, readable=True), required=True)
+            @functools.wraps(annotated) # reuse meta data from the wrapped function so click can see other options
+            def runtime_wrapper(config_file, *args, **kwargs):
+                path_to_ini_file = os.path.realpath(config_file)
+                kallithea.CONFIG = paste.deploy.appconfig('config:' + path_to_ini_file)
+                logging.config.fileConfig(path_to_ini_file)
+                if config_file_initialize_app:
+                    kallithea.config.middleware.make_app_without_logging(kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
+                    kallithea.lib.utils.setup_cache_regions(kallithea.CONFIG)
+                return annotated(*args, **kwargs)
+            return cli_command(runtime_wrapper)
+        return annotator
+    return cli_command
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_celery.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,39 @@
+# -*- 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/>.
+
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import kallithea
+
+@cli_base.register_command(config_file_initialize_app=True)
+@click.argument('celery_args', nargs=-1)
+def celery_run(celery_args):
+    """Start Celery worker(s) for asynchronous tasks.
+
+    This commands starts the Celery daemon which will spawn workers to handle
+    certain asynchronous tasks for Kallithea.
+
+    Any extra arguments you pass to this command will be passed through to
+    Celery. Use '--' before such extra arguments to avoid options to be parsed
+    by this CLI command.
+    """
+
+    if not kallithea.CELERY_ON:
+        raise Exception('Please set use_celery = true in .ini config '
+                        'file before running this command')
+
+    from kallithea.lib import celerypylons
+    cmd = celerypylons.worker.worker(celerypylons.app)
+    return cmd.run_from_argv(None, command='celery-run -c CONFIG_FILE --', argv=list(celery_args))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_config.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,92 @@
+# -*- 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/>.
+
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import os
+import uuid
+from collections import defaultdict
+
+import mako.exceptions
+
+from kallithea.lib import inifile
+
+def show_defaults(ctx, param, value):
+    # Following construct is taken from the Click documentation:
+    # https://click.palletsprojects.com/en/7.x/options/#callbacks-and-eager-options
+    # "The resilient_parsing flag is applied to the context if Click wants to
+    # parse the command line without any destructive behavior that would change
+    # the execution flow. In this case, because we would exit the program, we
+    # instead do nothing."
+    if not value or ctx.resilient_parsing:
+        return
+
+    for key, value in inifile.default_variables.items():
+        click.echo('%s=%s' % (key, value))
+
+    ctx.exit()
+
+@cli_base.register_command()
+@click.option('--show-defaults', callback=show_defaults,
+              is_flag=True, expose_value=False, is_eager=True,
+              help='Show the default values that can be overridden')
+@click.argument('config_file', type=click.Path(dir_okay=False, writable=True), required=True)
+@click.argument('key_value_pairs', nargs=-1)
+def config_create(config_file, key_value_pairs):
+    """Create a new configuration file.
+
+    This command creates a default configuration file, possibly adding/updating
+    settings you specify.
+
+    The primary high level configuration keys and their default values are
+    shown with --show-defaults . Custom values for these keys can be specified
+    on the command line as key=value arguments.
+
+    Additional key=value arguments will be patched/inserted in the [app:main]
+    section ... until another section name specifies where any following values
+    should go.
+    """
+
+    mako_variable_values = {}
+    ini_settings = defaultdict(dict)
+
+    section_name = None
+    for parameter in key_value_pairs:
+        parts = parameter.split('=', 1)
+        if len(parts) == 1 and parameter.startswith('[') and parameter.endswith(']'):
+            section_name = parameter
+        elif len(parts) == 2:
+            key, value = parts
+            if section_name is None and key in inifile.default_variables:
+                mako_variable_values[key] = value
+            else:
+                if section_name is None:
+                    section_name = '[app:main]'
+                ini_settings[section_name][key] = value
+        else:
+            raise ValueError("Invalid name=value parameter %r" % parameter)
+
+    # use default that cannot be replaced
+    mako_variable_values.update({
+        'uuid': lambda: uuid.uuid4().hex,
+    })
+    try:
+        config_file_abs = os.path.abspath(config_file)
+        inifile.create(config_file_abs, mako_variable_values, ini_settings)
+        click.echo('Wrote new config file in %s' % config_file_abs)
+        click.echo("Don't forget to build the front-end using 'kallithea-cli front-end-build'.")
+
+    except Exception:
+        click.echo(mako.exceptions.text_error_template().render())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_db.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,79 @@
+# -*- 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/>.
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import kallithea
+from kallithea.lib.db_manage import DbManage
+from kallithea.model.meta import Session
+
+@cli_base.register_command(config_file=True)
+@click.option('--user', help='Username of administrator account.')
+@click.option('--password', help='Password for administrator account.')
+@click.option('--email', help='Email address of administrator account.')
+@click.option('--repos', help='Absolute path to repositories location.')
+@click.option('--force-yes', is_flag=True, help='Answer yes to every question.')
+@click.option('--force-no', is_flag=True, help='Answer no to every question.')
+@click.option('--public-access/--no-public-access', default=True,
+        help='Enable/disable public access on this installation (default: enable)')
+def db_create(user, password, email, repos, force_yes, force_no, public_access):
+    """Initialize the database.
+
+    Create all required tables in the database specified in the configuration
+    file. Create the administrator account. Set certain settings based on
+    values you provide.
+
+    You can pass the answers to all questions as options to this command.
+    """
+    dbconf = kallithea.CONFIG['sqlalchemy.url']
+
+    # force_ask should be True (yes), False (no), or None (ask)
+    if force_yes:
+        force_ask = True
+    elif force_no:
+        force_ask = False
+    else:
+        force_ask = None
+
+    cli_args = dict(
+            username=user,
+            password=password,
+            email=email,
+            repos_location=repos,
+            force_ask=force_ask,
+            public_access=public_access,
+    )
+    dbmanage = DbManage(dbconf=dbconf, root=kallithea.CONFIG['here'],
+                        tests=False, cli_args=cli_args)
+    dbmanage.create_tables(override=True)
+    opts = dbmanage.config_prompt(None)
+    dbmanage.create_settings(opts)
+    dbmanage.create_default_user()
+    dbmanage.admin_prompt()
+    dbmanage.create_permissions()
+    dbmanage.populate_default_permissions()
+    Session().commit()
+
+    # initial repository scan
+    kallithea.config.middleware.make_app_without_logging(
+            kallithea.CONFIG.global_conf, **kallithea.CONFIG.local_conf)
+    added, _ = kallithea.lib.utils.repo2db_mapper(kallithea.model.scm.ScmModel().repo_scan())
+    if added:
+        click.echo('Initial repository scan: added following repositories:')
+        click.echo('\t%s' % '\n\t'.join(added))
+    else:
+        click.echo('Initial repository scan: no repositories found.')
+
+    click.echo('Database set up successfully.')
+    click.echo("Don't forget to build the front-end using 'kallithea-cli front-end-build'.")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_extensions.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,56 @@
+# -*- 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/>.
+"""
+This file was forked by the Kallithea project in July 2014 and later moved.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Mar 6, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import os
+import pkg_resources
+
+import kallithea
+from kallithea.lib.utils2 import ask_ok
+
+@cli_base.register_command(config_file=True)
+def extensions_create():
+    """Write template file for extending Kallithea in Python.
+
+    An 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.
+    """
+    here = kallithea.CONFIG['here']
+    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 %s already exists, do you want '
+               'to overwrite it ? [y/n] ') % ext_file
+        if not ask_ok(msg):
+            click.echo('Nothing done, exiting...')
+            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)
+        click.echo('Wrote new extensions file to %s' % ext_file)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_front_end.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,108 @@
+# -*- 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/>.
+
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import os
+import shutil
+import subprocess
+import json
+
+import kallithea
+
+@cli_base.register_command()
+@click.option('--install-deps/--no-install-deps', default=True,
+        help='Skip installation of dependencies, via "npm".')
+@click.option('--generate/--no-generate', default=True,
+        help='Skip generation of front-end files.')
+def front_end_build(install_deps, generate):
+    """Build the front-end.
+
+    Install required dependencies for the front-end and generate the necessary
+    files.  This step is complementary to any 'pip install' step which only
+    covers Python dependencies.
+
+    The installation of front-end dependencies happens via the tool 'npm' which
+    is expected to be installed already.
+    """
+    front_end_dir = os.path.abspath(os.path.join(kallithea.__file__, '..', 'front-end'))
+    public_dir = os.path.abspath(os.path.join(kallithea.__file__, '..', 'public'))
+
+    if install_deps:
+        click.echo("Running 'npm install' to install front-end dependencies from package.json")
+        subprocess.check_call(['npm', 'install'], cwd=front_end_dir, shell=kallithea.is_windows)
+
+    if generate:
+        tmp_dir = os.path.join(front_end_dir, 'tmp')
+        if not os.path.isdir(tmp_dir):
+            os.mkdir(tmp_dir)
+
+        click.echo("Building CSS styling based on Bootstrap")
+        with open(os.path.join(tmp_dir, 'pygments.css'), 'w') as f:
+            subprocess.check_call(['pygmentize',
+                    '-S', 'default',
+                    '-f', 'html',
+                    '-a', '.code-highlight'],
+                    stdout=f)
+        lesscpath = os.path.join(front_end_dir, 'node_modules', '.bin', 'lessc')
+        lesspath = os.path.join(front_end_dir, 'main.less')
+        csspath = os.path.join(public_dir, 'css', 'style.css')
+        subprocess.check_call([lesscpath, '--source-map',
+                '--source-map-less-inline', lesspath, csspath],
+                cwd=front_end_dir, shell=kallithea.is_windows)
+
+        click.echo("Preparing Bootstrap JS")
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'bootstrap', 'dist', 'js', 'bootstrap.js'), os.path.join(public_dir, 'js', 'bootstrap.js'))
+
+        click.echo("Preparing jQuery JS with Flot, Caret and Atwho")
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'jquery', 'dist', 'jquery.min.js'), os.path.join(public_dir, 'js', 'jquery.min.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'jquery.flot', 'jquery.flot.js'), os.path.join(public_dir, 'js', 'jquery.flot.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'jquery.flot', 'jquery.flot.selection.js'), os.path.join(public_dir, 'js', 'jquery.flot.selection.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'jquery.flot', 'jquery.flot.time.js'), os.path.join(public_dir, 'js', 'jquery.flot.time.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'jquery.caret', 'dist', 'jquery.caret.min.js'), os.path.join(public_dir, 'js', 'jquery.caret.min.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'at.js', 'dist', 'js', 'jquery.atwho.min.js'), os.path.join(public_dir, 'js', 'jquery.atwho.min.js'))
+
+        click.echo("Preparing DataTables JS")
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'datatables.net', 'js', 'jquery.dataTables.js'), os.path.join(public_dir, 'js', 'jquery.dataTables.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'datatables.net-bs', 'js', 'dataTables.bootstrap.js'), os.path.join(public_dir, 'js', 'dataTables.bootstrap.js'))
+
+        click.echo("Preparing Select2 JS")
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'select2', 'select2.js'), os.path.join(public_dir, 'js', 'select2.js'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'select2', 'select2.png'), os.path.join(public_dir, 'css', 'select2.png'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'select2', 'select2x2.png'), os.path.join(public_dir, 'css', 'select2x2.png'))
+        shutil.copy(os.path.join(front_end_dir, 'node_modules', 'select2', 'select2-spinner.gif'), os.path.join(public_dir, 'css', 'select2-spinner.gif'))
+
+        click.echo("Preparing CodeMirror JS")
+        if os.path.isdir(os.path.join(public_dir, 'codemirror')):
+            shutil.rmtree(os.path.join(public_dir, 'codemirror'))
+        shutil.copytree(os.path.join(front_end_dir, 'node_modules', 'codemirror'), os.path.join(public_dir, 'codemirror'))
+
+        click.echo("Generating LICENSES.txt")
+        license_checker_path = os.path.join(front_end_dir, 'node_modules', '.bin', 'license-checker')
+        check_licensing_json_path = os.path.join(tmp_dir, 'licensing.json')
+        licensing_txt_path = os.path.join(public_dir, 'LICENSES.txt')
+        subprocess.check_call([license_checker_path, '--json', '--out', check_licensing_json_path],
+                cwd=front_end_dir, shell=kallithea.is_windows)
+        with open(check_licensing_json_path) as jsonfile:
+            rows = json.loads(jsonfile.read())
+            with open(licensing_txt_path, 'w') as out:
+                out.write("The Kallithea front-end was built using the following Node modules:\n\n")
+                for name_version, values in sorted(rows.items()):
+                    name, version = name_version.rsplit('@', 1)
+                    line = "%s from https://www.npmjs.com/package/%s/v/%s\n  License: %s\n  Repository: %s\n" % (
+                        name_version, name, version, values['licenses'], values.get('repository', '-'))
+                    if values.get('copyright'):
+                        line += "  Copyright: %s\n" % (values['copyright'])
+                    out.write(line + '\n')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_iis.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,81 @@
+# -*- 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/>.
+import click
+import kallithea
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import os
+import sys
+
+dispath_py_template = '''\
+# Created by Kallithea 'kallithea-cli iis-install'
+import sys
+
+if hasattr(sys, "isapidllhandle"):
+    import win32traceutil
+
+import isapi_wsgi
+import os
+
+def __ExtensionFactory__():
+    from paste.deploy import loadapp
+    from logging.config import fileConfig
+    fileConfig('%(inifile)s')
+    application = loadapp('config:%(inifile)s')
+
+    def app(environ, start_response):
+        user = environ.get('REMOTE_USER', None)
+        if user is not None:
+            os.environ['REMOTE_USER'] = user
+        return application(environ, start_response)
+
+    return isapi_wsgi.ISAPIThreadPoolHandler(app)
+
+if __name__=='__main__':
+    from isapi.install import *
+    params = ISAPIParameters()
+    sm = [ScriptMapParams(Extension="*", Flags=0)]
+    vd = VirtualDirParameters(Name="%(virtualdir)s",
+                              Description = "Kallithea",
+                              ScriptMaps = sm,
+                              ScriptMapUpdate = "replace")
+    params.VirtualDirs = [vd]
+    HandleCommandLine(params)
+'''
+
+@cli_base.register_command(config_file=True)
+@click.option('--virtualdir', default='/',
+        help='The virtual folder to install into on IIS.')
+def iis_install(virtualdir):
+    """Install into IIS using isapi-wsgi."""
+
+    config_file_abs = kallithea.CONFIG['__file__']
+
+    try:
+        import isapi_wsgi
+    except ImportError:
+        sys.stderr.write('missing requirement: isapi-wsgi not installed\n')
+        sys.exit(1)
+
+    dispatchfile = os.path.join(os.getcwd(), 'dispatch.py')
+    click.echo('Writing %s' % dispatchfile)
+    with open(dispatchfile, 'w') as f:
+        f.write(dispath_py_template % {
+            'inifile': config_file_abs.replace('\\', '\\\\'),
+            'virtualdir': virtualdir,
+            })
+
+    click.echo('Run \'python "%s" install\' with administrative privileges '
+        'to generate the _dispatch.dll file and install it into the '
+        'default web site' % dispatchfile)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_index.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+This file was forked by the Kallithea project in July 2014 and later moved.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Aug 17, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import os
+from string import strip
+import sys
+
+import kallithea
+from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
+from kallithea.lib.pidlock import LockHeld, DaemonLock
+from kallithea.lib.utils import load_rcextensions
+from kallithea.model.repo import RepoModel
+
+@cli_base.register_command(config_file_initialize_app=True)
+@click.option('--repo-location', help='Base path of repositories to index. Default: all')
+@click.option('--index-only', help='Comma-separated list of repositories to build index on. Default: all')
+@click.option('--update-only', help='Comma-separated list of repositories to re-build index on. Default: all')
+@click.option('-f', '--full', 'full_index', help='Recreate the index from scratch')
+def index_create(repo_location, index_only, update_only, full_index):
+    """Create or update full text search index"""
+
+    index_location = kallithea.CONFIG['index_dir']
+    load_rcextensions(kallithea.CONFIG['here'])
+
+    if not repo_location:
+        repo_location = RepoModel().repos_path
+    repo_list = map(strip, index_only.split(',')) \
+        if index_only else None
+    repo_update_list = map(strip, update_only.split(',')) \
+        if update_only else None
+
+    try:
+        l = DaemonLock(os.path.join(index_location, 'make_index.lock'))
+        WhooshIndexingDaemon(index_location=index_location,
+                             repo_location=repo_location,
+                             repo_list=repo_list,
+                             repo_update_list=repo_update_list) \
+            .run(full_index=full_index)
+        l.release()
+    except LockHeld:
+        sys.exit(1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_ishell.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,41 @@
+# -*- 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/>.
+"""
+This file was forked by the Kallithea project in July 2014 and later moved.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Apr 4, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import sys
+
+# make following imports directly available inside the ishell
+from kallithea.model.db import *
+
+@cli_base.register_command(config_file_initialize_app=True)
+def ishell():
+    """Interactive shell for Kallithea."""
+    try:
+        from IPython import embed
+    except ImportError:
+        print 'Kallithea ishell requires the Python package IPython 4 or later'
+        sys.exit(-1)
+    from traitlets.config.loader import Config
+    cfg = Config()
+    cfg.InteractiveShellEmbed.confirm_exit = False
+    embed(config=cfg, banner1="Kallithea IShell.")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/bin/kallithea_cli_repo.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,185 @@
+# -*- 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/>.
+"""
+This file was forked by the Kallithea project in July 2014 and later moved.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Feb 9, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+import click
+import kallithea.bin.kallithea_cli_base as cli_base
+
+import datetime
+import os
+import re
+import shutil
+
+from kallithea.lib.utils import repo2db_mapper, REMOVED_REPO_PAT
+from kallithea.lib.utils2 import safe_unicode, safe_str, ask_ok
+from kallithea.model.db import Repository, Ui
+from kallithea.model.meta import Session
+from kallithea.model.scm import ScmModel
+
+@cli_base.register_command(config_file_initialize_app=True)
+@click.option('--remove-missing', is_flag=True,
+        help='Remove missing repositories from the Kallithea database.')
+def repo_scan(remove_missing):
+    """Scan filesystem for repositories.
+
+    Search the configured repository root for new repositories and add them
+    into Kallithea.
+    Additionally, report repositories that were previously known to Kallithea
+    but are no longer present on the filesystem. If option --remove-missing is
+    given, remove the missing repositories from the Kallithea database.
+    """
+    click.echo('Now scanning root location for new repos ...')
+    added, removed = repo2db_mapper(ScmModel().repo_scan(),
+                                    remove_obsolete=remove_missing)
+    click.echo('Scan completed.')
+    if added:
+        click.echo('Added: %s' % ', '.join(added))
+    if removed:
+        click.echo('%s: %s' % ('Removed' if remove_missing else 'Missing',
+                          ', '.join(removed)))
+
+@cli_base.register_command(config_file_initialize_app=True)
+@click.argument('repositories', nargs=-1)
+def repo_update_metadata(repositories):
+    """
+    Update repository metadata in database from repository content.
+
+    In normal operation, Kallithea will keep caches up-to-date
+    automatically. However, if repositories are externally modified, e.g. by
+    a direct push via the filesystem rather than via a Kallithea URL,
+    Kallithea is not aware of it. In this case, you should manually run this
+    command to update the repository cache.
+
+    If no repositories are specified, the caches of all repositories are
+    updated.
+    """
+    if not repositories:
+        repo_list = Repository.query().all()
+    else:
+        repo_names = [safe_unicode(n.strip()) for n in repositories]
+        repo_list = list(Repository.query()
+                        .filter(Repository.repo_name.in_(repo_names)))
+
+    for repo in repo_list:
+        # update latest revision metadata in database
+        repo.update_changeset_cache()
+        # invalidate in-memory VCS object cache... will be repopulated on
+        # first access
+        repo.set_invalidate()
+
+    Session().commit()
+
+    click.echo('Updated database with information about latest change in the following %s repositories:' % (len(repo_list)))
+    click.echo('\n'.join(repo.repo_name for repo in repo_list))
+
+@cli_base.register_command(config_file_initialize_app=True)
+@click.option('--ask/--no-ask', default=True, help='Ask for confirmation or not. Default is --ask.')
+@click.option('--older-than',
+        help="""Only purge repositories that have been removed at least the given time ago.
+        For example, '--older-than=30d' purges repositories deleted 30 days ago or longer.
+        Possible suffixes: d (days), h (hours), m (minutes), s (seconds).""")
+def repo_purge_deleted(ask, older_than):
+    """Purge backups of deleted repositories.
+
+    When a repository is deleted via the Kallithea web interface, the actual
+    data is still present on the filesystem but set aside using a special name.
+    This command allows to delete these files permanently.
+    """
+    def _parse_older_than(val):
+        regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
+        parts = regex.match(val)
+        if not parts:
+            return
+        parts = parts.groupdict()
+        time_params = {}
+        for (name, param) in parts.iteritems():
+            if param:
+                time_params[name] = int(param)
+        return datetime.timedelta(**time_params)
+
+    def _extract_date(name):
+        """
+        Extract the date part from rm__<date> pattern of removed repos,
+        and convert it to datetime object
+
+        :param name:
+        """
+        date_part = name[4:19]  # 4:19 since we don't parse milliseconds
+        return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
+
+    repos_location = Ui.get_repos_location()
+    to_remove = []
+    for dn_, dirs, f in os.walk(safe_str(repos_location)):
+        alldirs = list(dirs)
+        del dirs[:]
+        if ('.hg' in alldirs or
+            '.git' in alldirs or
+            '.svn' in alldirs or
+            'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)):
+            continue
+        for loc in alldirs:
+            if REMOVED_REPO_PAT.match(loc):
+                to_remove.append([os.path.join(dn_, loc),
+                                  _extract_date(loc)])
+            else:
+                dirs.append(loc)
+        if dirs:
+            click.echo('Scanning: %s' % dn_)
+
+    if not to_remove:
+        click.echo('There are no deleted repositories.')
+        return
+
+    # filter older than (if present)!
+    if older_than:
+        now = datetime.datetime.now()
+        to_remove_filtered = []
+        older_than_date = _parse_older_than(older_than)
+        for name, date_ in to_remove:
+            repo_age = now - date_
+            if repo_age > older_than_date:
+                to_remove_filtered.append([name, date_])
+
+        to_remove = to_remove_filtered
+
+        if not to_remove:
+            click.echo('There are no deleted repositories older than %s (%s)'
+                    % (older_than, older_than_date))
+            return
+
+        click.echo('Considering %s deleted repositories older than %s (%s).'
+            % (len(to_remove), older_than, older_than_date))
+    else:
+        click.echo('Considering %s deleted repositories.' % len(to_remove))
+
+    if not ask:
+        remove = True
+    else:
+        remove = ask_ok('The following repositories will be removed completely:\n%s\n'
+                'Do you want to proceed? [y/n] '
+                % '\n'.join(['%s deleted on %s' % (safe_str(x[0]), safe_str(x[1]))
+                                     for x in to_remove]))
+
+    if remove:
+        for path, date_ in to_remove:
+            click.echo('Purging repository %s' % path)
+            shutil.rmtree(path)
+    else:
+        click.echo('Nothing done, exiting...')
--- a/kallithea/bin/kallithea_config.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-#!/usr/bin/env python2
-
-# -*- 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.bin.kallithea_config
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-configuration generator for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Jun 18, 2013
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import uuid
-import argparse
-from mako.template import Template
-TMPL = 'template.ini.mako'
-here = os.path.dirname(os.path.abspath(__file__))
-
-def argparser(argv):
-    usage = (
-      "kallithea-config [-h] [--filename=FILENAME] [--template=TEMPLATE] \n"
-      "VARS optional specify extra template variable that will be available in "
-      "template. Use comma separated key=val format eg.\n"
-      "key1=val1,port=5000,host=127.0.0.1,elements='a\,b\,c'\n"
-    )
-
-    parser = argparse.ArgumentParser(
-        description='Kallithea CONFIG generator with variable replacement',
-        usage=usage
-    )
-
-    ## config
-    group = parser.add_argument_group('CONFIG')
-    group.add_argument('--filename', help='Output ini filename.')
-    group.add_argument('--template', help='Mako template file to use instead of '
-                                          'the default builtin template')
-    group.add_argument('--raw', help='Store given mako template as raw without '
-                                     'parsing. Use this to create custom template '
-                                     'initially', action='store_true')
-    group.add_argument('--show-defaults', help='Show all default variables for '
-                                               'builtin template', action='store_true')
-    args, other = parser.parse_known_args()
-    return parser, args, other
-
-
-def _escape_split(text, sep):
-    """
-    Allows for escaping of the separator: e.g. arg='foo\, bar'
-
-    It should be noted that the way bash et. al. do command line parsing, those
-    single quotes are required. a shameless ripoff from fabric project.
-
-    """
-    escaped_sep = r'\%s' % sep
-
-    if escaped_sep not in text:
-        return text.split(sep)
-
-    before, _, after = text.partition(escaped_sep)
-    startlist = before.split(sep)  # a regular split is fine here
-    unfinished = startlist[-1]
-    startlist = startlist[:-1]
-
-    # recurse because there may be more escaped separators
-    endlist = _escape_split(after, sep)
-
-    # finish building the escaped value. we use endlist[0] becaue the first
-    # part of the string sent in recursion is the rest of the escaped value.
-    unfinished += sep + endlist[0]
-
-    return startlist + [unfinished] + endlist[1:]  # put together all the parts
-
-def _run(argv):
-    parser, args, other = argparser(argv)
-    if not len(sys.argv) > 1:
-        print parser.print_help()
-        sys.exit(0)
-    # defaults that can be overwritten by arguments
-    tmpl_stored_args = {
-        'http_server': 'waitress',
-        'lang': 'en',
-        'database_engine': 'sqlite',
-        'host': '127.0.0.1',
-        'port': 5000,
-        'error_aggregation_service': None,
-    }
-    if other:
-        # parse arguments, we assume only first is correct
-        kwargs = {}
-        for el in _escape_split(other[0], ','):
-            kv = _escape_split(el, '=')
-            if len(kv) == 2:
-                k, v = kv
-                kwargs[k] = v
-        # update our template stored args
-        tmpl_stored_args.update(kwargs)
-
-    # use default that cannot be replaced
-    tmpl_stored_args.update({
-        'uuid': lambda: uuid.uuid4().hex,
-        'here': os.path.abspath(os.curdir),
-    })
-    if args.show_defaults:
-        for k,v in tmpl_stored_args.iteritems():
-            print '%s=%s' % (k, v)
-        sys.exit(0)
-    try:
-        # built in template
-        tmpl_file = os.path.join(here, TMPL)
-        if args.template:
-            tmpl_file = args.template
-
-        with open(tmpl_file, 'rb') as f:
-            tmpl_data = f.read().decode('utf-8')
-            if args.raw:
-                tmpl = tmpl_data
-            else:
-                tmpl = Template(tmpl_data).render(**tmpl_stored_args)
-        with open(args.filename, 'wb') as f:
-            f.write(tmpl.encode('utf-8'))
-        print 'Wrote new config file in %s' % (os.path.abspath(args.filename))
-
-    except Exception:
-        from mako import exceptions
-        print exceptions.text_error_template().render()
-
-def main(argv=None):
-    """
-    Main execution function for cli
-
-    :param argv:
-    """
-    if argv is None:
-        argv = sys.argv
-
-    return _run(argv)
-
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
--- a/kallithea/bin/ldap_sync.conf	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/bin/ldap_sync.conf	Sun Mar 31 21:28:56 2019 +0200
@@ -3,9 +3,9 @@
 api_user = admin
 api_key = XXXXXXXXXXXX
 
-ldap_uri = ldap://ldap.example.com:389
+ldap_uri = ldaps://ldap.example.com:636
 ldap_user = cn=kallithea,dc=example,dc=com
 ldap_key = XXXXXXXXX
 base_dn = dc=example,dc=com
 
-sync_users = True
\ No newline at end of file
+sync_users = True
--- a/kallithea/bin/ldap_sync.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/bin/ldap_sync.py	Sun Mar 31 21:28:56 2019 +0200
@@ -207,7 +207,7 @@
         groups = self.ldap_client.get_groups()
         for group in groups:
             try:
-                self.kallithea_api.create_repo_group(group)
+                self.kallithea_api.create_group(group)
                 added += 1
             except Exception:
                 existing += 1
--- a/kallithea/bin/rebranddb.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-#!/usr/bin/env python2
-
-# 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/>.
-"""
-Script for rebranding of database to and from what Kallithea expects
-
-Works on databases from v1.7.2 to v2.2.5
-"""
-
-import sys
-from sqlalchemy import *
-import sqlalchemy.orm
-import sqlalchemy.ext.declarative
-import migrate.changeset # a part of sqlalchemy-migrate which is available on pypi
-
-def do_migrate(db, old, new):
-    print 'Migrating %s from %s to %s' % (db, old or '?', new)
-    metadata = MetaData()
-    metadata.bind = create_engine(db)
-    metadata.reflect()
-    assert metadata.tables, 'Cannot reflect table names from db'
-
-    if not old:
-        assert 'db_migrate_version' in metadata.tables, 'Cannot reflect db_migrate_version from db'
-        t = metadata.tables['db_migrate_version']
-        l = t.select().where(t.c.repository_path == 'versions').execute().fetchall()
-        assert len(l) == 1, 'Cannot find a single versions entry in db_migrate_version'
-        assert l[0].repository_id.endswith('_db_migrations')
-        old = l[0].repository_id[:-len('_db_migrations')]
-        print 'Detected migration from old name %s' % old
-        if new != old:
-            assert not t.select().where(t.c.repository_id == new + '_db_migrations').execute().fetchall(), 'db_migrate_version has entries for both old and new name'
-
-    def tablename(brand, s):
-        return s if brand == 'kallithea' else (brand + '_' + s)
-    new_ui_name = tablename(new, 'ui')
-    old_ui_name = tablename(old, 'ui')
-    new_settings_name = tablename(new, 'settings')
-    old_settings_name = tablename(old, 'settings')
-
-    # Table renames using sqlalchemy-migrate (available on pypi)
-    if new_ui_name == old_ui_name:
-        print 'No renaming of %s' % new_ui_name
-    else:
-        try:
-            t = metadata.tables[old_ui_name]
-            print 'Renaming', t, 'to', new_ui_name
-            migrate.changeset.rename_table(t, new_ui_name)
-        except KeyError as e:
-            print 'Not renaming ui:', e
-
-    if new_settings_name == old_settings_name:
-        print 'No renaming of %s' % new_settings_name
-    else:
-        try:
-            t = metadata.tables[old_settings_name]
-            print 'Renaming', t, 'to', new_settings_name
-            migrate.changeset.rename_table(t, new_settings_name)
-        except KeyError as e:
-            print 'Not renaming settings:', e
-
-    old_auth_name = 'internal' if old == 'kallithea' else old
-    new_auth_name = 'internal' if new == 'kallithea' else new
-
-    # using this API because ... dunno ... it is simple and works
-    conn = metadata.bind.connect()
-    trans = conn.begin()
-    t = metadata.tables['users']
-
-    print 'Bulk fixing of User extern_name'
-    try:
-        t.c.extern_name
-    except AttributeError:
-        print 'No extern_name to rename'
-    else:
-        t.update().where(t.c.extern_name == old_auth_name).values(extern_name=new_auth_name).execute()
-
-    print 'Bulk fixing of User extern_type'
-    try:
-        t.c.extern_type
-    except AttributeError:
-        print 'No extern_type to rename'
-    else:
-        t.update().where(t.c.extern_type == old_auth_name).values(extern_type=new_auth_name).execute()
-
-    trans.commit()
-
-    # For the following conversions, use ORM ... and create stub models that works for that purpose
-    Base = sqlalchemy.ext.declarative.declarative_base()
-
-    class Ui(Base):
-        __tablename__ = new_ui_name
-        ui_id = Column("ui_id", Integer(), primary_key=True)
-        ui_section = Column("ui_section", String())
-        ui_key = Column("ui_key", String())
-        ui_value = Column("ui_value", String())
-        ui_active = Column("ui_active", Boolean())
-
-    class Setting(Base):
-        __tablename__ = new_settings_name
-        app_settings_id = Column("app_settings_id", Integer(), primary_key=True)
-        app_settings_name = Column("app_settings_name", String())
-        app_settings_value = Column("app_settings_value", String())
-        #app_settings_type = Column("app_settings_type", String()) # not present in v1.7.2
-
-    class DbMigrateVersion(Base):
-        __tablename__ = 'db_migrate_version'
-        repository_id = Column('repository_id', String(), primary_key=True)
-        repository_path = Column('repository_path', Text)
-        version = Column('version', Integer)
-
-    Session = sqlalchemy.orm.sessionmaker(bind=metadata.bind)
-    session = Session()
-
-    print 'Fixing hook names'
-
-    oldhooks = u'python:%s.lib.hooks.' % old
-    newhooks = u'python:%s.lib.hooks.' % new
-    for u in session.query(Ui).filter(Ui.ui_section == 'hooks').all():
-        if u.ui_value.startswith(oldhooks):
-            print '- fixing %s' % u.ui_key
-            u.ui_value = newhooks + u.ui_value[len(oldhooks):]
-            session.add(u)
-    session.commit()
-
-    print 'Fixing auth module names'
-    for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_plugins').all():
-        print '- fixing %s' % s.app_settings_name
-        s.app_settings_value = (s.app_settings_value
-                                .replace(old + '.lib.auth_modules.auth_', new + '.lib.auth_modules.auth_')
-                                .replace('.auth_modules.auth_' + old_auth_name, '.auth_modules.auth_' + new_auth_name))
-        session.add(s)
-    for s in session.query(Setting).filter(Setting.app_settings_name == 'auth_' + old_auth_name + '_enabled').all():
-        print '- fixing %s' % s.app_settings_name
-        s.app_settings_name = 'auth_' + new_auth_name + '_enabled'
-        session.add(s)
-    session.commit()
-
-    print 'Fixing db migration version number'
-    for s in session.query(DbMigrateVersion).filter(DbMigrateVersion.repository_id == old + '_db_migrations', DbMigrateVersion.repository_path == 'versions').all():
-        print '- fixing %s' % s.repository_id
-        s.repository_id = new + '_db_migrations'
-    session.commit()
-
-    print 'Done'
-
-def main(argv):
-    if len(argv) < 2 or argv[1] in ['-h', '--help']:
-        print 'usage: kallithea/bin/rebranddb.py DBSTRING [NEW] [OLD]'
-        print '  where DBSTRING is the value of sqlalchemy.db1.url from the .ini,'
-        print '  NEW defaults to "kallithea", OLD is by default detected from the db"'
-        raise SystemExit(0)
-    new = 'kallithea'
-    if len(argv) > 2:
-        new = argv[2]
-    old = None
-    if len(argv) > 3:
-        old = argv[3]
-    do_migrate(argv[1], old, new)
-
-if __name__ == '__main__':
-    main(sys.argv)
--- a/kallithea/bin/template.ini.mako	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,591 +0,0 @@
-## -*- coding: utf-8 -*-
-<%text>################################################################################</%text>
-<%text>################################################################################</%text>
-# Kallithea - config file generated with kallithea-config                      #
-<%text>################################################################################</%text>
-<%text>################################################################################</%text>
-
-[DEFAULT]
-debug = true
-pdebug = false
-
-<%text>################################################################################</%text>
-<%text>## Email settings                                                             ##</%text>
-<%text>##                                                                            ##</%text>
-<%text>## Refer to the documentation ("Email settings") for more details.            ##</%text>
-<%text>##                                                                            ##</%text>
-<%text>## It is recommended to use a valid sender address that passes access         ##</%text>
-<%text>## validation and spam filtering in mail servers.                             ##</%text>
-<%text>################################################################################</%text>
-
-<%text>## 'From' header for application emails. You can optionally add a name.</%text>
-<%text>## Default:</%text>
-#app_email_from = Kallithea
-<%text>## Examples:</%text>
-#app_email_from = Kallithea <kallithea-noreply@example.com>
-#app_email_from = kallithea-noreply@example.com
-
-<%text>## Subject prefix for application emails.</%text>
-<%text>## A space between this prefix and the real subject is automatically added.</%text>
-<%text>## Default:</%text>
-#email_prefix =
-<%text>## Example:</%text>
-#email_prefix = [Kallithea]
-
-<%text>## Recipients for error emails and fallback recipients of application mails.</%text>
-<%text>## Multiple addresses can be specified, space-separated.</%text>
-<%text>## Only addresses are allowed, do not add any name part.</%text>
-<%text>## Default:</%text>
-#email_to =
-<%text>## Examples:</%text>
-#email_to = admin@example.com
-#email_to = admin@example.com another_admin@example.com
-
-<%text>## 'From' header for error emails. You can optionally add a name.</%text>
-<%text>## Default:</%text>
-#error_email_from = pylons@yourapp.com
-<%text>## Examples:</%text>
-#error_email_from = Kallithea Errors <kallithea-noreply@example.com>
-#error_email_from = paste_error@example.com
-
-<%text>## SMTP server settings</%text>
-<%text>## Only smtp_server is mandatory. All other settings take the specified default</%text>
-<%text>## values.</%text>
-#smtp_server = smtp.example.com
-#smtp_username =
-#smtp_password =
-#smtp_port = 25
-#smtp_use_tls = false
-#smtp_use_ssl = false
-<%text>## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).</%text>
-<%text>## If empty, use any of the authentication parameters supported by the server.</%text>
-#smtp_auth =
-
-[server:main]
-%if http_server == 'paste':
-<%text>## PASTE ##</%text>
-use = egg:Paste#http
-<%text>## nr of worker threads to spawn</%text>
-threadpool_workers = 1
-<%text>## max request before thread respawn</%text>
-threadpool_max_requests = 100
-<%text>## option to use threads of process</%text>
-use_threadpool = true
-
-%elif http_server == 'waitress':
-<%text>## WAITRESS ##</%text>
-use = egg:waitress#main
-<%text>## number of worker threads</%text>
-threads = 1
-<%text>## MAX BODY SIZE 100GB</%text>
-max_request_body_size = 107374182400
-<%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
-<%text>## windows systems.</%text>
-#asyncore_use_poll = True
-
-%elif http_server == 'gunicorn':
-<%text>## GUNICORN ##</%text>
-use = egg:gunicorn#main
-<%text>## number of process workers. You must set `instance_id = *` when this option</%text>
-<%text>## is set to more than one worker</%text>
-workers = 1
-<%text>## process name</%text>
-proc_name = kallithea
-<%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
-<%text>## recommended for bigger setup is using of of other than sync one</%text>
-worker_class = sync
-max_requests = 1000
-<%text>## ammount of time a worker can handle request before it gets killed and</%text>
-<%text>## restarted</%text>
-timeout = 3600
-
-%elif http_server == 'uwsgi':
-<%text>## UWSGI ##</%text>
-<%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
-[uwsgi]
-socket = /tmp/uwsgi.sock
-master = true
-http = 127.0.0.1:5000
-
-<%text>## set as deamon and redirect all output to file</%text>
-#daemonize = ./uwsgi_kallithea.log
-
-<%text>## master process PID</%text>
-pidfile = ./uwsgi_kallithea.pid
-
-<%text>## stats server with workers statistics, use uwsgitop</%text>
-<%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
-stats = 127.0.0.1:1717
-memory-report = true
-
-<%text>## log 5XX errors</%text>
-log-5xx = true
-
-<%text>## Set the socket listen queue size.</%text>
-listen = 256
-
-<%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
-<%text>## (avoid memory leaks).</%text>
-max-requests = 1000
-
-<%text>## enable large buffers</%text>
-buffer-size = 65535
-
-<%text>## socket and http timeouts ##</%text>
-http-timeout = 3600
-socket-timeout = 3600
-
-<%text>## Log requests slower than the specified number of milliseconds.</%text>
-log-slow = 10
-
-<%text>## Exit if no app can be loaded.</%text>
-need-app = true
-
-<%text>## Set lazy mode (load apps in workers instead of master).</%text>
-lazy = true
-
-<%text>## scaling ##</%text>
-<%text>## set cheaper algorithm to use, if not set default will be used</%text>
-cheaper-algo = spare
-
-<%text>## minimum number of workers to keep at all times</%text>
-cheaper = 1
-
-<%text>## number of workers to spawn at startup</%text>
-cheaper-initial = 1
-
-<%text>## maximum number of workers that can be spawned</%text>
-workers = 4
-
-<%text>## how many workers should be spawned at a time</%text>
-cheaper-step = 1
-
-%endif
-<%text>## COMMON ##</%text>
-host = ${host}
-port = ${port}
-
-<%text>## middleware for hosting the WSGI application under a URL prefix</%text>
-#[filter:proxy-prefix]
-#use = egg:PasteDeploy#prefix
-#prefix = /<your-prefix>
-
-[app:main]
-use = egg:kallithea
-<%text>## enable proxy prefix middleware</%text>
-#filter-with = proxy-prefix
-
-full_stack = true
-static_files = true
-<%text>## Available Languages:</%text>
-<%text>## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW</%text>
-lang =
-cache_dir = ${here}/data
-index_dir = ${here}/data/index
-
-<%text>## perform a full repository scan on each server start, this should be</%text>
-<%text>## set to false after first startup, to allow faster server restarts.</%text>
-initial_repo_scan = false
-
-<%text>## uncomment and set this path to use archive download cache</%text>
-archive_cache_dir = ${here}/tarballcache
-
-<%text>## change this to unique ID for security</%text>
-app_instance_uuid = ${uuid()}
-
-<%text>## cut off limit for large diffs (size in bytes)</%text>
-cut_off_limit = 256000
-
-<%text>## use cache version of scm repo everywhere</%text>
-vcs_full_cache = true
-
-<%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
-force_https = false
-
-<%text>## use Strict-Transport-Security headers</%text>
-use_htsts = false
-
-<%text>## number of commits stats will parse on each iteration</%text>
-commit_parse_limit = 25
-
-<%text>## path to git executable</%text>
-git_path = git
-
-<%text>## git rev filter option, --all is the default filter, if you need to</%text>
-<%text>## hide all refs in changelog switch this to --branches --tags</%text>
-#git_rev_filter = --branches --tags
-
-<%text>## RSS feed options</%text>
-rss_cut_off_limit = 256000
-rss_items_per_page = 10
-rss_include_diff = false
-
-<%text>## options for showing and identifying changesets</%text>
-show_sha_length = 12
-show_revision_number = false
-
-<%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
-<%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
-<%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
-<%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
-gist_alias_url =
-
-<%text>## white list of API enabled controllers. This allows to add list of</%text>
-<%text>## controllers to which access will be enabled by api_key. eg: to enable</%text>
-<%text>## api access to raw_files put `FilesController:raw`, to enable access to patches</%text>
-<%text>## add `ChangesetController:changeset_patch`. This list should be "," separated</%text>
-<%text>## Syntax is <ControllerClass>:<function>. Check debug logs for generated names</%text>
-<%text>## Recommended settings below are commented out:</%text>
-api_access_controllers_whitelist =
-#    ChangesetController:changeset_patch,
-#    ChangesetController:changeset_raw,
-#    FilesController:raw,
-#    FilesController:archivefile
-
-<%text>## default encoding used to convert from and to unicode</%text>
-<%text>## can be also a comma seperated list of encoding in case of mixed encodings</%text>
-default_encoding = utf8
-
-<%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
-#bugtracker = https://bitbucket.org/conservancy/kallithea/issues
-
-<%text>## issue tracking mapping for commits messages</%text>
-<%text>## comment out issue_pat, issue_server, issue_prefix to enable</%text>
-
-<%text>## pattern to get the issues from commit messages</%text>
-<%text>## default one used here is #<numbers> with a regex passive group for `#`</%text>
-<%text>## {id} will be all groups matched from this pattern</%text>
-
-issue_pat = (?:\s*#)(\d+)
-
-<%text>## server url to the issue, each {id} will be replaced with match</%text>
-<%text>## fetched from the regex and {repo} is replaced with full repository name</%text>
-<%text>## including groups {repo_name} is replaced with just name of repo</%text>
-
-issue_server_link = https://issues.example.com/{repo}/issue/{id}
-
-<%text>## prefix to add to link to indicate it's an url</%text>
-<%text>## #314 will be replaced by <issue_prefix><id></%text>
-
-issue_prefix = #
-
-<%text>## issue_pat, issue_server_link, issue_prefix can have suffixes to specify</%text>
-<%text>## multiple patterns, to other issues server, wiki or others</%text>
-<%text>## below an example how to create a wiki pattern</%text>
-# wiki-some-id -> https://wiki.example.com/some-id
-
-#issue_pat_wiki = (?:wiki-)(.+)
-#issue_server_link_wiki = https://wiki.example.com/{id}
-#issue_prefix_wiki = WIKI-
-
-<%text>## instance-id prefix</%text>
-<%text>## a prefix key for this instance used for cache invalidation when running</%text>
-<%text>## multiple instances of kallithea, make sure it's globally unique for</%text>
-<%text>## all running kallithea instances. Leave empty if you don't use it</%text>
-instance_id =
-
-<%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
-<%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
-<%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
-auth_ret_code =
-
-<%text>## locking return code. When repository is locked return this HTTP code. 2XX</%text>
-<%text>## codes don't break the transactions while 4XX codes do</%text>
-lock_ret_code = 423
-
-<%text>## allows to change the repository location in settings page</%text>
-allow_repo_location_change = True
-
-<%text>## allows to setup custom hooks in settings page</%text>
-allow_custom_hooks_settings = True
-
-<%text>####################################</%text>
-<%text>###        CELERY CONFIG        ####</%text>
-<%text>####################################</%text>
-
-use_celery = false
-broker.host = localhost
-broker.vhost = rabbitmqhost
-broker.port = 5672
-broker.user = rabbitmq
-broker.password = qweqwe
-
-celery.imports = kallithea.lib.celerylib.tasks
-
-celery.result.backend = amqp
-celery.result.dburi = amqp://
-celery.result.serialier = json
-
-#celery.send.task.error.emails = true
-#celery.amqp.task.result.expires = 18000
-
-celeryd.concurrency = 2
-#celeryd.log.file = celeryd.log
-celeryd.log.level = DEBUG
-celeryd.max.tasks.per.child = 1
-
-<%text>## tasks will never be sent to the queue, but executed locally instead.</%text>
-celery.always.eager = false
-
-<%text>####################################</%text>
-<%text>###         BEAKER CACHE        ####</%text>
-<%text>####################################</%text>
-
-beaker.cache.data_dir = ${here}/data/cache/data
-beaker.cache.lock_dir = ${here}/data/cache/lock
-
-beaker.cache.regions = short_term,long_term,sql_cache_short
-
-beaker.cache.short_term.type = memory
-beaker.cache.short_term.expire = 60
-beaker.cache.short_term.key_length = 256
-
-beaker.cache.long_term.type = memory
-beaker.cache.long_term.expire = 36000
-beaker.cache.long_term.key_length = 256
-
-beaker.cache.sql_cache_short.type = memory
-beaker.cache.sql_cache_short.expire = 10
-beaker.cache.sql_cache_short.key_length = 256
-
-<%text>####################################</%text>
-<%text>###       BEAKER SESSION        ####</%text>
-<%text>####################################</%text>
-
-<%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
-<%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
-beaker.session.key = kallithea
-<%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
-beaker.session.httponly = true
-<%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
-beaker.session.timeout = 2592000
-
-<%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
-beaker.session.secret = ${uuid()}
-<%text>## Further, encrypt the data with AES.</%text>
-#beaker.session.encrypt_key = <key_for_encryption>
-#beaker.session.validate_key = <validation_key>
-
-<%text>## Type of storage used for the session, current types are</%text>
-<%text>## dbm, file, memcached, database, and memory.</%text>
-
-<%text>## File system storage of session data. (default)</%text>
-#beaker.session.type = file
-
-<%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
-#beaker.session.type = cookie
-
-<%text>## Database storage of session data.</%text>
-#beaker.session.type = ext:database
-#beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
-#beaker.session.table_name = db_session
-
-%if error_aggregation_service == 'appenlight':
-<%text>############################</%text>
-<%text>## ERROR HANDLING SYSTEMS ##</%text>
-<%text>############################</%text>
-
-<%text>####################</%text>
-<%text>### [appenlight] ###</%text>
-<%text>####################</%text>
-
-<%text>## AppEnlight is tailored to work with Kallithea, see</%text>
-<%text>## http://appenlight.com for details how to obtain an account</%text>
-<%text>## you must install python package `appenlight_client` to make it work</%text>
-
-<%text>## appenlight enabled</%text>
-appenlight = false
-
-appenlight.server_url = https://api.appenlight.com
-appenlight.api_key = YOUR_API_KEY
-
-<%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
-
-<%text>## enables 404 error logging (default False)</%text>
-appenlight.report_404 = false
-
-<%text>## time in seconds after request is considered being slow (default 1)</%text>
-appenlight.slow_request_time = 1
-
-<%text>## record slow requests in application</%text>
-<%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
-appenlight.slow_requests = true
-
-<%text>## enable hooking to application loggers</%text>
-#appenlight.logging = true
-
-<%text>## minimum log level for log capture</%text>
-#appenlight.logging.level = WARNING
-
-<%text>## send logs only from erroneous/slow requests</%text>
-<%text>## (saves API quota for intensive logging)</%text>
-appenlight.logging_on_error = false
-
-<%text>## list of additonal keywords that should be grabbed from environ object</%text>
-<%text>## can be string with comma separated list of words in lowercase</%text>
-<%text>## (by default client will always send following info:</%text>
-<%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
-<%text>## start with HTTP* this list be extended with additional keywords here</%text>
-appenlight.environ_keys_whitelist =
-
-<%text>## list of keywords that should be blanked from request object</%text>
-<%text>## can be string with comma separated list of words in lowercase</%text>
-<%text>## (by default client will always blank keys that contain following words</%text>
-<%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
-<%text>## this list be extended with additional keywords set here</%text>
-appenlight.request_keys_blacklist =
-
-<%text>## list of namespaces that should be ignores when gathering log entries</%text>
-<%text>## can be string with comma separated list of namespaces</%text>
-<%text>## (by default the client ignores own entries: appenlight_client.client)</%text>
-appenlight.log_namespace_blacklist =
-
-%elif error_aggregation_service == 'sentry':
-<%text>################</%text>
-<%text>### [sentry] ###</%text>
-<%text>################</%text>
-
-<%text>## sentry is a alternative open source error aggregator</%text>
-<%text>## you must install python packages `sentry` and `raven` to enable</%text>
-
-sentry.dsn = YOUR_DNS
-sentry.servers =
-sentry.name =
-sentry.key =
-sentry.public_key =
-sentry.secret_key =
-sentry.project =
-sentry.site =
-sentry.include_paths =
-sentry.exclude_paths =
-
-%endif
-<%text>################################################################################</%text>
-<%text>## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##</%text>
-<%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##</%text>
-<%text>## execute malicious code after an exception is raised.                       ##</%text>
-<%text>################################################################################</%text>
-set debug = false
-
-<%text>##################################</%text>
-<%text>###       LOGVIEW CONFIG       ###</%text>
-<%text>##################################</%text>
-
-logview.sqlalchemy = #faa
-logview.pylons.templating = #bfb
-logview.pylons.util = #eee
-
-<%text>#########################################################</%text>
-<%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###</%text>
-<%text>#########################################################</%text>
-
-%if database_engine == 'sqlite':
-# SQLITE [default]
-sqlalchemy.db1.url = sqlite:///${here}/kallithea.db?timeout=60
-
-%elif database_engine == 'postgres':
-# POSTGRESQL
-sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
-
-%elif database_engine == 'mysql':
-# MySQL
-sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea
-
-%endif
-# see sqlalchemy docs for others
-
-sqlalchemy.db1.echo = false
-sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.db1.convert_unicode = true
-
-<%text>################################</%text>
-<%text>### LOGGING CONFIGURATION   ####</%text>
-<%text>################################</%text>
-
-[loggers]
-keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
-
-[handlers]
-keys = console, console_sql
-
-[formatters]
-keys = generic, color_formatter, color_formatter_sql
-
-<%text>#############</%text>
-<%text>## LOGGERS ##</%text>
-<%text>#############</%text>
-
-[logger_root]
-level = NOTSET
-handlers = console
-
-[logger_routes]
-level = DEBUG
-handlers =
-qualname = routes.middleware
-<%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
-propagate = 1
-
-[logger_beaker]
-level = DEBUG
-handlers =
-qualname = beaker.container
-propagate = 1
-
-[logger_templates]
-level = INFO
-handlers =
-qualname = pylons.templating
-propagate = 1
-
-[logger_kallithea]
-level = DEBUG
-handlers =
-qualname = kallithea
-propagate = 1
-
-[logger_sqlalchemy]
-level = INFO
-handlers = console_sql
-qualname = sqlalchemy.engine
-propagate = 0
-
-[logger_whoosh_indexer]
-level = DEBUG
-handlers =
-qualname = whoosh_indexer
-propagate = 1
-
-<%text>##############</%text>
-<%text>## HANDLERS ##</%text>
-<%text>##############</%text>
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = INFO
-formatter = generic
-
-[handler_console_sql]
-class = StreamHandler
-args = (sys.stderr,)
-level = WARN
-formatter = generic
-
-<%text>################</%text>
-<%text>## FORMATTERS ##</%text>
-<%text>################</%text>
-
-[formatter_generic]
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter]
-class = kallithea.lib.colored_formatter.ColorFormatter
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter_sql]
-class = kallithea.lib.colored_formatter.ColorFormatterSql
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/app_cfg.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,214 @@
+# -*- 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/>.
+"""
+Global configuration file for TurboGears2 specific settings in Kallithea.
+
+This file complements the .ini file.
+"""
+
+import platform
+import os, sys, logging
+
+import tg
+from tg import hooks
+from tg.configuration import AppConfig
+from tg.support.converters import asbool
+import alembic.config
+from alembic.script.base import ScriptDirectory
+from alembic.migration import MigrationContext
+from sqlalchemy import create_engine
+import mercurial
+
+from kallithea.lib.middleware.https_fixup import HttpsFixup
+from kallithea.lib.middleware.simplegit import SimpleGit
+from kallithea.lib.middleware.simplehg import SimpleHg
+from kallithea.lib.auth import set_available_permissions
+from kallithea.lib.utils import load_rcextensions, make_ui, set_app_settings, set_vcs_config, \
+    set_indexer_config, check_git_version, repo2db_mapper
+from kallithea.lib.utils2 import str2bool
+import kallithea.model.base
+from kallithea.model.scm import ScmModel
+
+import formencode
+
+log = logging.getLogger(__name__)
+
+
+class KallitheaAppConfig(AppConfig):
+    # Note: AppConfig has a misleading name, as it's not the application
+    # configuration, but the application configurator. The AppConfig values are
+    # used as a template to create the actual configuration, which might
+    # overwrite or extend the one provided by the configurator template.
+
+    # To make it clear, AppConfig creates the config and sets into it the same
+    # values that AppConfig itself has. Then the values from the config file and
+    # gearbox options are loaded and merged into the configuration. Then an
+    # after_init_config(conf) method of AppConfig is called for any change that
+    # might depend on options provided by configuration files.
+
+    def __init__(self):
+        super(KallitheaAppConfig, self).__init__()
+
+        self['package'] = kallithea
+
+        self['prefer_toscawidgets2'] = False
+        self['use_toscawidgets'] = False
+
+        self['renderers'] = []
+
+        # Enable json in expose
+        self['renderers'].append('json')
+
+        # Configure template rendering
+        self['renderers'].append('mako')
+        self['default_renderer'] = 'mako'
+        self['use_dotted_templatenames'] = False
+
+        # Configure Sessions, store data as JSON to avoid pickle security issues
+        self['session.enabled'] = True
+        self['session.data_serializer'] = 'json'
+
+        # Configure the base SQLALchemy Setup
+        self['use_sqlalchemy'] = True
+        self['model'] = kallithea.model.base
+        self['DBSession'] = kallithea.model.meta.Session
+
+        # Configure App without an authentication backend.
+        self['auth_backend'] = None
+
+        # Use custom error page for these errors. By default, Turbogears2 does not add
+        # 400 in this list.
+        # Explicitly listing all is considered more robust than appending to defaults,
+        # in light of possible future framework changes.
+        self['errorpage.status_codes'] = [400, 401, 403, 404]
+
+        # Disable transaction manager -- currently Kallithea takes care of transactions itself
+        self['tm.enabled'] = False
+
+
+base_config = KallitheaAppConfig()
+
+# TODO still needed as long as we use pylonslib
+sys.modules['pylons'] = tg
+
+# DebugBar, a debug toolbar for TurboGears2.
+# (https://github.com/TurboGears/tgext.debugbar)
+# To enable it, install 'tgext.debugbar' and 'kajiki', and run Kallithea with
+# 'debug = true' (not in production!)
+# See the Kallithea documentation for more information.
+try:
+    from tgext.debugbar import enable_debugbar
+    import kajiki # only to check its existence
+except ImportError:
+    pass
+else:
+    base_config['renderers'].append('kajiki')
+    enable_debugbar(base_config)
+
+
+def setup_configuration(app):
+    config = app.config
+
+    # Verify that things work when Dulwich passes unicode paths to the file system layer.
+    # Note: UTF-8 is preferred, but for example ISO-8859-1 or mbcs should also work under the right cirumstances.
+    try:
+        u'\xe9'.encode(sys.getfilesystemencoding()) # Test using é (&eacute;)
+    except UnicodeEncodeError:
+        log.error("Cannot encode Unicode paths to file system encoding %r", sys.getfilesystemencoding())
+        for var in ['LC_CTYPE', 'LC_ALL', 'LANG']:
+            if var in os.environ:
+                val = os.environ[var]
+                log.error("Note: Environment variable %s is %r - perhaps change it to some other value from 'locale -a', like 'C.UTF-8' or 'en_US.UTF-8'", var, val)
+                break
+        else:
+            log.error("Note: No locale setting found in environment variables - perhaps set LC_CTYPE to some value from 'locale -a', like 'C.UTF-8' or 'en_US.UTF-8'")
+        log.error("Terminating ...")
+        sys.exit(1)
+
+    # Mercurial sets encoding at module import time, so we have to monkey patch it
+    hgencoding = config.get('hgencoding')
+    if hgencoding:
+        mercurial.encoding.encoding = hgencoding
+
+    if config.get('ignore_alembic_revision', False):
+        log.warn('database alembic revision checking is disabled')
+    else:
+        dbconf = config['sqlalchemy.url']
+        alembic_cfg = alembic.config.Config()
+        alembic_cfg.set_main_option('script_location', 'kallithea:alembic')
+        alembic_cfg.set_main_option('sqlalchemy.url', dbconf)
+        script_dir = ScriptDirectory.from_config(alembic_cfg)
+        available_heads = sorted(script_dir.get_heads())
+
+        engine = create_engine(dbconf)
+        with engine.connect() as conn:
+            context = MigrationContext.configure(conn)
+            current_heads = sorted(str(s) for s in context.get_current_heads())
+        if current_heads != available_heads:
+            log.error('Failed to run Kallithea:\n\n'
+                      'The database version does not match the Kallithea version.\n'
+                      'Please read the documentation on how to upgrade or downgrade the database.\n'
+                      'Current database version id(s): %s\n'
+                      'Expected database version id(s): %s\n'
+                      'If you are a developer and you know what you are doing, you can add `ignore_alembic_revision = True` '
+                      'to your .ini file to skip the check.\n' % (' '.join(current_heads), ' '.join(available_heads)))
+            sys.exit(1)
+
+    # store some globals into kallithea
+    kallithea.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
+    kallithea.CELERY_EAGER = str2bool(config['app_conf'].get('celery.always.eager'))
+    kallithea.CONFIG = config
+
+    load_rcextensions(root_path=config['here'])
+
+    set_available_permissions(config)
+    repos_path = make_ui('db').configitems('paths')[0][1]
+    config['base_path'] = repos_path
+    set_app_settings(config)
+
+    instance_id = kallithea.CONFIG.get('instance_id', '*')
+    if instance_id == '*':
+        instance_id = '%s-%s' % (platform.uname()[1], os.getpid())
+        kallithea.CONFIG['instance_id'] = instance_id
+
+    # update kallithea.CONFIG with the meanwhile changed 'config'
+    kallithea.CONFIG.update(config)
+
+    # configure vcs and indexer libraries (they are supposed to be independent
+    # as much as possible and thus avoid importing tg.config or
+    # kallithea.CONFIG).
+    set_vcs_config(kallithea.CONFIG)
+    set_indexer_config(kallithea.CONFIG)
+
+    check_git_version()
+
+
+hooks.register('configure_new_app', setup_configuration)
+
+
+def setup_application(app):
+    config = app.config
+
+    # we want our low level middleware to get to the request ASAP. We don't
+    # need any stack middleware in them - especially no StatusCodeRedirect buffering
+    app = SimpleHg(app, config)
+    app = SimpleGit(app, config)
+
+    # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy
+    if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']):
+        app = HttpsFixup(app, config)
+    return app
+
+
+hooks.register('before_config', setup_application)
--- a/kallithea/config/conf.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/conf.py	Sun Mar 31 21:28:56 2019 +0200
@@ -25,19 +25,21 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-from kallithea.lib.utils2 import __get_lem
+from kallithea.lib import pygmentsutils
 
 
 # language map is also used by whoosh indexer, which for those specified
 # extensions will index it's content
-LANGUAGES_EXTENSIONS_MAP = __get_lem()
+LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions()
+
+# Whoosh index targets
 
-#==============================================================================
-# WHOOSH INDEX EXTENSIONS
-#==============================================================================
-# EXTENSIONS WE WANT TO INDEX CONTENT OFF USING WHOOSH
+# Extensions we want to index content of using whoosh
 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
 
+# Filenames we want to index content of using whoosh
+INDEX_FILENAMES = pygmentsutils.get_index_filenames()
+
 # list of readme files to search in file tree and display in summary
 # attached weights defines the search  order lower is first
 ALL_READMES = [
@@ -65,7 +67,3 @@
 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
 
 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
-
-DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-DATE_FORMAT = "%Y-%m-%d"
--- a/kallithea/config/deployment.ini_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,580 +0,0 @@
-################################################################################
-################################################################################
-# Kallithea - Example config                                                   #
-#                                                                              #
-# The %(here)s variable will be replaced with the parent directory of this file#
-################################################################################
-################################################################################
-
-[DEFAULT]
-debug = true
-pdebug = false
-
-################################################################################
-## Email settings                                                             ##
-##                                                                            ##
-## Refer to the documentation ("Email settings") for more details.            ##
-##                                                                            ##
-## It is recommended to use a valid sender address that passes access         ##
-## validation and spam filtering in mail servers.                             ##
-################################################################################
-
-## 'From' header for application emails. You can optionally add a name.
-## Default:
-#app_email_from = Kallithea
-## Examples:
-#app_email_from = Kallithea <kallithea-noreply@example.com>
-#app_email_from = kallithea-noreply@example.com
-
-## Subject prefix for application emails.
-## A space between this prefix and the real subject is automatically added.
-## Default:
-#email_prefix =
-## Example:
-#email_prefix = [Kallithea]
-
-## Recipients for error emails and fallback recipients of application mails.
-## Multiple addresses can be specified, space-separated.
-## Only addresses are allowed, do not add any name part.
-## Default:
-#email_to =
-## Examples:
-#email_to = admin@example.com
-#email_to = admin@example.com another_admin@example.com
-
-## 'From' header for error emails. You can optionally add a name.
-## Default:
-#error_email_from = pylons@yourapp.com
-## Examples:
-#error_email_from = Kallithea Errors <kallithea-noreply@example.com>
-#error_email_from = paste_error@example.com
-
-## SMTP server settings
-## Only smtp_server is mandatory. All other settings take the specified default
-## values.
-#smtp_server = smtp.example.com
-#smtp_username =
-#smtp_password =
-#smtp_port = 25
-#smtp_use_tls = false
-#smtp_use_ssl = false
-## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
-## If empty, use any of the authentication parameters supported by the server.
-#smtp_auth =
-
-[server:main]
-## PASTE ##
-#use = egg:Paste#http
-## nr of worker threads to spawn
-#threadpool_workers = 1
-## max request before thread respawn
-#threadpool_max_requests = 100
-## option to use threads of process
-#use_threadpool = true
-
-## WAITRESS ##
-use = egg:waitress#main
-## number of worker threads
-threads = 1
-## MAX BODY SIZE 100GB
-max_request_body_size = 107374182400
-## use poll instead of select, fixes fd limits, may not work on old
-## windows systems.
-#asyncore_use_poll = True
-
-## GUNICORN ##
-#use = egg:gunicorn#main
-## number of process workers. You must set `instance_id = *` when this option
-## is set to more than one worker
-#workers = 1
-## process name
-#proc_name = kallithea
-## type of worker class, one of sync, eventlet, gevent, tornado
-## recommended for bigger setup is using of of other than sync one
-#worker_class = sync
-#max_requests = 1000
-## ammount of time a worker can handle request before it gets killed and
-## restarted
-#timeout = 3600
-
-## UWSGI ##
-## run with uwsgi --ini-paste-logged <inifile.ini>
-#[uwsgi]
-#socket = /tmp/uwsgi.sock
-#master = true
-#http = 127.0.0.1:5000
-
-## set as deamon and redirect all output to file
-#daemonize = ./uwsgi_kallithea.log
-
-## master process PID
-#pidfile = ./uwsgi_kallithea.pid
-
-## stats server with workers statistics, use uwsgitop
-## for monitoring, `uwsgitop 127.0.0.1:1717`
-#stats = 127.0.0.1:1717
-#memory-report = true
-
-## log 5XX errors
-#log-5xx = true
-
-## Set the socket listen queue size.
-#listen = 256
-
-## Gracefully Reload workers after the specified amount of managed requests
-## (avoid memory leaks).
-#max-requests = 1000
-
-## enable large buffers
-#buffer-size = 65535
-
-## socket and http timeouts ##
-#http-timeout = 3600
-#socket-timeout = 3600
-
-## Log requests slower than the specified number of milliseconds.
-#log-slow = 10
-
-## Exit if no app can be loaded.
-#need-app = true
-
-## Set lazy mode (load apps in workers instead of master).
-#lazy = true
-
-## scaling ##
-## set cheaper algorithm to use, if not set default will be used
-#cheaper-algo = spare
-
-## minimum number of workers to keep at all times
-#cheaper = 1
-
-## number of workers to spawn at startup
-#cheaper-initial = 1
-
-## maximum number of workers that can be spawned
-#workers = 4
-
-## how many workers should be spawned at a time
-#cheaper-step = 1
-
-## COMMON ##
-host = 127.0.0.1
-port = 5000
-
-## middleware for hosting the WSGI application under a URL prefix
-#[filter:proxy-prefix]
-#use = egg:PasteDeploy#prefix
-#prefix = /<your-prefix>
-
-[app:main]
-use = egg:kallithea
-## enable proxy prefix middleware
-#filter-with = proxy-prefix
-
-full_stack = true
-static_files = true
-## Available Languages:
-## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
-lang =
-cache_dir = %(here)s/data
-index_dir = %(here)s/data/index
-
-## perform a full repository scan on each server start, this should be
-## set to false after first startup, to allow faster server restarts.
-initial_repo_scan = false
-
-## uncomment and set this path to use archive download cache
-archive_cache_dir = %(here)s/tarballcache
-
-## change this to unique ID for security
-app_instance_uuid = ${app_instance_uuid}
-
-## cut off limit for large diffs (size in bytes)
-cut_off_limit = 256000
-
-## use cache version of scm repo everywhere
-vcs_full_cache = true
-
-## force https in Kallithea, fixes https redirects, assumes it's always https
-force_https = false
-
-## use Strict-Transport-Security headers
-use_htsts = false
-
-## number of commits stats will parse on each iteration
-commit_parse_limit = 25
-
-## path to git executable
-git_path = git
-
-## git rev filter option, --all is the default filter, if you need to
-## hide all refs in changelog switch this to --branches --tags
-#git_rev_filter = --branches --tags
-
-## RSS feed options
-rss_cut_off_limit = 256000
-rss_items_per_page = 10
-rss_include_diff = false
-
-## options for showing and identifying changesets
-show_sha_length = 12
-show_revision_number = false
-
-## gist URL alias, used to create nicer urls for gist. This should be an
-## url that does rewrites to _admin/gists/<gistid>.
-## example: http://gist.example.com/{gistid}. Empty means use the internal
-## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
-gist_alias_url =
-
-## white list of API enabled controllers. This allows to add list of
-## controllers to which access will be enabled by api_key. eg: to enable
-## api access to raw_files put `FilesController:raw`, to enable access to patches
-## add `ChangesetController:changeset_patch`. This list should be "," separated
-## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
-## Recommended settings below are commented out:
-api_access_controllers_whitelist =
-#    ChangesetController:changeset_patch,
-#    ChangesetController:changeset_raw,
-#    FilesController:raw,
-#    FilesController:archivefile
-
-## default encoding used to convert from and to unicode
-## can be also a comma seperated list of encoding in case of mixed encodings
-default_encoding = utf8
-
-## issue tracker for Kallithea (leave blank to disable, absent for default)
-#bugtracker = https://bitbucket.org/conservancy/kallithea/issues
-
-## issue tracking mapping for commits messages
-## comment out issue_pat, issue_server, issue_prefix to enable
-
-## pattern to get the issues from commit messages
-## default one used here is #<numbers> with a regex passive group for `#`
-## {id} will be all groups matched from this pattern
-
-issue_pat = (?:\s*#)(\d+)
-
-## server url to the issue, each {id} will be replaced with match
-## fetched from the regex and {repo} is replaced with full repository name
-## including groups {repo_name} is replaced with just name of repo
-
-issue_server_link = https://issues.example.com/{repo}/issue/{id}
-
-## prefix to add to link to indicate it's an url
-## #314 will be replaced by <issue_prefix><id>
-
-issue_prefix = #
-
-## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
-## multiple patterns, to other issues server, wiki or others
-## below an example how to create a wiki pattern
-# wiki-some-id -> https://wiki.example.com/some-id
-
-#issue_pat_wiki = (?:wiki-)(.+)
-#issue_server_link_wiki = https://wiki.example.com/{id}
-#issue_prefix_wiki = WIKI-
-
-## instance-id prefix
-## a prefix key for this instance used for cache invalidation when running
-## multiple instances of kallithea, make sure it's globally unique for
-## all running kallithea instances. Leave empty if you don't use it
-instance_id =
-
-## alternative return HTTP header for failed authentication. Default HTTP
-## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
-## handling that. Set this variable to 403 to return HTTPForbidden
-auth_ret_code =
-
-## locking return code. When repository is locked return this HTTP code. 2XX
-## codes don't break the transactions while 4XX codes do
-lock_ret_code = 423
-
-## allows to change the repository location in settings page
-allow_repo_location_change = True
-
-## allows to setup custom hooks in settings page
-allow_custom_hooks_settings = True
-
-####################################
-###        CELERY CONFIG        ####
-####################################
-
-use_celery = false
-broker.host = localhost
-broker.vhost = rabbitmqhost
-broker.port = 5672
-broker.user = rabbitmq
-broker.password = qweqwe
-
-celery.imports = kallithea.lib.celerylib.tasks
-
-celery.result.backend = amqp
-celery.result.dburi = amqp://
-celery.result.serialier = json
-
-#celery.send.task.error.emails = true
-#celery.amqp.task.result.expires = 18000
-
-celeryd.concurrency = 2
-#celeryd.log.file = celeryd.log
-celeryd.log.level = DEBUG
-celeryd.max.tasks.per.child = 1
-
-## tasks will never be sent to the queue, but executed locally instead.
-celery.always.eager = false
-
-####################################
-###         BEAKER CACHE        ####
-####################################
-
-beaker.cache.data_dir = %(here)s/data/cache/data
-beaker.cache.lock_dir = %(here)s/data/cache/lock
-
-beaker.cache.regions = short_term,long_term,sql_cache_short
-
-beaker.cache.short_term.type = memory
-beaker.cache.short_term.expire = 60
-beaker.cache.short_term.key_length = 256
-
-beaker.cache.long_term.type = memory
-beaker.cache.long_term.expire = 36000
-beaker.cache.long_term.key_length = 256
-
-beaker.cache.sql_cache_short.type = memory
-beaker.cache.sql_cache_short.expire = 10
-beaker.cache.sql_cache_short.key_length = 256
-
-####################################
-###       BEAKER SESSION        ####
-####################################
-
-## Name of session cookie. Should be unique for a given host and path, even when running
-## on different ports. Otherwise, cookie sessions will be shared and messed up.
-beaker.session.key = kallithea
-## Sessions should always only be accessible by the browser, not directly by JavaScript.
-beaker.session.httponly = true
-## Session lifetime. 2592000 seconds is 30 days.
-beaker.session.timeout = 2592000
-
-## Server secret used with HMAC to ensure integrity of cookies.
-beaker.session.secret = ${app_instance_uuid}
-## Further, encrypt the data with AES.
-#beaker.session.encrypt_key = <key_for_encryption>
-#beaker.session.validate_key = <validation_key>
-
-## Type of storage used for the session, current types are
-## dbm, file, memcached, database, and memory.
-
-## File system storage of session data. (default)
-#beaker.session.type = file
-
-## Cookie only, store all session data inside the cookie. Requires secure secrets.
-#beaker.session.type = cookie
-
-## Database storage of session data.
-#beaker.session.type = ext:database
-#beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
-#beaker.session.table_name = db_session
-
-############################
-## ERROR HANDLING SYSTEMS ##
-############################
-
-####################
-### [appenlight] ###
-####################
-
-## AppEnlight is tailored to work with Kallithea, see
-## http://appenlight.com for details how to obtain an account
-## you must install python package `appenlight_client` to make it work
-
-## appenlight enabled
-appenlight = false
-
-appenlight.server_url = https://api.appenlight.com
-appenlight.api_key = YOUR_API_KEY
-
-## TWEAK AMOUNT OF INFO SENT HERE
-
-## enables 404 error logging (default False)
-appenlight.report_404 = false
-
-## time in seconds after request is considered being slow (default 1)
-appenlight.slow_request_time = 1
-
-## record slow requests in application
-## (needs to be enabled for slow datastore recording and time tracking)
-appenlight.slow_requests = true
-
-## enable hooking to application loggers
-#appenlight.logging = true
-
-## minimum log level for log capture
-#appenlight.logging.level = WARNING
-
-## send logs only from erroneous/slow requests
-## (saves API quota for intensive logging)
-appenlight.logging_on_error = false
-
-## list of additonal keywords that should be grabbed from environ object
-## can be string with comma separated list of words in lowercase
-## (by default client will always send following info:
-## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
-## start with HTTP* this list be extended with additional keywords here
-appenlight.environ_keys_whitelist =
-
-## list of keywords that should be blanked from request object
-## can be string with comma separated list of words in lowercase
-## (by default client will always blank keys that contain following words
-## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
-## this list be extended with additional keywords set here
-appenlight.request_keys_blacklist =
-
-## list of namespaces that should be ignores when gathering log entries
-## can be string with comma separated list of namespaces
-## (by default the client ignores own entries: appenlight_client.client)
-appenlight.log_namespace_blacklist =
-
-################
-### [sentry] ###
-################
-
-## sentry is a alternative open source error aggregator
-## you must install python packages `sentry` and `raven` to enable
-
-sentry.dsn = YOUR_DNS
-sentry.servers =
-sentry.name =
-sentry.key =
-sentry.public_key =
-sentry.secret_key =
-sentry.project =
-sentry.site =
-sentry.include_paths =
-sentry.exclude_paths =
-
-################################################################################
-## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
-## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
-## execute malicious code after an exception is raised.                       ##
-################################################################################
-set debug = false
-
-##################################
-###       LOGVIEW CONFIG       ###
-##################################
-
-logview.sqlalchemy = #faa
-logview.pylons.templating = #bfb
-logview.pylons.util = #eee
-
-#########################################################
-### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
-#########################################################
-
-# SQLITE [default]
-sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
-
-# POSTGRESQL
-#sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
-
-# MySQL
-#sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea
-
-# see sqlalchemy docs for others
-
-sqlalchemy.db1.echo = false
-sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.db1.convert_unicode = true
-
-################################
-### LOGGING CONFIGURATION   ####
-################################
-
-[loggers]
-keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
-
-[handlers]
-keys = console, console_sql
-
-[formatters]
-keys = generic, color_formatter, color_formatter_sql
-
-#############
-## LOGGERS ##
-#############
-
-[logger_root]
-level = NOTSET
-handlers = console
-
-[logger_routes]
-level = DEBUG
-handlers =
-qualname = routes.middleware
-## "level = DEBUG" logs the route matched and routing variables.
-propagate = 1
-
-[logger_beaker]
-level = DEBUG
-handlers =
-qualname = beaker.container
-propagate = 1
-
-[logger_templates]
-level = INFO
-handlers =
-qualname = pylons.templating
-propagate = 1
-
-[logger_kallithea]
-level = DEBUG
-handlers =
-qualname = kallithea
-propagate = 1
-
-[logger_sqlalchemy]
-level = INFO
-handlers = console_sql
-qualname = sqlalchemy.engine
-propagate = 0
-
-[logger_whoosh_indexer]
-level = DEBUG
-handlers =
-qualname = whoosh_indexer
-propagate = 1
-
-##############
-## HANDLERS ##
-##############
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = INFO
-formatter = generic
-
-[handler_console_sql]
-class = StreamHandler
-args = (sys.stderr,)
-level = WARN
-formatter = generic
-
-################
-## FORMATTERS ##
-################
-
-[formatter_generic]
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter]
-class = kallithea.lib.colored_formatter.ColorFormatter
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter_sql]
-class = kallithea.lib.colored_formatter.ColorFormatterSql
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
--- a/kallithea/config/environment.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/environment.py	Sun Mar 31 21:28:56 2019 +0200
@@ -11,130 +11,11 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-    Pylons environment configuration
-"""
-
-import os
-import logging
-import kallithea
-import platform
-
-import pylons
-import mako.lookup
-import beaker
-
-# don't remove this import it does magic for celery
-from kallithea.lib import celerypylons
-
-import kallithea.lib.app_globals as app_globals
-
-from kallithea.config.routing import make_map
-
-from kallithea.lib import helpers
-from kallithea.lib.auth import set_available_permissions
-from kallithea.lib.utils import repo2db_mapper, make_ui, set_app_settings,\
-    load_rcextensions, check_git_version, set_vcs_config
-from kallithea.lib.utils2 import engine_from_config, str2bool
-from kallithea.lib.db_manage import DbManage
-from kallithea.model import init_model
-from kallithea.model.scm import ScmModel
-
-log = logging.getLogger(__name__)
+"""WSGI environment setup for Kallithea."""
 
-
-def load_environment(global_conf, app_conf, initial=False,
-                     test_env=None, test_index=None):
-    """
-    Configure the Pylons environment via the ``pylons.config``
-    object
-    """
-    config = pylons.configuration.PylonsConfig()
-
-    # Pylons paths
-    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-    paths = dict(
-        root=root,
-        controllers=os.path.join(root, 'controllers'),
-        static_files=os.path.join(root, 'public'),
-        templates=[os.path.join(root, 'templates')]
-    )
-
-    # Initialize config with the basic options
-    config.init_app(global_conf, app_conf, package='kallithea', paths=paths)
-
-    # store some globals into kallithea
-    kallithea.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
-    kallithea.CELERY_EAGER = str2bool(config['app_conf'].get('celery.always.eager'))
-
-    config['routes.map'] = make_map(config)
-    config['pylons.app_globals'] = app_globals.Globals(config)
-    config['pylons.h'] = helpers
-    kallithea.CONFIG = config
-
-    load_rcextensions(root_path=config['here'])
+from kallithea.config.app_cfg import base_config
 
-    # Setup cache object as early as possible
-    pylons.cache._push_object(config['pylons.app_globals'].cache)
-
-    # Create the Mako TemplateLookup, with the default auto-escaping
-    config['pylons.app_globals'].mako_lookup = mako.lookup.TemplateLookup(
-        directories=paths['templates'],
-        error_handler=pylons.error.handle_mako_error,
-        module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
-        input_encoding='utf-8', default_filters=['escape'],
-        imports=['from webhelpers.html import escape'])
-
-    # sets the c attribute access when don't existing attribute are accessed
-    config['pylons.strict_tmpl_context'] = True
-    test = os.path.split(config['__file__'])[-1] == 'test.ini'
-    if test:
-        if test_env is None:
-            test_env = not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0))
-        if test_index is None:
-            test_index = not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0))
-        if os.environ.get('TEST_DB'):
-            # swap config if we pass enviroment variable
-            config['sqlalchemy.db1.url'] = os.environ.get('TEST_DB')
+__all__ = ['load_environment']
 
-        from kallithea.lib.utils import create_test_env, create_test_index
-        from kallithea.tests import TESTS_TMP_PATH
-        #set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and
-        #test repos
-        if test_env:
-            create_test_env(TESTS_TMP_PATH, config)
-        #set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
-        if test_index:
-            create_test_index(TESTS_TMP_PATH, config, True)
-
-    DbManage.check_waitress()
-    # MULTIPLE DB configs
-    # Setup the SQLAlchemy database engine
-    sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
-    init_model(sa_engine_db1)
-
-    set_available_permissions(config)
-    repos_path = make_ui('db').configitems('paths')[0][1]
-    config['base_path'] = repos_path
-    set_app_settings(config)
-
-    instance_id = kallithea.CONFIG.get('instance_id')
-    if instance_id == '*':
-        instance_id = '%s-%s' % (platform.uname()[1], os.getpid())
-        kallithea.CONFIG['instance_id'] = instance_id
-
-    # CONFIGURATION OPTIONS HERE (note: all config options will override
-    # any Pylons config options)
-
-    # store config reference into our module to skip import magic of
-    # pylons
-    kallithea.CONFIG.update(config)
-    set_vcs_config(kallithea.CONFIG)
-
-    #check git version
-    check_git_version()
-
-    if str2bool(config.get('initial_repo_scan', True)):
-        repo2db_mapper(ScmModel().repo_scan(repos_path),
-                       remove_obsolete=False, install_git_hooks=False)
-    return config
+# Use base_config to setup the environment loader function
+load_environment = base_config.make_load_environment()
--- a/kallithea/config/middleware.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/middleware.py	Sun Mar 31 21:28:56 2019 +0200
@@ -11,102 +11,41 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-    Pylons middleware initialization
-"""
+"""WSGI middleware initialization for the Kallithea application."""
+
+import logging.config
+from kallithea.config.app_cfg import base_config
+from kallithea.config.environment import load_environment
+
+__all__ = ['make_app']
 
-from routes.middleware import RoutesMiddleware
-from paste.cascade import Cascade
-from paste.registry import RegistryManager
-from paste.urlparser import StaticURLParser
-from paste.deploy.converters import asbool
-from paste.gzipper import make_gzip_middleware
+# Use base_config to setup the necessary PasteDeploy application factory.
+# make_base_app will wrap the TurboGears2 app with all the middleware it needs.
+make_base_app = base_config.setup_tg_wsgi_app(load_environment)
 
-from pylons.middleware import ErrorHandler, StatusCodeRedirect
-from pylons.wsgiapp import PylonsApp
 
-from kallithea.lib.middleware.simplehg import SimpleHg
-from kallithea.lib.middleware.simplegit import SimpleGit
-from kallithea.lib.middleware.https_fixup import HttpsFixup
-from kallithea.lib.middleware.sessionmiddleware import SecureSessionMiddleware
-from kallithea.config.environment import load_environment
-from kallithea.lib.middleware.wrapper import RequestWrapper
+def make_app_without_logging(global_conf, full_stack=True, **app_conf):
+    """The core of make_app for use from gearbox commands (other than 'serve')"""
+    return make_base_app(global_conf, full_stack=full_stack, **app_conf)
 
 
-def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
-    """Create a Pylons WSGI application and return it
-
-    ``global_conf``
-        The inherited configuration for this application. Normally from
-        the [DEFAULT] section of the Paste ini file.
-
-    ``full_stack``
-        Whether or not this application provides a full WSGI stack (by
-        default, meaning it handles its own exceptions and errors).
-        Disable full_stack when this application is "managed" by
-        another WSGI middleware.
-
-    ``app_conf``
-        The application's local configuration. Normally specified in
-        the [app:<name>] section of the Paste ini file (where <name>
-        defaults to main).
-
+def make_app(global_conf, full_stack=True, **app_conf):
     """
-    # Configure the Pylons environment
-    config = load_environment(global_conf, app_conf)
-
-    # The Pylons WSGI app
-    app = PylonsApp(config=config)
-
-    # Routing/Session/Cache Middleware
-    app = RoutesMiddleware(app, config['routes.map'])
-    app = SecureSessionMiddleware(app, config)
-
-    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
-    if asbool(config['pdebug']):
-        from kallithea.lib.profiler import ProfilingMiddleware
-        app = ProfilingMiddleware(app)
-
-    if asbool(full_stack):
+    Set up Kallithea with the settings found in the PasteDeploy configuration
+    file used.
 
-        from kallithea.lib.middleware.sentry import Sentry
-        from kallithea.lib.middleware.appenlight import AppEnlight
-        if AppEnlight and asbool(config['app_conf'].get('appenlight')):
-            app = AppEnlight(app, config)
-        elif Sentry:
-            app = Sentry(app, config)
-
-        # Handle Python exceptions
-        app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
-
-        # Display error documents for 401, 403, 404 status codes (and
-        # 500 when debug is disabled)
-        # Note: will buffer the output in memory!
-        if asbool(config['debug']):
-            app = StatusCodeRedirect(app)
-        else:
-            app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
+    :param global_conf: The global settings for Kallithea (those
+        defined under the ``[DEFAULT]`` section).
+    :type global_conf: dict
+    :param full_stack: Should the whole TurboGears2 stack be set up?
+    :type full_stack: str or bool
+    :return: The Kallithea application with all the relevant middleware
+        loaded.
 
-        # we want our low level middleware to get to the request ASAP. We don't
-        # need any pylons stack middleware in them - especially no StatusCodeRedirect buffering
-        app = SimpleHg(app, config)
-        app = SimpleGit(app, config)
-
-        # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy
-        if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']):
-            app = HttpsFixup(app, config)
-
-        app = RequestWrapper(app, config) # logging
+    This is the PasteDeploy factory for the Kallithea application.
 
-    # Establish the Registry for this application
-    app = RegistryManager(app) # thread / request-local module globals / variables
-
-    if asbool(static_files):
-        # Serve static files
-        static_app = StaticURLParser(config['pylons.paths']['static_files'])
-        app = Cascade([static_app, app])
-        app = make_gzip_middleware(app, global_conf, compress_level=1)
-
-    app.config = config
-
-    return app
+    ``app_conf`` contains all the application-specific settings (those defined
+    under ``[app:main]``.
+    """
+    logging.config.fileConfig(global_conf['__file__'])
+    return make_app_without_logging(global_conf, full_stack=full_stack, **app_conf)
--- a/kallithea/config/post_receive_tmpl.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/post_receive_tmpl.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,34 +1,35 @@
-#!/usr/bin/env python2
+"""Kallithea Git hook
+
+This hook is installed and maintained by Kallithea. It will be overwritten
+by Kallithea - don't customize it manually!
+
+When Kallithea invokes Git, the KALLITHEA_EXTRAS environment variable will
+contain additional info like the Kallithea instance and user info that this
+hook will use.
+"""
+
 import os
 import sys
 
-try:
-    import kallithea
-    KALLITHEA_HOOK_VER = '_TMPL_'
-    os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER
-    from kallithea.lib.hooks import handle_git_post_receive as _handler
-except ImportError:
-    if os.environ.get('RC_DEBUG_GIT_HOOK'):
-        import traceback
-        print traceback.format_exc()
-    kallithea = None
+# Set output mode on windows to binary for stderr.
+# This prevents python (or the windows console) from replacing \n with \r\n.
+# Git doesn't display remote output lines that contain \r,
+# and therefore without this modification git would display empty lines
+# instead of the exception output.
+if sys.platform == "win32":
+    import msvcrt
+    msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
+
+KALLITHEA_HOOK_VER = '_TMPL_'
+os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER
+import kallithea.lib.hooks
 
 
 def main():
-    if kallithea is None:
-        # exit with success if we cannot import kallithea !!
-        # this allows simply push to this repo even without
-        # kallithea
-        sys.exit(0)
+    repo_path = os.path.abspath('.')
+    git_stdin_lines = sys.stdin.readlines()
+    sys.exit(kallithea.lib.hooks.handle_git_post_receive(repo_path, git_stdin_lines))
 
-    repo_path = os.path.abspath('.')
-    push_data = sys.stdin.readlines()
-    # os.environ is modified here by a subprocess call that
-    # runs git and later git executes this hook.
-    # Environ gets some additional info from kallithea system
-    # like IP or username from basic-auth
-    _handler(repo_path, push_data, os.environ)
-    sys.exit(0)
 
 if __name__ == '__main__':
     main()
--- a/kallithea/config/pre_receive_tmpl.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/pre_receive_tmpl.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,34 +1,35 @@
-#!/usr/bin/env python2
+"""Kallithea Git hook
+
+This hook is installed and maintained by Kallithea. It will be overwritten
+by Kallithea - don't customize it manually!
+
+When Kallithea invokes Git, the KALLITHEA_EXTRAS environment variable will
+contain additional info like the Kallithea instance and user info that this
+hook will use.
+"""
+
 import os
 import sys
 
-try:
-    import kallithea
-    KALLITHEA_HOOK_VER = '_TMPL_'
-    os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER
-    from kallithea.lib.hooks import handle_git_pre_receive as _handler
-except ImportError:
-    if os.environ.get('RC_DEBUG_GIT_HOOK'):
-        import traceback
-        print traceback.format_exc()
-    kallithea = None
+# Set output mode on windows to binary for stderr.
+# This prevents python (or the windows console) from replacing \n with \r\n.
+# Git doesn't display remote output lines that contain \r,
+# and therefore without this modification git would display empty lines
+# instead of the exception output.
+if sys.platform == "win32":
+    import msvcrt
+    msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
+
+KALLITHEA_HOOK_VER = '_TMPL_'
+os.environ['KALLITHEA_HOOK_VER'] = KALLITHEA_HOOK_VER
+import kallithea.lib.hooks
 
 
 def main():
-    if kallithea is None:
-        # exit with success if we cannot import kallithea !!
-        # this allows simply push to this repo even without
-        # kallithea
-        sys.exit(0)
+    repo_path = os.path.abspath('.')
+    git_stdin_lines = sys.stdin.readlines()
+    sys.exit(kallithea.lib.hooks.handle_git_pre_receive(repo_path, git_stdin_lines))
 
-    repo_path = os.path.abspath('.')
-    push_data = sys.stdin.readlines()
-    # os.environ is modified here by a subprocess call that
-    # runs git and later git executes this hook.
-    # Environ gets some additional info from kallithea system
-    # like IP or username from basic-auth
-    _handler(repo_path, push_data, os.environ)
-    sys.exit(0)
 
 if __name__ == '__main__':
     main()
--- a/kallithea/config/rcextensions/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/rcextensions/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -41,7 +41,7 @@
      :param created_on:
      :param enable_downloads:
      :param repo_id:
-     :param user_id:
+     :param owner_id:
      :param enable_statistics:
      :param clone_uri:
      :param fork_id:
@@ -49,6 +49,8 @@
      :param created_by:
     """
     return 0
+
+
 CREATE_REPO_HOOK = _crrepohook
 
 
@@ -73,6 +75,8 @@
     """
     reason = 'allowed'
     return True, reason
+
+
 PRE_CREATE_USER_HOOK = _pre_cruserhook
 
 #==============================================================================
@@ -105,6 +109,8 @@
       :param created_by:
     """
     return 0
+
+
 CREATE_USER_HOOK = _cruserhook
 
 
@@ -123,7 +129,7 @@
      :param created_on:
      :param enable_downloads:
      :param repo_id:
-     :param user_id:
+     :param owner_id:
      :param enable_statistics:
      :param clone_uri:
      :param fork_id:
@@ -132,6 +138,8 @@
      :param deleted_on:
     """
     return 0
+
+
 DELETE_REPO_HOOK = _dlrepohook
 
 
@@ -165,6 +173,8 @@
       :param deleted_by:
     """
     return 0
+
+
 DELETE_USER_HOOK = _dluserhook
 
 
@@ -189,6 +199,8 @@
       :param pushed_revs: list of pushed revisions
     """
     return 0
+
+
 PUSH_HOOK = _pushhook
 
 
@@ -212,4 +224,6 @@
       :param repository: repository name
     """
     return 0
+
+
 PULL_HOOK = _pullhook
--- a/kallithea/config/routing.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/config/routing.py	Sun Mar 31 21:28:56 2019 +0200
@@ -19,6 +19,7 @@
 refer to the routes manual at http://routes.groovie.org/docs/
 """
 
+from tg import request
 from routes import Mapper
 
 # prefix for non repository related links needs to be prefixed with `/`
@@ -27,7 +28,7 @@
 
 def make_map(config):
     """Create, configure and return the routes Mapper"""
-    rmap = Mapper(directory=config['pylons.paths']['controllers'],
+    rmap = Mapper(directory=config['paths']['controllers'],
                   always_scan=config['debug'])
     rmap.minimization = False
     rmap.explicit = False
@@ -45,7 +46,7 @@
         repo_name = match_dict.get('repo_name')
 
         if match_dict.get('f_path'):
-            #fix for multiple initial slashes that causes errors
+            # fix for multiple initial slashes that causes errors
             match_dict['f_path'] = match_dict['f_path'].lstrip('/')
 
         by_id_match = get_repo_by_id(repo_name)
@@ -89,20 +90,18 @@
     def check_int(environ, match_dict):
         return match_dict.get('id').isdigit()
 
-    # The ErrorController route (handles 404/500 error pages); it should
-    # likely stay at the top, ensuring it can always be resolved
-    rmap.connect('/error/{action}', controller='error')
-    rmap.connect('/error/{action}/{id}', controller='error')
-
     #==========================================================================
     # CUSTOM ROUTES HERE
     #==========================================================================
 
-    #MAIN PAGE
+    # MAIN PAGE
     rmap.connect('home', '/', controller='home', action='index')
     rmap.connect('about', '/about', controller='home', action='about')
+    rmap.redirect('/favicon.ico', '/images/favicon.ico')
     rmap.connect('repo_switcher_data', '/_repos', controller='home',
                  action='repo_switcher_data')
+    rmap.connect('users_and_groups_data', '/_users_and_groups', controller='home',
+                 action='users_and_groups_data')
 
     rmap.connect('rst_help',
                  "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
@@ -110,7 +109,7 @@
     rmap.connect('kallithea_project_url', "https://kallithea-scm.org/", _static=True)
     rmap.connect('issues_url', 'https://bitbucket.org/conservancy/kallithea/issues', _static=True)
 
-    #ADMIN REPOSITORY ROUTES
+    # ADMIN REPOSITORY ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/repos') as m:
         m.connect("repos", "/repos",
@@ -119,14 +118,13 @@
                   action="index", conditions=dict(method=["GET"]))
         m.connect("new_repo", "/create_repository",
                   action="create_repository", conditions=dict(method=["GET"]))
-        m.connect("put_repo", "/repos/{repo_name:.*?}",
-                  action="update", conditions=dict(method=["PUT"],
+        m.connect("update_repo", "/repos/{repo_name:.*?}",
+                  action="update", conditions=dict(method=["POST"],
                   function=check_repo))
-        m.connect("delete_repo", "/repos/{repo_name:.*?}",
-                  action="delete", conditions=dict(method=["DELETE"],
-                  ))
+        m.connect("delete_repo", "/repos/{repo_name:.*?}/delete",
+                  action="delete", conditions=dict(method=["POST"]))
 
-    #ADMIN REPOSITORY GROUPS ROUTES
+    # ADMIN REPOSITORY GROUPS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/repo_groups') as m:
         m.connect("repos_groups", "/repo_groups",
@@ -136,47 +134,40 @@
         m.connect("new_repos_group", "/repo_groups/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_repos_group", "/repo_groups/{group_name:.*?}",
-                  action="update", conditions=dict(method=["PUT"],
+                  action="update", conditions=dict(method=["POST"],
                                                    function=check_group))
 
         m.connect("repos_group", "/repo_groups/{group_name:.*?}",
                   action="show", conditions=dict(method=["GET"],
                                                  function=check_group))
 
-        #EXTRAS REPO GROUP ROUTES
+        # EXTRAS REPO GROUP ROUTES
         m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
                   action="edit",
                   conditions=dict(method=["GET"], function=check_group))
-        m.connect("edit_repo_group", "/repo_groups/{group_name:.*?}/edit",
-                  action="edit",
-                  conditions=dict(method=["PUT"], function=check_group))
 
         m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
                   action="edit_repo_group_advanced",
                   conditions=dict(method=["GET"], function=check_group))
-        m.connect("edit_repo_group_advanced", "/repo_groups/{group_name:.*?}/edit/advanced",
-                  action="edit_repo_group_advanced",
-                  conditions=dict(method=["PUT"], function=check_group))
 
         m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
                   action="edit_repo_group_perms",
                   conditions=dict(method=["GET"], function=check_group))
-        m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
+        m.connect("edit_repo_group_perms_update", "/repo_groups/{group_name:.*?}/edit/permissions",
                   action="update_perms",
-                  conditions=dict(method=["PUT"], function=check_group))
-        m.connect("edit_repo_group_perms", "/repo_groups/{group_name:.*?}/edit/permissions",
+                  conditions=dict(method=["POST"], function=check_group))
+        m.connect("edit_repo_group_perms_delete", "/repo_groups/{group_name:.*?}/edit/permissions/delete",
                   action="delete_perms",
-                  conditions=dict(method=["DELETE"], function=check_group))
+                  conditions=dict(method=["POST"], function=check_group))
 
-        m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}",
-                  action="delete", conditions=dict(method=["DELETE"],
+        m.connect("delete_repo_group", "/repo_groups/{group_name:.*?}/delete",
+                  action="delete", conditions=dict(method=["POST"],
                                                    function=check_group_skip_path))
 
-
-    #ADMIN USER ROUTES
+    # ADMIN USER ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/users') as m:
-        m.connect("users", "/users",
+        m.connect("new_user", "/users/new",
                   action="create", conditions=dict(method=["POST"]))
         m.connect("users", "/users",
                   action="index", conditions=dict(method=["GET"]))
@@ -185,47 +176,43 @@
         m.connect("new_user", "/users/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_user", "/users/{id}",
-                  action="update", conditions=dict(method=["PUT"]))
-        m.connect("delete_user", "/users/{id}",
-                  action="delete", conditions=dict(method=["DELETE"]))
+                  action="update", conditions=dict(method=["POST"]))
+        m.connect("delete_user", "/users/{id}/delete",
+                  action="delete", conditions=dict(method=["POST"]))
         m.connect("edit_user", "/users/{id}/edit",
                   action="edit", conditions=dict(method=["GET"]))
-        m.connect("user", "/users/{id}",
-                  action="show", conditions=dict(method=["GET"]))
 
-        #EXTRAS USER ROUTES
+        # EXTRAS USER ROUTES
         m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
                   action="edit_advanced", conditions=dict(method=["GET"]))
-        m.connect("edit_user_advanced", "/users/{id}/edit/advanced",
-                  action="update_advanced", conditions=dict(method=["PUT"]))
 
         m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
                   action="edit_api_keys", conditions=dict(method=["GET"]))
-        m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
+        m.connect("edit_user_api_keys_update", "/users/{id}/edit/api_keys",
                   action="add_api_key", conditions=dict(method=["POST"]))
-        m.connect("edit_user_api_keys", "/users/{id}/edit/api_keys",
-                  action="delete_api_key", conditions=dict(method=["DELETE"]))
+        m.connect("edit_user_api_keys_delete", "/users/{id}/edit/api_keys/delete",
+                  action="delete_api_key", conditions=dict(method=["POST"]))
 
         m.connect("edit_user_perms", "/users/{id}/edit/permissions",
                   action="edit_perms", conditions=dict(method=["GET"]))
-        m.connect("edit_user_perms", "/users/{id}/edit/permissions",
-                  action="update_perms", conditions=dict(method=["PUT"]))
+        m.connect("edit_user_perms_update", "/users/{id}/edit/permissions",
+                  action="update_perms", conditions=dict(method=["POST"]))
 
         m.connect("edit_user_emails", "/users/{id}/edit/emails",
                   action="edit_emails", conditions=dict(method=["GET"]))
-        m.connect("edit_user_emails", "/users/{id}/edit/emails",
-                  action="add_email", conditions=dict(method=["PUT"]))
-        m.connect("edit_user_emails", "/users/{id}/edit/emails",
-                  action="delete_email", conditions=dict(method=["DELETE"]))
+        m.connect("edit_user_emails_update", "/users/{id}/edit/emails",
+                  action="add_email", conditions=dict(method=["POST"]))
+        m.connect("edit_user_emails_delete", "/users/{id}/edit/emails/delete",
+                  action="delete_email", conditions=dict(method=["POST"]))
 
         m.connect("edit_user_ips", "/users/{id}/edit/ips",
                   action="edit_ips", conditions=dict(method=["GET"]))
-        m.connect("edit_user_ips", "/users/{id}/edit/ips",
-                  action="add_ip", conditions=dict(method=["PUT"]))
-        m.connect("edit_user_ips", "/users/{id}/edit/ips",
-                  action="delete_ip", conditions=dict(method=["DELETE"]))
+        m.connect("edit_user_ips_update", "/users/{id}/edit/ips",
+                  action="add_ip", conditions=dict(method=["POST"]))
+        m.connect("edit_user_ips_delete", "/users/{id}/edit/ips/delete",
+                  action="delete_ip", conditions=dict(method=["POST"]))
 
-    #ADMIN USER GROUPS REST ROUTES
+    # ADMIN USER GROUPS REST ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/user_groups') as m:
         m.connect("users_groups", "/user_groups",
@@ -235,28 +222,25 @@
         m.connect("new_users_group", "/user_groups/new",
                   action="new", conditions=dict(method=["GET"]))
         m.connect("update_users_group", "/user_groups/{id}",
-                  action="update", conditions=dict(method=["PUT"]))
-        m.connect("delete_users_group", "/user_groups/{id}",
-                  action="delete", conditions=dict(method=["DELETE"]))
+                  action="update", conditions=dict(method=["POST"]))
+        m.connect("delete_users_group", "/user_groups/{id}/delete",
+                  action="delete", conditions=dict(method=["POST"]))
         m.connect("edit_users_group", "/user_groups/{id}/edit",
                   action="edit", conditions=dict(method=["GET"]),
                   function=check_user_group)
-        m.connect("users_group", "/user_groups/{id}",
-                  action="show", conditions=dict(method=["GET"]))
 
-        #EXTRAS USER GROUP ROUTES
+        # EXTRAS USER GROUP ROUTES
         m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
                   action="edit_default_perms", conditions=dict(method=["GET"]))
-        m.connect("edit_user_group_default_perms", "/user_groups/{id}/edit/default_perms",
-                  action="update_default_perms", conditions=dict(method=["PUT"]))
-
+        m.connect("edit_user_group_default_perms_update", "/user_groups/{id}/edit/default_perms",
+                  action="update_default_perms", conditions=dict(method=["POST"]))
 
         m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
                   action="edit_perms", conditions=dict(method=["GET"]))
-        m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
-                  action="update_perms", conditions=dict(method=["PUT"]))
-        m.connect("edit_user_group_perms", "/user_groups/{id}/edit/perms",
-                  action="delete_perms", conditions=dict(method=["DELETE"]))
+        m.connect("edit_user_group_perms_update", "/user_groups/{id}/edit/perms",
+                  action="update_perms", conditions=dict(method=["POST"]))
+        m.connect("edit_user_group_perms_delete", "/user_groups/{id}/edit/perms/delete",
+                  action="delete_perms", conditions=dict(method=["POST"]))
 
         m.connect("edit_user_group_advanced", "/user_groups/{id}/edit/advanced",
                   action="edit_advanced", conditions=dict(method=["GET"]))
@@ -264,9 +248,7 @@
         m.connect("edit_user_group_members", "/user_groups/{id}/edit/members",
                   action="edit_members", conditions=dict(method=["GET"]))
 
-
-
-    #ADMIN PERMISSIONS ROUTES
+    # ADMIN PERMISSIONS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/permissions') as m:
         m.connect("admin_permissions", "/permissions",
@@ -275,28 +257,27 @@
                   action="permission_globals", conditions=dict(method=["GET"]))
 
         m.connect("admin_permissions_ips", "/permissions/ips",
-                  action="permission_ips", conditions=dict(method=["POST"]))
-        m.connect("admin_permissions_ips", "/permissions/ips",
                   action="permission_ips", conditions=dict(method=["GET"]))
 
         m.connect("admin_permissions_perms", "/permissions/perms",
-                  action="permission_perms", conditions=dict(method=["POST"]))
-        m.connect("admin_permissions_perms", "/permissions/perms",
                   action="permission_perms", conditions=dict(method=["GET"]))
 
+    # ADMIN DEFAULTS ROUTES
+    with rmap.submapper(path_prefix=ADMIN_PREFIX,
+                        controller='admin/defaults') as m:
+        m.connect('defaults', 'defaults',
+                  action="index")
+        m.connect('defaults_update', 'defaults/{id}/update',
+                  action="update", conditions=dict(method=["POST"]))
 
-    #ADMIN DEFAULTS REST ROUTES
-    rmap.resource('default', 'defaults',
-                  controller='admin/defaults', path_prefix=ADMIN_PREFIX)
-
-    #ADMIN AUTH SETTINGS
+    # ADMIN AUTH SETTINGS
     rmap.connect('auth_settings', '%s/auth' % ADMIN_PREFIX,
                  controller='admin/auth_settings', action='auth_settings',
                  conditions=dict(method=["POST"]))
     rmap.connect('auth_home', '%s/auth' % ADMIN_PREFIX,
                  controller='admin/auth_settings')
 
-    #ADMIN SETTINGS ROUTES
+    # ADMIN SETTINGS ROUTES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/settings') as m:
         m.connect("admin_settings", "/settings",
@@ -326,8 +307,8 @@
 
         m.connect("admin_settings_hooks", "/settings/hooks",
                   action="settings_hooks", conditions=dict(method=["POST"]))
-        m.connect("admin_settings_hooks", "/settings/hooks",
-                  action="settings_hooks", conditions=dict(method=["DELETE"]))
+        m.connect("admin_settings_hooks_delete", "/settings/hooks/delete",
+                  action="settings_hooks", conditions=dict(method=["POST"]))
         m.connect("admin_settings_hooks", "/settings/hooks",
                   action="settings_hooks", conditions=dict(method=["GET"]))
 
@@ -343,7 +324,7 @@
         m.connect("admin_settings_system_update", "/settings/system/updates",
                   action="settings_system_update", conditions=dict(method=["GET"]))
 
-    #ADMIN MY ACCOUNT
+    # ADMIN MY ACCOUNT
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/my_account') as m:
 
@@ -370,46 +351,17 @@
                   action="my_account_emails", conditions=dict(method=["GET"]))
         m.connect("my_account_emails", "/my_account/emails",
                   action="my_account_emails_add", conditions=dict(method=["POST"]))
-        m.connect("my_account_emails", "/my_account/emails",
-                  action="my_account_emails_delete", conditions=dict(method=["DELETE"]))
+        m.connect("my_account_emails_delete", "/my_account/emails/delete",
+                  action="my_account_emails_delete", conditions=dict(method=["POST"]))
 
         m.connect("my_account_api_keys", "/my_account/api_keys",
                   action="my_account_api_keys", conditions=dict(method=["GET"]))
         m.connect("my_account_api_keys", "/my_account/api_keys",
                   action="my_account_api_keys_add", conditions=dict(method=["POST"]))
-        m.connect("my_account_api_keys", "/my_account/api_keys",
-                  action="my_account_api_keys_delete", conditions=dict(method=["DELETE"]))
+        m.connect("my_account_api_keys_delete", "/my_account/api_keys/delete",
+                  action="my_account_api_keys_delete", conditions=dict(method=["POST"]))
 
-    #NOTIFICATION REST ROUTES
-    with rmap.submapper(path_prefix=ADMIN_PREFIX,
-                        controller='admin/notifications') as m:
-        m.connect("notifications", "/notifications",
-                  action="create", conditions=dict(method=["POST"]))
-        m.connect("notifications", "/notifications",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
-                  action="mark_all_read", conditions=dict(method=["GET"]))
-        m.connect("formatted_notifications", "/notifications.{format}",
-                  action="index", conditions=dict(method=["GET"]))
-        m.connect("new_notification", "/notifications/new",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("formatted_new_notification", "/notifications/new.{format}",
-                  action="new", conditions=dict(method=["GET"]))
-        m.connect("/notifications/{notification_id}",
-                  action="update", conditions=dict(method=["PUT"]))
-        m.connect("/notifications/{notification_id}",
-                  action="delete", conditions=dict(method=["DELETE"]))
-        m.connect("edit_notification", "/notifications/{notification_id}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("formatted_edit_notification",
-                  "/notifications/{notification_id}.{format}/edit",
-                  action="edit", conditions=dict(method=["GET"]))
-        m.connect("notification", "/notifications/{notification_id}",
-                  action="show", conditions=dict(method=["GET"]))
-        m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
-                  action="show", conditions=dict(method=["GET"]))
-
-    #ADMIN GIST
+    # ADMIN GIST
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/gists') as m:
         m.connect("gists", "/gists",
@@ -419,17 +371,13 @@
         m.connect("new_gist", "/gists/new",
                   action="new", conditions=dict(method=["GET"]))
 
-
-        m.connect("/gists/{gist_id}",
-                  action="update", conditions=dict(method=["PUT"]))
-        m.connect("/gists/{gist_id}",
-                  action="delete", conditions=dict(method=["DELETE"]))
+        m.connect("gist_delete", "/gists/{gist_id}/delete",
+                  action="delete", conditions=dict(method=["POST"]))
         m.connect("edit_gist", "/gists/{gist_id}/edit",
                   action="edit", conditions=dict(method=["GET", "POST"]))
         m.connect("edit_gist_check_revision", "/gists/{gist_id}/edit/check_revision",
                   action="check_revision", conditions=dict(method=["POST"]))
 
-
         m.connect("gist", "/gists/{gist_id}",
                   action="show", conditions=dict(method=["GET"]))
         m.connect("gist_rev", "/gists/{gist_id}/{revision}",
@@ -442,7 +390,7 @@
                   revision='tip',
                   action="show", conditions=dict(method=["GET"]))
 
-    #ADMIN MAIN PAGES
+    # ADMIN MAIN PAGES
     with rmap.submapper(path_prefix=ADMIN_PREFIX,
                         controller='admin/admin') as m:
         m.connect('admin_home', '', action='index')
@@ -451,11 +399,11 @@
     #==========================================================================
     # API V2
     #==========================================================================
-    with rmap.submapper(path_prefix=ADMIN_PREFIX,
-                        controller='api/api') as m:
+    with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='api/api',
+                        action='_dispatch') as m:
         m.connect('api', '/api')
 
-    #USER JOURNAL
+    # USER JOURNAL
     rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
                  controller='journal', action='index')
     rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
@@ -484,7 +432,7 @@
                  controller='journal', action='toggle_following',
                  conditions=dict(method=["POST"]))
 
-    #SEARCH
+    # SEARCH
     rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
     rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
                  controller='search',
@@ -494,7 +442,7 @@
                  conditions=dict(function=check_repo),
                  )
 
-    #LOGIN/LOGOUT/REGISTER/SIGN IN
+    # LOGIN/LOGOUT/REGISTER/SIGN IN
     rmap.connect('authentication_token', '%s/authentication_token' % ADMIN_PREFIX, controller='login', action='authentication_token')
     rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
     rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
@@ -510,7 +458,7 @@
                  '%s/password_reset_confirmation' % ADMIN_PREFIX,
                  controller='login', action='password_reset_confirmation')
 
-    #FEEDS
+    # FEEDS
     rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
                 controller='feed', action='rss',
                 conditions=dict(function=check_repo))
@@ -543,8 +491,6 @@
                 controller='summary', action='repo_size',
                 conditions=dict(function=check_repo))
 
-    rmap.connect('branch_tag_switcher', '/{repo_name:.*?}/branches-tags',
-                 controller='home', action='branch_tag_switcher')
     rmap.connect('repo_refs_data', '/{repo_name:.*?}/refs-data',
                  controller='home', action='repo_refs_data')
 
@@ -568,21 +514,20 @@
                  conditions=dict(method=["GET"], function=check_repo))
     rmap.connect("edit_repo_perms_update", "/{repo_name:.*?}/settings/permissions",
                  controller='admin/repos', action="edit_permissions_update",
-                 conditions=dict(method=["PUT"], function=check_repo))
-    rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions",
+                 conditions=dict(method=["POST"], function=check_repo))
+    rmap.connect("edit_repo_perms_revoke", "/{repo_name:.*?}/settings/permissions/delete",
                  controller='admin/repos', action="edit_permissions_revoke",
-                 conditions=dict(method=["DELETE"], function=check_repo))
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_fields", "/{repo_name:.*?}/settings/fields",
                  controller='admin/repos', action="edit_fields",
                  conditions=dict(method=["GET"], function=check_repo))
     rmap.connect('create_repo_fields', "/{repo_name:.*?}/settings/fields/new",
                  controller='admin/repos', action="create_repo_field",
-                 conditions=dict(method=["PUT"], function=check_repo))
-    rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}",
+                 conditions=dict(method=["POST"], function=check_repo))
+    rmap.connect('delete_repo_fields', "/{repo_name:.*?}/settings/fields/{field_id}/delete",
                  controller='admin/repos', action="delete_repo_field",
-                 conditions=dict(method=["DELETE"], function=check_repo))
-
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_advanced", "/{repo_name:.*?}/settings/advanced",
                  controller='admin/repos', action="edit_advanced",
@@ -590,43 +535,41 @@
 
     rmap.connect("edit_repo_advanced_locking", "/{repo_name:.*?}/settings/advanced/locking",
                  controller='admin/repos', action="edit_advanced_locking",
-                 conditions=dict(method=["PUT"], function=check_repo))
+                 conditions=dict(method=["POST"], function=check_repo))
     rmap.connect('toggle_locking', "/{repo_name:.*?}/settings/advanced/locking_toggle",
                  controller='admin/repos', action="toggle_locking",
                  conditions=dict(method=["GET"], function=check_repo))
 
     rmap.connect("edit_repo_advanced_journal", "/{repo_name:.*?}/settings/advanced/journal",
                  controller='admin/repos', action="edit_advanced_journal",
-                 conditions=dict(method=["PUT"], function=check_repo))
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_advanced_fork", "/{repo_name:.*?}/settings/advanced/fork",
                  controller='admin/repos', action="edit_advanced_fork",
-                 conditions=dict(method=["PUT"], function=check_repo))
-
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
                  controller='admin/repos', action="edit_caches",
                  conditions=dict(method=["GET"], function=check_repo))
-    rmap.connect("edit_repo_caches", "/{repo_name:.*?}/settings/caches",
+    rmap.connect("update_repo_caches", "/{repo_name:.*?}/settings/caches",
                  controller='admin/repos', action="edit_caches",
-                 conditions=dict(method=["PUT"], function=check_repo))
-
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
                  controller='admin/repos', action="edit_remote",
                  conditions=dict(method=["GET"], function=check_repo))
-    rmap.connect("edit_repo_remote", "/{repo_name:.*?}/settings/remote",
+    rmap.connect("edit_repo_remote_update", "/{repo_name:.*?}/settings/remote",
                  controller='admin/repos', action="edit_remote",
-                 conditions=dict(method=["PUT"], function=check_repo))
+                 conditions=dict(method=["POST"], function=check_repo))
 
     rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
                  controller='admin/repos', action="edit_statistics",
                  conditions=dict(method=["GET"], function=check_repo))
-    rmap.connect("edit_repo_statistics", "/{repo_name:.*?}/settings/statistics",
+    rmap.connect("edit_repo_statistics_update", "/{repo_name:.*?}/settings/statistics",
                  controller='admin/repos', action="edit_statistics",
-                 conditions=dict(method=["PUT"], function=check_repo))
+                 conditions=dict(method=["POST"], function=check_repo))
 
-    #still working url for backward compat.
+    # still working url for backward compat.
     rmap.connect('raw_changeset_home_depraced',
                  '/{repo_name:.*?}/raw-changeset/{revision}',
                  controller='changeset', action='changeset_raw',
@@ -653,16 +596,11 @@
                 controller='changeset', revision='tip', action='comment',
                 conditions=dict(function=check_repo))
 
-    rmap.connect('changeset_comment_preview',
-                 '/{repo_name:.*?}/changeset-comment-preview',
-                controller='changeset', action='preview_comment',
+    rmap.connect('changeset_comment_delete',
+                 '/{repo_name:.*?}/changeset-comment/{comment_id}/delete',
+                controller='changeset', action='delete_comment',
                 conditions=dict(function=check_repo, method=["POST"]))
 
-    rmap.connect('changeset_comment_delete',
-                 '/{repo_name:.*?}/changeset-comment-delete/{comment_id}',
-                controller='changeset', action='delete_comment',
-                conditions=dict(function=check_repo, method=["DELETE"]))
-
     rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
                  controller='changeset', action='changeset_info')
 
@@ -706,10 +644,10 @@
                  action='post', conditions=dict(function=check_repo,
                                                 method=["POST"]))
     rmap.connect('pullrequest_delete',
-                 '/{repo_name:.*?}/pull-request/{pull_request_id}',
+                 '/{repo_name:.*?}/pull-request/{pull_request_id}/delete',
                  controller='pullrequests',
                  action='delete', conditions=dict(function=check_repo,
-                                                method=["DELETE"]))
+                                                  method=["POST"]))
 
     rmap.connect('pullrequest_show_all',
                  '/{repo_name:.*?}/pull-request',
@@ -731,27 +669,14 @@
     rmap.connect('pullrequest_comment_delete',
                  '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
                 controller='pullrequests', action='delete_comment',
-                conditions=dict(function=check_repo, method=["DELETE"]))
+                conditions=dict(function=check_repo, method=["POST"]))
 
     rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
                 controller='summary', conditions=dict(function=check_repo))
 
-    rmap.connect('branches_home', '/{repo_name:.*?}/branches',
-                controller='branches', conditions=dict(function=check_repo))
-
-    rmap.connect('tags_home', '/{repo_name:.*?}/tags',
-                controller='tags', conditions=dict(function=check_repo))
-
-    rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
-                controller='bookmarks', conditions=dict(function=check_repo))
-
     rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
                 controller='changelog', conditions=dict(function=check_repo))
 
-    rmap.connect('changelog_summary_home', '/{repo_name:.*?}/changelog_summary',
-                controller='changelog', action='changelog_summary',
-                conditions=dict(function=check_repo))
-
     rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
                 controller='changelog', f_path=None,
                 conditions=dict(function=check_repo))
@@ -842,3 +767,29 @@
                  conditions=dict(function=check_repo))
 
     return rmap
+
+
+class UrlGenerator(object):
+    """Emulate pylons.url in providing a wrapper around routes.url
+
+    This code was added during migration from Pylons to Turbogears2. Pylons
+    already provided a wrapper like this, but Turbogears2 does not.
+
+    When the routing of Kallithea is changed to use less Routes and more
+    Turbogears2-style routing, this class may disappear or change.
+
+    url() (the __call__ method) returns the URL based on a route name and
+    arguments.
+    url.current() returns the URL of the current page with arguments applied.
+
+    Refer to documentation of Routes for details:
+    https://routes.readthedocs.io/en/latest/generating.html#generation
+    """
+    def __call__(self, *args, **kwargs):
+        return request.environ['routes.url'](*args, **kwargs)
+
+    def current(self, *args, **kwargs):
+        return request.environ['routes.url'].current(*args, **kwargs)
+
+
+url = UrlGenerator()
--- a/kallithea/controllers/admin/admin.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/admin.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,19 +28,20 @@
 
 import logging
 
-from pylons import request, tmpl_context as c, url
+from tg import request, tmpl_context as c
 from sqlalchemy.orm import joinedload
 from whoosh.qparser.default import QueryParser
 from whoosh.qparser.dateparse import DateParserPlugin
 from whoosh import query
 from sqlalchemy.sql.expression import or_, and_, func
 
+from kallithea.config.routing import url
 from kallithea.model.db import UserLog
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.utils2 import safe_int, remove_prefix, remove_suffix
 from kallithea.lib.indexers import JOURNAL_SCHEMA
-from kallithea.lib.helpers import Page
+from kallithea.lib.page import Page
 
 
 log = logging.getLogger(__name__)
@@ -64,14 +65,14 @@
 
     def wildcard_handler(col, wc_term):
         if wc_term.startswith('*') and not wc_term.endswith('*'):
-            #postfix == endswith
+            # postfix == endswith
             wc_term = remove_prefix(wc_term, prefix='*')
-            return func.lower(col).endswith(wc_term)
+            return func.lower(col).endswith(func.lower(wc_term))
         elif wc_term.startswith('*') and wc_term.endswith('*'):
-            #wildcard == ilike
+            # wildcard == ilike
             wc_term = remove_prefix(wc_term, prefix='*')
             wc_term = remove_suffix(wc_term, suffix='*')
-            return func.lower(col).contains(wc_term)
+            return func.lower(col).contains(func.lower(wc_term))
 
     def get_filterion(field, val, term):
 
@@ -87,7 +88,7 @@
             field = getattr(UserLog, field)
         log.debug('filter field: %s val=>%s', field, val)
 
-        #sql filtering
+        # sql filtering
         if isinstance(term, query.Wildcard):
             return wildcard_handler(field, val)
         elif isinstance(term, query.Prefix):
@@ -119,23 +120,23 @@
 
 class AdminController(BaseController):
 
-    @LoginRequired()
-    def __before__(self):
-        super(AdminController, self).__before__()
+    @LoginRequired(allow_default_user=True)
+    def _before(self, *args, **kwargs):
+        super(AdminController, self)._before(*args, **kwargs)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def index(self):
-        users_log = UserLog.query()\
-                .options(joinedload(UserLog.user))\
+        users_log = UserLog.query() \
+                .options(joinedload(UserLog.user)) \
                 .options(joinedload(UserLog.repository))
 
-        #FILTERING
+        # FILTERING
         c.search_term = request.GET.get('filter')
         users_log = _journal_filter(users_log, c.search_term)
 
         users_log = users_log.order_by(UserLog.action_date.desc())
 
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
 
         def url_generator(**kw):
             return url.current(filter=c.search_term, **kw)
--- a/kallithea/controllers/admin/auth_settings.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/auth_settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,14 +27,15 @@
 import formencode.htmlfill
 import traceback
 
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib.compat import formatted_json
 from kallithea.lib.base import BaseController, render
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator
 from kallithea.lib import auth_modules
 from kallithea.model.forms import AuthSettingsForm
 from kallithea.model.db import Setting
@@ -46,9 +47,9 @@
 class AuthSettingsController(BaseController):
 
     @LoginRequired()
-    @HasPermissionAllDecorator('hg.admin')
-    def __before__(self):
-        super(AuthSettingsController, self).__before__()
+    @HasPermissionAnyDecorator('hg.admin')
+    def _before(self, *args, **kwargs):
+        super(AuthSettingsController, self)._before(*args, **kwargs)
 
     def __load_defaults(self):
         c.available_plugins = [
@@ -58,32 +59,32 @@
             'kallithea.lib.auth_modules.auth_crowd',
             'kallithea.lib.auth_modules.auth_pam'
         ]
-        c.enabled_plugins = Setting.get_auth_plugins()
+        self.enabled_plugins = auth_modules.get_auth_plugins()
+        c.enabled_plugin_names = [plugin.__class__.__module__ for plugin in self.enabled_plugins]
 
     def __render(self, defaults, errors):
         c.defaults = {}
         c.plugin_settings = {}
         c.plugin_shortnames = {}
 
-        for module in c.enabled_plugins:
-            plugin = auth_modules.loadplugin(module)
-            plugin_name = plugin.name
-            c.plugin_shortnames[module] = plugin_name
+        for plugin in self.enabled_plugins:
+            module = plugin.__class__.__module__
+            c.plugin_shortnames[module] = plugin.name
             c.plugin_settings[module] = plugin.plugin_settings()
             for v in c.plugin_settings[module]:
-                fullname = ("auth_" + plugin_name + "_" + v["name"])
+                fullname = "auth_%s_%s" % (plugin.name, v["name"])
                 if "default" in v:
                     c.defaults[fullname] = v["default"]
                 # Current values will be the default on the form, if there are any
                 setting = Setting.get_by_name(fullname)
                 if setting is not None:
                     c.defaults[fullname] = setting.app_settings_value
-        # we want to show , separated list of enabled plugins
-        c.defaults['auth_plugins'] = ','.join(c.enabled_plugins)
-
         if defaults:
             c.defaults.update(defaults)
 
+        # we want to show , separated list of enabled plugins
+        c.defaults['auth_plugins'] = ','.join(c.enabled_plugin_names)
+
         log.debug(formatted_json(defaults))
         return formencode.htmlfill.render(
             render('admin/auth/auth_settings.html'),
@@ -117,10 +118,10 @@
             # (yet), since that'll cause validation errors and/or wrong
             # settings being applied (e.g. checkboxes being cleared),
             # since the plugin settings will not be in the POST data.
-            c.enabled_plugins = [ p for p in c.enabled_plugins if p in new_enabled_plugins ]
+            c.enabled_plugin_names = [p for p in c.enabled_plugin_names if p in new_enabled_plugins]
 
         # Next, parse everything including plugin settings.
-        _form = AuthSettingsForm(c.enabled_plugins)()
+        _form = AuthSettingsForm(c.enabled_plugin_names)()
 
         try:
             form_result = _form.to_python(dict(request.POST))
@@ -130,7 +131,6 @@
                     v = ','.join(v)
                 log.debug("%s = %s", k, str(v))
                 setting = Setting.create_or_update(k, v)
-                Session().add(setting)
             Session().commit()
             h.flash(_('Auth settings updated successfully'),
                        category='success')
@@ -146,4 +146,4 @@
             h.flash(_('error occurred during update of auth settings'),
                     category='error')
 
-        return redirect(url('auth_home'))
+        raise HTTPFound(location=url('auth_home'))
--- a/kallithea/controllers/admin/defaults.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/defaults.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,12 +30,13 @@
 import formencode
 from formencode import htmlfill
 
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, render
 from kallithea.model.forms import DefaultsForm
 from kallithea.model.meta import Session
@@ -46,19 +47,13 @@
 
 
 class DefaultsController(BaseController):
-    """REST Controller styled on the Atom Publishing Protocol"""
-    # To properly map this controller, ensure your config/routing.py
-    # file has a resource setup:
-    #     map.resource('default', 'defaults')
 
     @LoginRequired()
-    @HasPermissionAllDecorator('hg.admin')
-    def __before__(self):
-        super(DefaultsController, self).__before__()
+    @HasPermissionAnyDecorator('hg.admin')
+    def _before(self, *args, **kwargs):
+        super(DefaultsController, self)._before(*args, **kwargs)
 
     def index(self, format='html'):
-        """GET /defaults: All items in the collection"""
-        # url('defaults')
         c.backends = BACKENDS.keys()
         defaults = Setting.get_default_repo_settings()
 
@@ -69,30 +64,13 @@
             force_defaults=False
         )
 
-    def create(self):
-        """POST /defaults: Create a new item"""
-        # url('defaults')
-
-    def new(self, format='html'):
-        """GET /defaults/new: Form to create a new item"""
-        # url('new_default')
-
     def update(self, id):
-        """PUT /defaults/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('default', id=ID),
-        #           method='put')
-        # url('default', id=ID)
-
         _form = DefaultsForm()()
 
         try:
             form_result = _form.to_python(dict(request.POST))
             for k, v in form_result.iteritems():
                 setting = Setting.create_or_update(k, v)
-                Session().add(setting)
             Session().commit()
             h.flash(_('Default settings updated successfully'),
                     category='success')
@@ -112,21 +90,4 @@
             h.flash(_('Error occurred during update of defaults'),
                     category='error')
 
-        return redirect(url('defaults'))
-
-    def delete(self, id):
-        """DELETE /defaults/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('default', id=ID),
-        #           method='delete')
-        # url('default', id=ID)
-
-    def show(self, id, format='html'):
-        """GET /defaults/id: Show a specific item"""
-        # url('default', id=ID)
-
-    def edit(self, id, format='html'):
-        """GET /defaults/id/edit: Form to edit an existing item"""
-        # url('edit_default', id=ID)
+        raise HTTPFound(location=url('defaults'))
--- a/kallithea/controllers/admin/gists.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/gists.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,21 +30,20 @@
 import traceback
 import formencode.htmlfill
 
-from pylons import request, response, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, response, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden
 
+from kallithea.config.routing import url
 from kallithea.model.forms import GistForm
 from kallithea.model.gist import GistModel
 from kallithea.model.meta import Session
 from kallithea.model.db import Gist, User
 from kallithea.lib import helpers as h
-from kallithea.lib.base import BaseController, render
-from kallithea.lib.auth import LoginRequired, NotAnonymous
-from kallithea.lib.utils import jsonify
+from kallithea.lib.base import BaseController, render, jsonify
+from kallithea.lib.auth import LoginRequired
 from kallithea.lib.utils2 import safe_int, safe_unicode, time_to_datetime
-from kallithea.lib.helpers import Page
-from webob.exc import HTTPNotFound, HTTPForbidden
+from kallithea.lib.page import Page
 from sqlalchemy.sql.expression import or_
 from kallithea.lib.vcs.exceptions import VCSError, NodeNotChangedError
 
@@ -66,52 +65,47 @@
             c.lifetime_values.append(extra_values)
         c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
 
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     def index(self):
-        """GET /admin/gists: All items in the collection"""
-        # url('gists')
-        not_default_user = c.authuser.username != User.DEFAULT_USER
+        not_default_user = not request.authuser.is_default_user
         c.show_private = request.GET.get('private') and not_default_user
         c.show_public = request.GET.get('public') and not_default_user
 
-        gists = Gist().query()\
-            .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
+        gists = Gist().query() \
+            .filter_by(is_expired=False) \
             .order_by(Gist.created_on.desc())
 
         # MY private
         if c.show_private and not c.show_public:
-            gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
-                             .filter(Gist.gist_owner == c.authuser.user_id)
+            gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE) \
+                             .filter(Gist.owner_id == request.authuser.user_id)
         # MY public
         elif c.show_public and not c.show_private:
-            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
-                             .filter(Gist.gist_owner == c.authuser.user_id)
+            gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC) \
+                             .filter(Gist.owner_id == request.authuser.user_id)
 
         # MY public+private
         elif c.show_private and c.show_public:
             gists = gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
-                                     Gist.gist_type == Gist.GIST_PRIVATE))\
-                             .filter(Gist.gist_owner == c.authuser.user_id)
+                                     Gist.gist_type == Gist.GIST_PRIVATE)) \
+                             .filter(Gist.owner_id == request.authuser.user_id)
 
         # default show ALL public gists
         if not c.show_public and not c.show_private:
             gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
 
         c.gists = gists
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
         c.gists_pager = Page(c.gists, page=p, items_per_page=10)
         return render('admin/gists/index.html')
 
     @LoginRequired()
-    @NotAnonymous()
     def create(self):
-        """POST /admin/gists: Create a new item"""
-        # url('gists')
         self.__load_defaults()
         gist_form = GistForm([x[0] for x in c.lifetime_values])()
         try:
             form_result = gist_form.to_python(dict(request.POST))
-            #TODO: multiple files support, from the form
+            # TODO: multiple files support, from the form
             filename = form_result['filename'] or Gist.DEFAULT_FILENAME
             nodes = {
                 filename: {
@@ -123,7 +117,7 @@
             gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
             gist = GistModel().create(
                 description=form_result['description'],
-                owner=c.authuser.user_id,
+                owner=request.authuser.user_id,
                 gist_mapping=nodes,
                 gist_type=gist_type,
                 lifetime=form_result['lifetime']
@@ -144,40 +138,18 @@
         except Exception as e:
             log.error(traceback.format_exc())
             h.flash(_('Error occurred during gist creation'), category='error')
-            return redirect(url('new_gist'))
-        return redirect(url('gist', gist_id=new_gist_id))
+            raise HTTPFound(location=url('new_gist'))
+        raise HTTPFound(location=url('gist', gist_id=new_gist_id))
 
     @LoginRequired()
-    @NotAnonymous()
     def new(self, format='html'):
-        """GET /admin/gists/new: Form to create a new item"""
-        # url('new_gist')
         self.__load_defaults()
         return render('admin/gists/new.html')
 
     @LoginRequired()
-    @NotAnonymous()
-    def update(self, gist_id):
-        """PUT /admin/gists/gist_id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('gist', gist_id=ID),
-        #           method='put')
-        # url('gist', gist_id=ID)
-
-    @LoginRequired()
-    @NotAnonymous()
     def delete(self, gist_id):
-        """DELETE /admin/gists/gist_id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('gist', gist_id=ID),
-        #           method='delete')
-        # url('gist', gist_id=ID)
         gist = GistModel().get_gist(gist_id)
-        owner = gist.gist_owner == c.authuser.user_id
+        owner = gist.owner_id == request.authuser.user_id
         if h.HasPermissionAny('hg.admin')() or owner:
             GistModel().delete(gist)
             Session().commit()
@@ -185,20 +157,16 @@
         else:
             raise HTTPForbidden()
 
-        return redirect(url('gists'))
+        raise HTTPFound(location=url('gists'))
 
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     def show(self, gist_id, revision='tip', format='html', f_path=None):
-        """GET /admin/gists/gist_id: Show a specific item"""
-        # url('gist', gist_id=ID)
         c.gist = Gist.get_or_404(gist_id)
 
-        #check if this gist is not expired
-        if c.gist.gist_expires != -1:
-            if time.time() > c.gist.gist_expires:
-                log.error('Gist expired at %s',
-                          time_to_datetime(c.gist.gist_expires))
-                raise HTTPNotFound()
+        if c.gist.is_expired:
+            log.error('Gist expired at %s',
+                      time_to_datetime(c.gist.gist_expires))
+            raise HTTPNotFound()
         try:
             c.file_changeset, c.files = GistModel().get_gist_files(gist_id,
                                                             revision=revision)
@@ -212,18 +180,13 @@
         return render('admin/gists/show.html')
 
     @LoginRequired()
-    @NotAnonymous()
     def edit(self, gist_id, format='html'):
-        """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
-        # url('edit_gist', gist_id=ID)
         c.gist = Gist.get_or_404(gist_id)
 
-        #check if this gist is not expired
-        if c.gist.gist_expires != -1:
-            if time.time() > c.gist.gist_expires:
-                log.error('Gist expired at %s',
-                          time_to_datetime(c.gist.gist_expires))
-                raise HTTPNotFound()
+        if c.gist.is_expired:
+            log.error('Gist expired at %s',
+                      time_to_datetime(c.gist.gist_expires))
+            raise HTTPNotFound()
         try:
             c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
         except VCSError:
@@ -270,12 +233,11 @@
                 h.flash(_('Error occurred during update of gist %s') % gist_id,
                         category='error')
 
-            return redirect(url('gist', gist_id=gist_id))
+            raise HTTPFound(location=url('gist', gist_id=gist_id))
 
         return rendered
 
     @LoginRequired()
-    @NotAnonymous()
     @jsonify
     def check_revision(self, gist_id):
         c.gist = Gist.get_or_404(gist_id)
@@ -283,7 +245,7 @@
         success = True
         revision = request.POST.get('revision')
 
-        ##TODO: maybe move this to model ?
+        # TODO: maybe move this to model ?
         if revision != last_rev.raw_id:
             log.error('Last revision %s is different than submitted %s',
                       revision, last_rev)
--- a/kallithea/controllers/admin/my_account.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/my_account.py	Sun Mar 31 21:28:56 2019 +0200
@@ -31,17 +31,16 @@
 
 from sqlalchemy import func
 from formencode import htmlfill
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
-from kallithea import EXTERN_TYPE_INTERNAL
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib import auth_modules
-from kallithea.lib.auth import LoginRequired, NotAnonymous, AuthUser
+from kallithea.lib.auth import LoginRequired, AuthUser
 from kallithea.lib.base import BaseController, render
 from kallithea.lib.utils2 import generate_api_key, safe_int
-from kallithea.lib.compat import json
 from kallithea.model.db import Repository, UserEmailMap, User, UserFollowing
 from kallithea.model.forms import UserForm, PasswordChangeForm
 from kallithea.model.user import UserModel
@@ -60,48 +59,37 @@
     #         path_prefix='/admin', name_prefix='admin_')
 
     @LoginRequired()
-    @NotAnonymous()
-    def __before__(self):
-        super(MyAccountController, self).__before__()
+    def _before(self, *args, **kwargs):
+        super(MyAccountController, self)._before(*args, **kwargs)
 
     def __load_data(self):
-        c.user = User.get(self.authuser.user_id)
-        if c.user.username == User.DEFAULT_USER:
+        c.user = User.get(request.authuser.user_id)
+        if c.user.is_default_user:
             h.flash(_("You can't edit this user since it's"
                       " crucial for entire application"), category='warning')
-            return redirect(url('users'))
-        c.EXTERN_TYPE_INTERNAL = EXTERN_TYPE_INTERNAL
+            raise HTTPFound(location=url('users'))
 
     def _load_my_repos_data(self, watched=False):
         if watched:
             admin = False
-            repos_list = [x.follows_repository for x in
-                          Session().query(UserFollowing).filter(
-                              UserFollowing.user_id ==
-                              self.authuser.user_id).all()]
+            repos_list = Session().query(Repository) \
+                         .join(UserFollowing) \
+                         .filter(UserFollowing.user_id ==
+                                 request.authuser.user_id).all()
         else:
             admin = True
-            repos_list = Session().query(Repository)\
-                         .filter(Repository.user_id ==
-                                 self.authuser.user_id)\
-                         .order_by(func.lower(Repository.repo_name)).all()
+            repos_list = Session().query(Repository) \
+                         .filter(Repository.owner_id ==
+                                 request.authuser.user_id).all()
 
-        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
-                                                   admin=admin)
-        #json used to render the grid
-        return json.dumps(repos_data)
+        return RepoModel().get_repos_as_dict(repos_list, admin=admin)
 
     def my_account(self):
-        """
-        GET /_admin/my_account Displays info about my account
-        """
-        # url('my_account')
         c.active = 'profile'
         self.__load_data()
-        c.perm_user = AuthUser(user_id=self.authuser.user_id)
-        c.ip_addr = self.ip_addr
+        c.perm_user = AuthUser(user_id=request.authuser.user_id)
         managed_fields = auth_modules.get_managed_fields(c.user)
-        def_user_perms = User.get_default_user().AuthUser.permissions['global']
+        def_user_perms = AuthUser(dbuser=User.get_default_user()).permissions['global']
         if 'hg.register.none' in def_user_perms:
             managed_fields.extend(['username', 'firstname', 'lastname', 'email'])
 
@@ -111,8 +99,8 @@
         update = False
         if request.POST:
             _form = UserForm(edit=True,
-                             old_data={'user_id': self.authuser.user_id,
-                                       'email': self.authuser.email})()
+                             old_data={'user_id': request.authuser.user_id,
+                                       'email': request.authuser.email})()
             form_result = {}
             try:
                 post_data = dict(request.POST)
@@ -124,7 +112,7 @@
                               'new_password', 'password_confirmation',
                              ] + managed_fields
 
-                UserModel().update(self.authuser.user_id, form_result,
+                UserModel().update(request.authuser.user_id, form_result,
                                    skip_attrs=skip_attrs)
                 h.flash(_('Your account was updated successfully'),
                         category='success')
@@ -141,10 +129,10 @@
                     force_defaults=False)
             except Exception:
                 log.error(traceback.format_exc())
-                h.flash(_('Error occurred during update of user %s') \
+                h.flash(_('Error occurred during update of user %s')
                         % form_result.get('username'), category='error')
         if update:
-            return redirect('my_account')
+            raise HTTPFound(location='my_account')
         return htmlfill.render(
             render('admin/my_account/my_account.html'),
             defaults=defaults,
@@ -159,10 +147,10 @@
         c.can_change_password = 'password' not in managed_fields
 
         if request.POST and c.can_change_password:
-            _form = PasswordChangeForm(self.authuser.username)()
+            _form = PasswordChangeForm(request.authuser.username)()
             try:
                 form_result = _form.to_python(request.POST)
-                UserModel().update(self.authuser.user_id, form_result)
+                UserModel().update(request.authuser.user_id, form_result)
                 Session().commit()
                 h.flash(_("Successfully updated password"), category='success')
             except formencode.Invalid as errors:
@@ -183,7 +171,7 @@
         c.active = 'repos'
         self.__load_data()
 
-        #json used to render the grid
+        # data used to render the grid
         c.data = self._load_my_repos_data()
         return render('admin/my_account/my_account.html')
 
@@ -191,15 +179,14 @@
         c.active = 'watched'
         self.__load_data()
 
-        #json used to render the grid
+        # data used to render the grid
         c.data = self._load_my_repos_data(watched=True)
         return render('admin/my_account/my_account.html')
 
     def my_account_perms(self):
         c.active = 'perms'
         self.__load_data()
-        c.perm_user = AuthUser(user_id=self.authuser.user_id)
-        c.ip_addr = self.ip_addr
+        c.perm_user = AuthUser(user_id=request.authuser.user_id)
 
         return render('admin/my_account/my_account.html')
 
@@ -207,7 +194,7 @@
         c.active = 'emails'
         self.__load_data()
 
-        c.user_email_map = UserEmailMap.query()\
+        c.user_email_map = UserEmailMap.query() \
             .filter(UserEmailMap.user == c.user).all()
         return render('admin/my_account/my_account.html')
 
@@ -215,7 +202,7 @@
         email = request.POST.get('new_email')
 
         try:
-            UserModel().add_extra_email(self.authuser.user_id, email)
+            UserModel().add_extra_email(request.authuser.user_id, email)
             Session().commit()
             h.flash(_("Added email %s to user") % email, category='success')
         except formencode.Invalid as error:
@@ -225,15 +212,15 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during email saving'),
                     category='error')
-        return redirect(url('my_account_emails'))
+        raise HTTPFound(location=url('my_account_emails'))
 
     def my_account_emails_delete(self):
         email_id = request.POST.get('del_email_id')
         user_model = UserModel()
-        user_model.delete_extra_email(self.authuser.user_id, email_id)
+        user_model.delete_extra_email(request.authuser.user_id, email_id)
         Session().commit()
         h.flash(_("Removed email from user"), category='success')
-        return redirect(url('my_account_emails'))
+        raise HTTPFound(location=url('my_account_emails'))
 
     def my_account_api_keys(self):
         c.active = 'api_keys'
@@ -247,31 +234,28 @@
             (str(60 * 24 * 30), _('1 month')),
         ]
         c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
-        c.user_api_keys = ApiKeyModel().get_api_keys(self.authuser.user_id,
+        c.user_api_keys = ApiKeyModel().get_api_keys(request.authuser.user_id,
                                                      show_expired=show_expired)
         return render('admin/my_account/my_account.html')
 
     def my_account_api_keys_add(self):
         lifetime = safe_int(request.POST.get('lifetime'), -1)
         description = request.POST.get('description')
-        ApiKeyModel().create(self.authuser.user_id, description, lifetime)
+        ApiKeyModel().create(request.authuser.user_id, description, lifetime)
         Session().commit()
         h.flash(_("API key successfully created"), category='success')
-        return redirect(url('my_account_api_keys'))
+        raise HTTPFound(location=url('my_account_api_keys'))
 
     def my_account_api_keys_delete(self):
         api_key = request.POST.get('del_api_key')
-        user_id = self.authuser.user_id
         if request.POST.get('del_api_key_builtin'):
-            user = User.get(user_id)
-            if user is not None:
-                user.api_key = generate_api_key()
-                Session().add(user)
-                Session().commit()
-                h.flash(_("API key successfully reset"), category='success')
+            user = User.get(request.authuser.user_id)
+            user.api_key = generate_api_key()
+            Session().commit()
+            h.flash(_("API key successfully reset"), category='success')
         elif api_key:
-            ApiKeyModel().delete(api_key, self.authuser.user_id)
+            ApiKeyModel().delete(api_key, request.authuser.user_id)
             Session().commit()
             h.flash(_("API key successfully deleted"), category='success')
 
-        return redirect(url('my_account_api_keys'))
+        raise HTTPFound(location=url('my_account_api_keys'))
--- a/kallithea/controllers/admin/notifications.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,175 +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.controllers.admin.notifications
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-notifications controller for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Nov 23, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-import traceback
-
-from pylons import request
-from pylons import tmpl_context as c
-from pylons.controllers.util import abort
-from webob.exc import HTTPBadRequest
-
-from kallithea.model.db import Notification
-from kallithea.model.notification import NotificationModel
-from kallithea.model.meta import Session
-from kallithea.lib.auth import LoginRequired, NotAnonymous
-from kallithea.lib.base import BaseController, render
-from kallithea.lib import helpers as h
-from kallithea.lib.helpers import Page
-from kallithea.lib.utils2 import safe_int
-
-
-log = logging.getLogger(__name__)
-
-
-class NotificationsController(BaseController):
-    """REST Controller styled on the Atom Publishing Protocol"""
-    # To properly map this controller, ensure your config/routing.py
-    # file has a resource setup:
-    #     map.resource('notification', 'notifications', controller='_admin/notifications',
-    #         path_prefix='/_admin', name_prefix='_admin_')
-
-    @LoginRequired()
-    @NotAnonymous()
-    def __before__(self):
-        super(NotificationsController, self).__before__()
-
-    def index(self, format='html'):
-        """GET /_admin/notifications: All items in the collection"""
-        # url('notifications')
-        c.user = self.authuser
-        notif = NotificationModel().get_for_user(self.authuser.user_id,
-                                            filter_=request.GET.getall('type'))
-
-        p = safe_int(request.GET.get('page', 1), 1)
-        c.notifications = Page(notif, page=p, items_per_page=10)
-        c.pull_request_type = Notification.TYPE_PULL_REQUEST
-        c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
-                          Notification.TYPE_PULL_REQUEST_COMMENT]
-
-        _current_filter = request.GET.getall('type')
-        c.current_filter = 'all'
-        if _current_filter == [c.pull_request_type]:
-            c.current_filter = 'pull_request'
-        elif _current_filter == c.comment_type:
-            c.current_filter = 'comment'
-
-        return render('admin/notifications/notifications.html')
-
-    def mark_all_read(self):
-        if request.environ.get('HTTP_X_PARTIAL_XHR'):
-            nm = NotificationModel()
-            # mark all read
-            nm.mark_all_read_for_user(self.authuser.user_id,
-                                      filter_=request.GET.getall('type'))
-            Session().commit()
-            c.user = self.authuser
-            notif = nm.get_for_user(self.authuser.user_id,
-                                    filter_=request.GET.getall('type'))
-            c.notifications = Page(notif, page=1, items_per_page=10)
-            return render('admin/notifications/notifications_data.html')
-
-    def create(self):
-        """POST /_admin/notifications: Create a new item"""
-        # url('notifications')
-
-    def new(self, format='html'):
-        """GET /_admin/notifications/new: Form to create a new item"""
-        # url('new_notification')
-
-    def update(self, notification_id):
-        """PUT /_admin/notifications/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('notification', notification_id=ID),
-        #           method='put')
-        # url('notification', notification_id=ID)
-        try:
-            no = Notification.get(notification_id)
-            owner = all(un.user.user_id == c.authuser.user_id
-                        for un in no.notifications_to_users)
-            if h.HasPermissionAny('hg.admin')() or owner:
-                # deletes only notification2user
-                NotificationModel().mark_read(c.authuser.user_id, no)
-                Session().commit()
-                return 'ok'
-        except Exception:
-            Session().rollback()
-            log.error(traceback.format_exc())
-        raise HTTPBadRequest()
-
-    def delete(self, notification_id):
-        """DELETE /_admin/notifications/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('notification', notification_id=ID),
-        #           method='delete')
-        # url('notification', notification_id=ID)
-        try:
-            no = Notification.get(notification_id)
-            owner = any(un.user.user_id == c.authuser.user_id
-                        for un in no.notifications_to_users)
-            if h.HasPermissionAny('hg.admin')() or owner:
-                # deletes only notification2user
-                NotificationModel().delete(c.authuser.user_id, no)
-                Session().commit()
-                return 'ok'
-        except Exception:
-            Session().rollback()
-            log.error(traceback.format_exc())
-        raise HTTPBadRequest()
-
-    def show(self, notification_id, format='html'):
-        """GET /_admin/notifications/id: Show a specific item"""
-        # url('notification', notification_id=ID)
-        c.user = self.authuser
-        no = Notification.get(notification_id)
-
-        owner = any(un.user.user_id == c.authuser.user_id
-                    for un in no.notifications_to_users)
-        repo_admin = h.HasRepoPermissionAny('repository.admin')
-        if no and (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
-            unotification = NotificationModel()\
-                            .get_user_notification(c.user.user_id, no)
-
-            # if this association to user is not valid, we don't want to show
-            # this message
-            if unotification is not None:
-                if not unotification.read:
-                    unotification.mark_as_read()
-                    Session().commit()
-                c.notification = no
-
-                return render('admin/notifications/show_notification.html')
-
-        return abort(403)
-
-    def edit(self, notification_id, format='html'):
-        """GET /_admin/notifications/id/edit: Form to edit an existing item"""
-        # url('edit_notification', notification_id=ID)
--- a/kallithea/controllers/admin/permissions.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/permissions.py	Sun Mar 31 21:28:56 2019 +0200
@@ -31,12 +31,13 @@
 import formencode
 from formencode import htmlfill
 
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator, AuthUser
 from kallithea.lib.base import BaseController, render
 from kallithea.model.forms import DefaultPermissionsForm
 from kallithea.model.permission import PermissionModel
@@ -53,9 +54,9 @@
     #     map.resource('permission', 'permissions')
 
     @LoginRequired()
-    @HasPermissionAllDecorator('hg.admin')
-    def __before__(self):
-        super(PermissionsController, self).__before__()
+    @HasPermissionAnyDecorator('hg.admin')
+    def _before(self, *args, **kwargs):
+        super(PermissionsController, self)._before(*args, **kwargs)
 
     def __load_data(self):
         c.repo_perms_choices = [('repository.none', _('None'),),
@@ -139,7 +140,7 @@
                 h.flash(_('Error occurred during update of permissions'),
                         category='error')
 
-            return redirect(url('admin_permissions'))
+            raise HTTPFound(location=url('admin_permissions'))
 
         c.user = User.get_default_user()
         defaults = {'anonymous': c.user.active}
@@ -184,7 +185,7 @@
     def permission_ips(self):
         c.active = 'ips'
         c.user = User.get_default_user()
-        c.user_ip_map = UserIpMap.query()\
+        c.user_ip_map = UserIpMap.query() \
                         .filter(UserIpMap.user == c.user).all()
 
         return render('admin/permissions/permissions.html')
@@ -192,5 +193,5 @@
     def permission_perms(self):
         c.active = 'perms'
         c.user = User.get_default_user()
-        c.perm_user = c.user.AuthUser
+        c.perm_user = AuthUser(dbuser=c.user)
         return render('admin/permissions/permissions.html')
--- a/kallithea/controllers/admin/repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -32,16 +32,16 @@
 
 from formencode import htmlfill
 
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect
-from pylons.i18n.translation import _, ungettext
+from tg import request, tmpl_context as c, app_globals
+from tg.i18n import ugettext as _, ungettext
+from webob.exc import HTTPFound, HTTPForbidden, HTTPNotFound, HTTPInternalServerError
 
 import kallithea
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
-from kallithea.lib.compat import json
 from kallithea.lib.auth import LoginRequired, \
-    HasRepoGroupPermissionAnyDecorator, HasRepoGroupPermissionAll, \
-    HasPermissionAll
+    HasRepoGroupPermissionLevelDecorator, HasRepoGroupPermissionLevel, \
+    HasPermissionAny
 from kallithea.lib.base import BaseController, render
 from kallithea.model.db import RepoGroup, Repository
 from kallithea.model.scm import RepoGroupList, AvailableRepoGroupChoices
@@ -49,7 +49,6 @@
 from kallithea.model.forms import RepoGroupForm, RepoGroupPermsForm
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
-from webob.exc import HTTPInternalServerError, HTTPNotFound
 from kallithea.lib.utils2 import safe_int
 from sqlalchemy.sql.expression import func
 
@@ -59,24 +58,20 @@
 
 class RepoGroupsController(BaseController):
 
-    @LoginRequired()
-    def __before__(self):
-        super(RepoGroupsController, self).__before__()
+    @LoginRequired(allow_default_user=True)
+    def _before(self, *args, **kwargs):
+        super(RepoGroupsController, self)._before(*args, **kwargs)
 
     def __load_defaults(self, extras=(), exclude=()):
         """extras is used for keeping current parent ignoring permissions
         exclude is used for not moving group to itself TODO: also exclude descendants
         Note: only admin can create top level groups
         """
-        repo_groups = AvailableRepoGroupChoices([], ['group.admin'], extras)
+        repo_groups = AvailableRepoGroupChoices([], 'admin', extras)
         exclude_group_ids = set(rg.group_id for rg in exclude)
         c.repo_groups = [rg for rg in repo_groups
                          if rg[0] not in exclude_group_ids]
 
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.user_groups_array = repo_model.get_user_groups_js()
-
     def __load_data(self, group_id):
         """
         Load defaults settings for edit, and update
@@ -100,24 +95,20 @@
         return data
 
     def _revoke_perms_on_yourself(self, form_result):
-        _up = filter(lambda u: c.authuser.username == u[0],
+        _up = filter(lambda u: request.authuser.username == u[0],
                      form_result['perms_updates'])
-        _new = filter(lambda u: c.authuser.username == u[0],
+        _new = filter(lambda u: request.authuser.username == u[0],
                       form_result['perms_new'])
         if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
             return True
         return False
 
     def index(self, format='html'):
-        """GET /repo_groups: All items in the collection"""
-        # url('repos_groups')
-        _list = RepoGroup.query()\
-                    .order_by(func.lower(RepoGroup.group_name))\
-                    .all()
-        group_iter = RepoGroupList(_list, perm_set=['group.admin'])
+        _list = RepoGroup.query(sorted=True).all()
+        group_iter = RepoGroupList(_list, perm_level='admin')
         repo_groups_data = []
         total_records = len(group_iter)
-        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
+        _tmpl_lookup = app_globals.mako_lookup
         template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 
         repo_group_name = lambda repo_group_name, children_groups: (
@@ -140,25 +131,20 @@
                 "group_name": repo_group_name(repo_gr.group_name, children_groups),
                 "desc": h.escape(repo_gr.group_description),
                 "repos": repo_count,
-                "owner": h.person(repo_gr.user),
+                "owner": h.person(repo_gr.owner),
                 "action": repo_group_actions(repo_gr.group_id, repo_gr.group_name,
                                              repo_count)
             })
 
-        c.data = json.dumps({
-            "totalRecords": total_records,
-            "startIndex": 0,
+        c.data = {
             "sort": None,
             "dir": "asc",
             "records": repo_groups_data
-        })
+        }
 
         return render('admin/repo_groups/repo_groups.html')
 
     def create(self):
-        """POST /repo_groups: Create a new item"""
-        # url('repos_groups')
-
         self.__load_defaults()
 
         # permissions for can create group based on parent_id are checked
@@ -169,12 +155,12 @@
             gr = RepoGroupModel().create(
                 group_name=form_result['group_name'],
                 group_description=form_result['group_description'],
-                parent=form_result['group_parent_id'],
-                owner=self.authuser.user_id, # TODO: make editable
+                parent=form_result['parent_group_id'],
+                owner=request.authuser.user_id, # TODO: make editable
                 copy_permissions=form_result['group_copy_permissions']
             )
             Session().commit()
-            #TODO: in futureaction_logger(, '', '', '', self.sa)
+            # TODO: in future action_logger(, '', '', '')
         except formencode.Invalid as errors:
             return htmlfill.render(
                 render('admin/repo_groups/repo_group_add.html'),
@@ -185,20 +171,18 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of repository group %s') \
+            h.flash(_('Error occurred during creation of repository group %s')
                     % request.POST.get('group_name'), category='error')
-            parent_group_id = form_result['group_parent_id']
-            #TODO: maybe we should get back to the main view, not the admin one
-            return redirect(url('repos_groups', parent_group=parent_group_id))
+            parent_group_id = form_result['parent_group_id']
+            # TODO: maybe we should get back to the main view, not the admin one
+            raise HTTPFound(location=url('repos_groups', parent_group=parent_group_id))
         h.flash(_('Created repository group %s') % gr.group_name,
                 category='success')
-        return redirect(url('repos_group_home', group_name=gr.group_name))
+        raise HTTPFound(location=url('repos_group_home', group_name=gr.group_name))
 
     def new(self):
-        """GET /repo_groups/new: Form to create a new item"""
-        # url('new_repos_group')
-        if HasPermissionAll('hg.admin')('group create'):
-            #we're global admin, we're ok and we can create TOP level groups
+        if HasPermissionAny('hg.admin')('group create'):
+            # we're global admin, we're ok and we can create TOP level groups
             pass
         else:
             # we pass in parent group into creation form, thus we know
@@ -206,31 +190,23 @@
             group_id = safe_int(request.GET.get('parent_group'))
             group = RepoGroup.get(group_id) if group_id else None
             group_name = group.group_name if group else None
-            if HasRepoGroupPermissionAll('group.admin')(group_name, 'group create'):
+            if HasRepoGroupPermissionLevel('admin')(group_name, 'group create'):
                 pass
             else:
-                return abort(403)
+                raise HTTPForbidden()
 
         self.__load_defaults()
         return render('admin/repo_groups/repo_group_add.html')
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def update(self, group_name):
-        """PUT /repo_groups/group_name: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('repos_group', group_name=GROUP_NAME),
-        #           method='put')
-        # url('repos_group', group_name=GROUP_NAME)
-
-        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.repo_group = RepoGroup.guess_instance(group_name)
         self.__load_defaults(extras=[c.repo_group.parent_group],
                              exclude=[c.repo_group])
 
         # TODO: kill allow_empty_group - it is only used for redundant form validation!
-        if HasPermissionAll('hg.admin')('group edit'):
-            #we're global admin, we're ok and we can create TOP level groups
+        if HasPermissionAny('hg.admin')('group edit'):
+            # we're global admin, we're ok and we can create TOP level groups
             allow_empty_group = True
         elif not c.repo_group.parent_group:
             allow_empty_group = True
@@ -247,13 +223,13 @@
 
             new_gr = RepoGroupModel().update(group_name, form_result)
             Session().commit()
-            h.flash(_('Updated repository group %s') \
+            h.flash(_('Updated repository group %s')
                     % form_result['group_name'], category='success')
             # we now have new name !
             group_name = new_gr.group_name
-            #TODO: in future action_logger(, '', '', '', self.sa)
+            # TODO: in future action_logger(, '', '', '')
         except formencode.Invalid as errors:
-
+            c.active = 'settings'
             return htmlfill.render(
                 render('admin/repo_groups/repo_group_edit.html'),
                 defaults=errors.value,
@@ -263,48 +239,40 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of repository group %s') \
+            h.flash(_('Error occurred during update of repository group %s')
                     % request.POST.get('group_name'), category='error')
 
-        return redirect(url('edit_repo_group', group_name=group_name))
+        raise HTTPFound(location=url('edit_repo_group', group_name=group_name))
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def delete(self, group_name):
-        """DELETE /repo_groups/group_name: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('repos_group', group_name=GROUP_NAME),
-        #           method='delete')
-        # url('repos_group', group_name=GROUP_NAME)
-
-        gr = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        gr = c.repo_group = RepoGroup.guess_instance(group_name)
         repos = gr.repositories.all()
         if repos:
             h.flash(_('This group contains %s repositories and cannot be '
                       'deleted') % len(repos), category='warning')
-            return redirect(url('repos_groups'))
+            raise HTTPFound(location=url('repos_groups'))
 
         children = gr.children.all()
         if children:
             h.flash(_('This group contains %s subgroups and cannot be deleted'
                       % (len(children))), category='warning')
-            return redirect(url('repos_groups'))
+            raise HTTPFound(location=url('repos_groups'))
 
         try:
             RepoGroupModel().delete(group_name)
             Session().commit()
             h.flash(_('Removed repository group %s') % group_name,
                     category='success')
-            #TODO: in future action_logger(, '', '', '', self.sa)
+            # TODO: in future action_logger(, '', '', '')
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('Error occurred during deletion of repository group %s')
                     % group_name, category='error')
 
         if gr.parent_group:
-            return redirect(url('repos_group_home', group_name=gr.parent_group.group_name))
-        return redirect(url('repos_groups'))
+            raise HTTPFound(location=url('repos_group_home', group_name=gr.parent_group.group_name))
+        raise HTTPFound(location=url('repos_groups'))
 
     def show_by_name(self, group_name):
         """
@@ -317,42 +285,27 @@
             return self.show(group_name)
         raise HTTPNotFound
 
-    @HasRepoGroupPermissionAnyDecorator('group.read', 'group.write',
-                                         'group.admin')
+    @HasRepoGroupPermissionLevelDecorator('read')
     def show(self, group_name):
-        """GET /repo_groups/group_name: Show a specific item"""
-        # url('repos_group', group_name=GROUP_NAME)
         c.active = 'settings'
 
-        c.group = c.repo_group = RepoGroupModel()._get_repo_group(group_name)
-        c.group_repos = c.group.repositories.all()
+        c.group = c.repo_group = RepoGroup.guess_instance(group_name)
 
-        #overwrite our cached list with current filter
-        c.repo_cnt = 0
-
-        groups = RepoGroup.query().order_by(RepoGroup.group_name)\
-            .filter(RepoGroup.group_parent_id == c.group.group_id).all()
-        c.groups = self.scm_model.get_repo_groups(groups)
+        groups = RepoGroup.query(sorted=True).filter_by(parent_group=c.group).all()
+        repo_groups_list = self.scm_model.get_repo_groups(groups)
 
-        c.repos_list = Repository.query()\
-                        .filter(Repository.group_id == c.group.group_id)\
-                        .order_by(func.lower(Repository.repo_name))\
-                        .all()
-
-        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
-                                                   admin=False)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        repos_list = Repository.query(sorted=True).filter_by(group=c.group).all()
+        c.data = RepoModel().get_repos_as_dict(repos_list,
+                                               repo_groups_list=repo_groups_list,
+                                               short_name=True)
 
         return render('admin/repo_groups/repo_group_show.html')
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def edit(self, group_name):
-        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
-        # url('edit_repo_group', group_name=GROUP_NAME)
         c.active = 'settings'
 
-        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.repo_group = RepoGroup.guess_instance(group_name)
         self.__load_defaults(extras=[c.repo_group.parent_group],
                              exclude=[c.repo_group])
         defaults = self.__load_data(c.repo_group.group_id)
@@ -364,21 +317,17 @@
             force_defaults=False
         )
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def edit_repo_group_advanced(self, group_name):
-        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
-        # url('edit_repo_group', group_name=GROUP_NAME)
         c.active = 'advanced'
-        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.repo_group = RepoGroup.guess_instance(group_name)
 
         return render('admin/repo_groups/repo_group_edit.html')
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def edit_repo_group_perms(self, group_name):
-        """GET /repo_groups/group_name/edit: Form to edit an existing item"""
-        # url('edit_repo_group', group_name=GROUP_NAME)
         c.active = 'perms'
-        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.repo_group = RepoGroup.guess_instance(group_name)
         self.__load_defaults()
         defaults = self.__load_data(c.repo_group.group_id)
 
@@ -389,7 +338,7 @@
             force_defaults=False
         )
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def update_perms(self, group_name):
         """
         Update permissions for given repository group
@@ -397,14 +346,14 @@
         :param group_name:
         """
 
-        c.repo_group = RepoGroupModel()._get_repo_group(group_name)
+        c.repo_group = RepoGroup.guess_instance(group_name)
         valid_recursive_choices = ['none', 'repos', 'groups', 'all']
         form_result = RepoGroupPermsForm(valid_recursive_choices)().to_python(request.POST)
-        if not c.authuser.is_admin:
+        if not request.authuser.is_admin:
             if self._revoke_perms_on_yourself(form_result):
                 msg = _('Cannot revoke permission for yourself as admin')
                 h.flash(msg, category='warning')
-                return redirect(url('edit_repo_group_perms', group_name=group_name))
+                raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
         recursive = form_result['recursive']
         # iterate over all members(if in recursive mode) of this groups and
         # set the permissions !
@@ -413,20 +362,15 @@
                                              form_result['perms_new'],
                                              form_result['perms_updates'],
                                              recursive)
-        #TODO: implement this
-        #action_logger(self.authuser, 'admin_changed_repo_permissions',
-        #              repo_name, self.ip_addr, self.sa)
+        # TODO: implement this
+        #action_logger(request.authuser, 'admin_changed_repo_permissions',
+        #              repo_name, request.ip_addr)
         Session().commit()
         h.flash(_('Repository group permissions updated'), category='success')
-        return redirect(url('edit_repo_group_perms', group_name=group_name))
+        raise HTTPFound(location=url('edit_repo_group_perms', group_name=group_name))
 
-    @HasRepoGroupPermissionAnyDecorator('group.admin')
+    @HasRepoGroupPermissionLevelDecorator('admin')
     def delete_perms(self, group_name):
-        """
-        DELETE an existing repository group permission user
-
-        :param group_name:
-        """
         try:
             obj_type = request.POST.get('obj_type')
             obj_id = None
@@ -435,8 +379,8 @@
             elif obj_type == 'user_group':
                 obj_id = safe_int(request.POST.get('user_group_id'))
 
-            if not c.authuser.is_admin:
-                if obj_type == 'user' and c.authuser.user_id == obj_id:
+            if not request.authuser.is_admin:
+                if obj_type == 'user' and request.authuser.user_id == obj_id:
                     msg = _('Cannot revoke permission for yourself as admin')
                     h.flash(msg, category='warning')
                     raise Exception('revoke admin permission on self')
--- a/kallithea/controllers/admin/repos.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/repos.py	Sun Mar 31 21:28:56 2019 +0200
@@ -29,26 +29,24 @@
 import traceback
 import formencode
 from formencode import htmlfill
-from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
 from sqlalchemy.sql.expression import func
+from webob.exc import HTTPFound, HTTPInternalServerError, HTTPForbidden, HTTPNotFound
 
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib.auth import LoginRequired, \
-    HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny, \
-    HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.utils import action_logger, jsonify
+    HasRepoPermissionLevelDecorator, NotAnonymous, HasPermissionAny
+from kallithea.lib.base import BaseRepoController, render, jsonify
+from kallithea.lib.utils import action_logger
 from kallithea.lib.vcs import RepositoryError
 from kallithea.model.meta import Session
-from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\
+from kallithea.model.db import User, Repository, UserFollowing, RepoGroup, \
     Setting, RepositoryField
 from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
 from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices, RepoList
 from kallithea.model.repo import RepoModel
-from kallithea.lib.compat import json
 from kallithea.lib.exceptions import AttachedForksError
 from kallithea.lib.utils2 import safe_int
 
@@ -62,81 +60,68 @@
     # file has a resource setup:
     #     map.resource('repo', 'repos')
 
-    @LoginRequired()
-    def __before__(self):
-        super(ReposController, self).__before__()
+    @LoginRequired(allow_default_user=True)
+    def _before(self, *args, **kwargs):
+        super(ReposController, self)._before(*args, **kwargs)
 
-    def _load_repo(self, repo_name):
-        repo_obj = Repository.get_by_repo_name(repo_name)
+    def _load_repo(self):
+        repo_obj = c.db_repo
 
         if repo_obj is None:
-            h.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+            h.not_mapped_error(c.repo_name)
+            raise HTTPFound(location=url('repos'))
 
         return repo_obj
 
     def __load_defaults(self, repo=None):
         top_perms = ['hg.create.repository']
-        repo_group_perms = ['group.admin']
         if HasPermissionAny('hg.create.write_on_repogroup.true')():
-            repo_group_perms.append('group.write')
+            repo_group_perm_level = 'write'
+        else:
+            repo_group_perm_level = 'admin'
         extras = [] if repo is None else [repo.group]
 
-        c.repo_groups = AvailableRepoGroupChoices(top_perms, repo_group_perms, extras)
+        c.repo_groups = AvailableRepoGroupChoices(top_perms, repo_group_perm_level, extras)
 
         c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo)
 
-    def __load_data(self, repo_name=None):
+    def __load_data(self):
         """
         Load defaults settings for edit, and update
-
-        :param repo_name:
         """
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         self.__load_defaults(c.repo_info)
 
-        defaults = RepoModel()._get_defaults(repo_name)
+        defaults = RepoModel()._get_defaults(c.repo_name)
         defaults['clone_uri'] = c.repo_info.clone_uri_hidden # don't show password
 
         return defaults
 
     def index(self, format='html'):
-        """GET /repos: All items in the collection"""
-        # url('repos')
-        _list = Repository.query()\
-                        .order_by(func.lower(Repository.repo_name))\
-                        .all()
+        _list = Repository.query(sorted=True).all()
 
-        c.repos_list = RepoList(_list, perm_set=['repository.admin'])
-        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
-                                                   admin=True,
-                                                   super_user_actions=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        c.repos_list = RepoList(_list, perm_level='admin')
+        # the repo list will be filtered to only show repos where the user has read permissions
+        repos_data = RepoModel().get_repos_as_dict(c.repos_list, admin=True)
+        # data used to render the grid
+        c.data = repos_data
 
         return render('admin/repos/repos.html')
 
     @NotAnonymous()
     def create(self):
-        """
-        POST /repos: Create a new item"""
-        # url('repos')
-
         self.__load_defaults()
         form_result = {}
-        task_id = None
         try:
             # CanWriteGroup validators checks permissions of this POST
             form_result = RepoForm(repo_groups=c.repo_groups,
-                                   landing_revs=c.landing_revs_choices)()\
+                                   landing_revs=c.landing_revs_choices)() \
                             .to_python(dict(request.POST))
 
             # create is done sometimes async on celery, db transaction
             # management is handled there.
-            task = RepoModel().create(form_result, self.authuser.user_id)
-            from celery.result import BaseAsyncResult
-            if isinstance(task, BaseAsyncResult):
-                task_id = task.task_id
+            task = RepoModel().create(form_result, request.authuser.user_id)
+            task_id = task.task_id
         except formencode.Invalid as errors:
             log.info(errors)
             return htmlfill.render(
@@ -152,15 +137,14 @@
             msg = (_('Error creating repository %s')
                    % form_result.get('repo_name'))
             h.flash(msg, category='error')
-            return redirect(url('home'))
+            raise HTTPFound(location=url('home'))
 
-        return redirect(h.url('repo_creating_home',
+        raise HTTPFound(location=h.url('repo_creating_home',
                               repo_name=form_result['repo_name_full'],
                               task_id=task_id))
 
     @NotAnonymous()
     def create_repository(self):
-        """GET /_admin/create_repository: Form to create a new item"""
         self.__load_defaults()
         if not c.repo_groups:
             raise HTTPForbidden
@@ -184,7 +168,6 @@
             force_defaults=False)
 
     @LoginRequired()
-    @NotAnonymous()
     def repo_creating(self, repo_name):
         c.repo = repo_name
         c.task_id = request.GET.get('task_id')
@@ -193,7 +176,6 @@
         return render('admin/repos/repo_creating.html')
 
     @LoginRequired()
-    @NotAnonymous()
     @jsonify
     def repo_check(self, repo_name):
         c.repo = repo_name
@@ -201,9 +183,9 @@
 
         if task_id and task_id not in ['None']:
             from kallithea import CELERY_ON
-            from celery.result import AsyncResult
+            from kallithea.lib import celerypylons
             if CELERY_ON:
-                task = AsyncResult(task_id)
+                task = celerypylons.result.AsyncResult(task_id)
                 if task.failed():
                     raise HTTPInternalServerError(task.traceback)
 
@@ -227,20 +209,12 @@
             return {'result': True}
         return {'result': False}
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def update(self, repo_name):
-        """
-        PUT /repos/repo_name: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('put_repo', repo_name=ID),
-        #           method='put')
-        # url('put_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         self.__load_defaults(c.repo_info)
         c.active = 'settings'
-        c.repo_fields = RepositoryField.query()\
+        c.repo_fields = RepositoryField.query() \
             .filter(RepositoryField.repository == c.repo_info).all()
 
         repo_model = RepoModel()
@@ -262,14 +236,13 @@
             h.flash(_('Repository %s updated successfully') % repo_name,
                     category='success')
             changed_name = repo.repo_name
-            action_logger(self.authuser, 'admin_updated_repo',
-                              changed_name, self.ip_addr, self.sa)
+            action_logger(request.authuser, 'admin_updated_repo',
+                changed_name, request.ip_addr)
             Session().commit()
         except formencode.Invalid as errors:
             log.info(errors)
-            defaults = self.__load_data(repo_name)
+            defaults = self.__load_data()
             defaults.update(errors.value)
-            c.users_array = repo_model.get_users_js()
             return htmlfill.render(
                 render('admin/repos/repo_edit.html'),
                 defaults=defaults,
@@ -280,26 +253,17 @@
 
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of repository %s') \
+            h.flash(_('Error occurred during update of repository %s')
                     % repo_name, category='error')
-        return redirect(url('edit_repo', repo_name=changed_name))
+        raise HTTPFound(location=url('edit_repo', repo_name=changed_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def delete(self, repo_name):
-        """
-        DELETE /repos/repo_name: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('delete_repo', repo_name=ID),
-        #           method='delete')
-        # url('delete_repo', repo_name=ID)
-
         repo_model = RepoModel()
         repo = repo_model.get_by_repo_name(repo_name)
         if not repo:
             h.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+            raise HTTPFound(location=url('repos'))
         try:
             _forks = repo.forks.count()
             handle_forks = None
@@ -312,8 +276,8 @@
                     handle_forks = 'delete'
                     h.flash(_('Deleted %s forks') % _forks, category='success')
             repo_model.delete(repo, forks=handle_forks)
-            action_logger(self.authuser, 'admin_deleted_repo',
-                  repo_name, self.ip_addr, self.sa)
+            action_logger(request.authuser, 'admin_deleted_repo',
+                repo_name, request.ip_addr)
             ScmModel().mark_for_invalidation(repo_name)
             h.flash(_('Deleted repository %s') % repo_name, category='success')
             Session().commit()
@@ -327,18 +291,14 @@
                     category='error')
 
         if repo.group:
-            return redirect(url('repos_group_home', group_name=repo.group.group_name))
-        return redirect(url('repos'))
+            raise HTTPFound(location=url('repos_group_home', group_name=repo.group.group_name))
+        raise HTTPFound(location=url('repos'))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        defaults = self.__load_data(repo_name)
-        c.repo_fields = RepositoryField.query()\
+        defaults = self.__load_data()
+        c.repo_fields = RepositoryField.query() \
             .filter(RepositoryField.repository == c.repo_info).all()
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
         c.active = 'settings'
         return htmlfill.render(
             render('admin/repos/repo_edit.html'),
@@ -346,14 +306,9 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_permissions(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.user_groups_array = repo_model.get_user_groups_js()
+        c.repo_info = self._load_repo()
         c.active = 'permissions'
         defaults = RepoModel()._get_defaults(repo_name)
 
@@ -363,19 +318,19 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_permissions_update(self, repo_name):
         form = RepoPermsForm()().to_python(request.POST)
         RepoModel()._update_permissions(repo_name, form['perms_new'],
                                         form['perms_updates'])
-        #TODO: implement this
-        #action_logger(self.authuser, 'admin_changed_repo_permissions',
-        #              repo_name, self.ip_addr, self.sa)
+        # TODO: implement this
+        #action_logger(request.authuser, 'admin_changed_repo_permissions',
+        #              repo_name, request.ip_addr)
         Session().commit()
         h.flash(_('Repository permissions updated'), category='success')
-        return redirect(url('edit_repo_perms', repo_name=repo_name))
+        raise HTTPFound(location=url('edit_repo_perms', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_permissions_revoke(self, repo_name):
         try:
             obj_type = request.POST.get('obj_type')
@@ -384,6 +339,7 @@
                 obj_id = safe_int(request.POST.get('user_id'))
             elif obj_type == 'user_group':
                 obj_id = safe_int(request.POST.get('user_group_id'))
+            else: assert False
 
             if obj_type == 'user':
                 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
@@ -391,30 +347,30 @@
                 RepoModel().revoke_user_group_permission(
                     repo=repo_name, group_name=obj_id
                 )
-            #TODO: implement this
-            #action_logger(self.authuser, 'admin_revoked_repo_permissions',
-            #              repo_name, self.ip_addr, self.sa)
+            else: assert False
+            # TODO: implement this
+            #action_logger(request.authuser, 'admin_revoked_repo_permissions',
+            #              repo_name, request.ip_addr)
             Session().commit()
         except Exception:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during revoking of permission'),
                     category='error')
             raise HTTPInternalServerError()
+        return []
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_fields(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
-        c.repo_fields = RepositoryField.query()\
+        c.repo_info = self._load_repo()
+        c.repo_fields = RepositoryField.query() \
             .filter(RepositoryField.repository == c.repo_info).all()
         c.active = 'fields'
         if request.POST:
 
-            return redirect(url('repo_edit_fields'))
+            raise HTTPFound(location=url('repo_edit_fields'))
         return render('admin/repos/repo_edit.html')
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def create_repo_field(self, repo_name):
         try:
             form_result = RepoFieldForm()().to_python(dict(request.POST))
@@ -427,15 +383,14 @@
             new_field.field_label = form_result['new_field_label']
             Session().add(new_field)
             Session().commit()
+        except formencode.Invalid as e:
+            h.flash(_('Field validation error: %s') % e.msg, category='error')
         except Exception as e:
             log.error(traceback.format_exc())
-            msg = _('An error occurred during creation of field')
-            if isinstance(e, formencode.Invalid):
-                msg += ". " + e.msg
-            h.flash(msg, category='error')
-        return redirect(url('edit_repo_fields', repo_name=repo_name))
+            h.flash(_('An error occurred during creation of field: %r') % e, category='error')
+        raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def delete_repo_field(self, repo_name, field_id):
         field = RepositoryField.get_or_404(field_id)
         try:
@@ -445,39 +400,37 @@
             log.error(traceback.format_exc())
             msg = _('An error occurred during removal of field')
             h.flash(msg, category='error')
-        return redirect(url('edit_repo_fields', repo_name=repo_name))
+        raise HTTPFound(location=url('edit_repo_fields', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_advanced(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         c.default_user_id = User.get_default_user().user_id
-        c.in_public_journal = UserFollowing.query()\
-            .filter(UserFollowing.user_id == c.default_user_id)\
+        c.in_public_journal = UserFollowing.query() \
+            .filter(UserFollowing.user_id == c.default_user_id) \
             .filter(UserFollowing.follows_repository == c.repo_info).scalar()
 
-        _repos = Repository.query().order_by(Repository.repo_name).all()
-        read_access_repos = RepoList(_repos)
+        _repos = Repository.query(sorted=True).all()
+        read_access_repos = RepoList(_repos, perm_level='read')
         c.repos_list = [(None, _('-- Not a fork --'))]
         c.repos_list += [(x.repo_id, x.repo_name)
                          for x in read_access_repos
                          if x.repo_id != c.repo_info.repo_id]
 
         defaults = {
-            'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else ''
+            'id_fork_of': c.repo_info.fork_id if c.repo_info.fork_id else ''
         }
 
         c.active = 'advanced'
         if request.POST:
-            return redirect(url('repo_edit_advanced'))
+            raise HTTPFound(location=url('repo_edit_advanced'))
         return htmlfill.render(
             render('admin/repos/repo_edit.html'),
             defaults=defaults,
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_advanced_journal(self, repo_name):
         """
         Sets this repository to be visible in public journal,
@@ -497,10 +450,9 @@
             h.flash(_('An error occurred during setting this'
                       ' repository in public journal'),
                     category='error')
-        return redirect(url('edit_repo_advanced', repo_name=repo_name))
+        raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
 
-
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_advanced_fork(self, repo_name):
         """
         Mark given repository as a fork of another
@@ -510,7 +462,7 @@
         try:
             fork_id = request.POST.get('id_fork_of')
             repo = ScmModel().mark_as_fork(repo_name, fork_id,
-                                           self.authuser.username)
+                                           request.authuser.username)
             fork = repo.fork.repo_name if repo.fork else _('Nothing')
             Session().commit()
             h.flash(_('Marked repository %s as fork of %s') % (repo_name, fork),
@@ -523,9 +475,9 @@
             h.flash(_('An error occurred during this operation'),
                     category='error')
 
-        return redirect(url('edit_repo_advanced', repo_name=repo_name))
+        raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_advanced_locking(self, repo_name):
         """
         Unlock repository when it is locked !
@@ -535,7 +487,7 @@
         try:
             repo = Repository.get_by_repo_name(repo_name)
             if request.POST.get('set_lock'):
-                Repository.lock(repo, c.authuser.user_id)
+                Repository.lock(repo, request.authuser.user_id)
                 h.flash(_('Repository has been locked'), category='success')
             elif request.POST.get('set_unlock'):
                 Repository.unlock(repo)
@@ -544,16 +496,10 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during unlocking'),
                     category='error')
-        return redirect(url('edit_repo_advanced', repo_name=repo_name))
+        raise HTTPFound(location=url('edit_repo_advanced', repo_name=repo_name))
 
-    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+    @HasRepoPermissionLevelDecorator('write')
     def toggle_locking(self, repo_name):
-        """
-        Toggle locking of repository by simple GET call to url
-
-        :param repo_name:
-        """
-
         try:
             repo = Repository.get_by_repo_name(repo_name)
 
@@ -562,24 +508,22 @@
                     Repository.unlock(repo)
                     h.flash(_('Repository has been unlocked'), category='success')
                 else:
-                    Repository.lock(repo, c.authuser.user_id)
+                    Repository.lock(repo, request.authuser.user_id)
                     h.flash(_('Repository has been locked'), category='success')
 
         except Exception as e:
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during unlocking'),
                     category='error')
-        return redirect(url('summary_home', repo_name=repo_name))
+        raise HTTPFound(location=url('summary_home', repo_name=repo_name))
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_caches(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         c.active = 'caches'
         if request.POST:
             try:
-                ScmModel().mark_for_invalidation(repo_name, delete=True)
+                ScmModel().mark_for_invalidation(repo_name)
                 Session().commit()
                 h.flash(_('Cache invalidation successful'),
                         category='success')
@@ -588,31 +532,27 @@
                 h.flash(_('An error occurred during cache invalidation'),
                         category='error')
 
-            return redirect(url('edit_repo_caches', repo_name=c.repo_name))
+            raise HTTPFound(location=url('edit_repo_caches', repo_name=c.repo_name))
         return render('admin/repos/repo_edit.html')
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_remote(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         c.active = 'remote'
         if request.POST:
             try:
-                ScmModel().pull_changes(repo_name, self.authuser.username)
+                ScmModel().pull_changes(repo_name, request.authuser.username)
                 h.flash(_('Pulled from remote location'), category='success')
             except Exception as e:
                 log.error(traceback.format_exc())
                 h.flash(_('An error occurred during pull from remote location'),
                         category='error')
-            return redirect(url('edit_repo_remote', repo_name=c.repo_name))
+            raise HTTPFound(location=url('edit_repo_remote', repo_name=c.repo_name))
         return render('admin/repos/repo_edit.html')
 
-    @HasRepoPermissionAllDecorator('repository.admin')
+    @HasRepoPermissionLevelDecorator('admin')
     def edit_statistics(self, repo_name):
-        """GET /repo_name/settings: Form to edit an existing item"""
-        # url('edit_repo', repo_name=ID)
-        c.repo_info = self._load_repo(repo_name)
+        c.repo_info = self._load_repo()
         repo = c.repo_info.scm_instance
 
         if c.repo_info.stats:
@@ -638,6 +578,6 @@
                 log.error(traceback.format_exc())
                 h.flash(_('An error occurred during deletion of repository stats'),
                         category='error')
-            return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
+            raise HTTPFound(location=url('edit_repo_statistics', repo_name=c.repo_name))
 
         return render('admin/repos/repo_edit.html')
--- a/kallithea/controllers/admin/settings.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,16 +30,18 @@
 import formencode
 
 from formencode import htmlfill
-from pylons import request, tmpl_context as c, url, config
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c, config
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, render
-from kallithea.lib.celerylib import tasks, run_task
+from kallithea.lib.celerylib import tasks
 from kallithea.lib.exceptions import HgsubversionImportError
 from kallithea.lib.utils import repo2db_mapper, set_app_settings
+from kallithea.lib.vcs import VCSError
 from kallithea.model.db import Ui, Repository, Setting
 from kallithea.model.forms import ApplicationSettingsForm, \
     ApplicationUiSettingsForm, ApplicationVisualisationForm
@@ -57,38 +59,30 @@
     #     map.resource('setting', 'settings', controller='admin/settings',
     #         path_prefix='/admin', name_prefix='admin_')
 
-    @LoginRequired()
-    def __before__(self):
-        super(SettingsController, self).__before__()
+    @LoginRequired(allow_default_user=True)
+    def _before(self, *args, **kwargs):
+        super(SettingsController, self)._before(*args, **kwargs)
 
     def _get_hg_ui_settings(self):
         ret = Ui.query().all()
 
-        if not ret:
-            raise Exception('Could not get application ui settings !')
         settings = {}
         for each in ret:
-            k = each.ui_key
+            k = each.ui_section + '_' + each.ui_key
             v = each.ui_value
-            if k == '/':
-                k = 'root_path'
+            if k == 'paths_/':
+                k = 'paths_root_path'
 
-            if k == 'push_ssl':
-                v = str2bool(v)
-
-            if k.find('.') != -1:
-                k = k.replace('.', '_')
+            k = k.replace('.', '_')
 
             if each.ui_section in ['hooks', 'extensions']:
                 v = each.ui_active
 
-            settings[each.ui_section + '_' + k] = v
+            settings[k] = v
         return settings
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_vcs(self):
-        """GET /admin/settings: All items in the collection"""
-        # url('admin_settings')
         c.active = 'vcs'
         if request.POST:
             application_form = ApplicationUiSettingsForm()()
@@ -104,66 +98,37 @@
                      force_defaults=False)
 
             try:
-                sett = Ui.get_by_key('push_ssl')
-                sett.ui_value = form_result['web_push_ssl']
-                Session().add(sett)
                 if c.visual.allow_repo_location_change:
-                    sett = Ui.get_by_key('/')
+                    sett = Ui.get_by_key('paths', '/')
                     sett.ui_value = form_result['paths_root_path']
-                    Session().add(sett)
 
-                #HOOKS
-                sett = Ui.get_by_key(Ui.HOOK_UPDATE)
+                # HOOKS
+                sett = Ui.get_by_key('hooks', Ui.HOOK_UPDATE)
                 sett.ui_active = form_result['hooks_changegroup_update']
-                Session().add(sett)
 
-                sett = Ui.get_by_key(Ui.HOOK_REPO_SIZE)
+                sett = Ui.get_by_key('hooks', Ui.HOOK_REPO_SIZE)
                 sett.ui_active = form_result['hooks_changegroup_repo_size']
-                Session().add(sett)
 
-                sett = Ui.get_by_key(Ui.HOOK_PUSH)
+                sett = Ui.get_by_key('hooks', Ui.HOOK_PUSH_LOG)
                 sett.ui_active = form_result['hooks_changegroup_push_logger']
-                Session().add(sett)
 
-                sett = Ui.get_by_key(Ui.HOOK_PULL)
+                sett = Ui.get_by_key('hooks', Ui.HOOK_PULL_LOG)
                 sett.ui_active = form_result['hooks_outgoing_pull_logger']
 
-                Session().add(sett)
-
                 ## EXTENSIONS
-                sett = Ui.get_by_key('largefiles')
-                if not sett:
-                    #make one if it's not there !
-                    sett = Ui()
-                    sett.ui_key = 'largefiles'
-                    sett.ui_section = 'extensions'
+                sett = Ui.get_or_create('extensions', 'largefiles')
                 sett.ui_active = form_result['extensions_largefiles']
-                Session().add(sett)
 
-                sett = Ui.get_by_key('hgsubversion')
-                if not sett:
-                    #make one if it's not there !
-                    sett = Ui()
-                    sett.ui_key = 'hgsubversion'
-                    sett.ui_section = 'extensions'
-
+                sett = Ui.get_or_create('extensions', 'hgsubversion')
                 sett.ui_active = form_result['extensions_hgsubversion']
                 if sett.ui_active:
                     try:
                         import hgsubversion  # pragma: no cover
                     except ImportError:
                         raise HgsubversionImportError
-                Session().add(sett)
 
-#                sett = Ui.get_by_key('hggit')
-#                if not sett:
-#                    #make one if it's not there !
-#                    sett = Ui()
-#                    sett.ui_key = 'hggit'
-#                    sett.ui_section = 'extensions'
-#
+#                sett = Ui.get_or_create('extensions', 'hggit')
 #                sett.ui_active = form_result['extensions_hggit']
-#                Session().add(sett)
 
                 Session().commit()
 
@@ -189,15 +154,13 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_mapping(self):
-        """GET /admin/settings/mapping: All items in the collection"""
-        # url('admin_settings_mapping')
         c.active = 'mapping'
         if request.POST:
             rm_obsolete = request.POST.get('destroy', False)
             install_git_hooks = request.POST.get('hooks', False)
-            overwrite_git_hooks = request.POST.get('hooks_overwrite', False);
+            overwrite_git_hooks = request.POST.get('hooks_overwrite', False)
             invalidate_cache = request.POST.get('invalidate', False)
             log.debug('rescanning repo location with destroy obsolete=%s, '
                       'install git hooks=%s and '
@@ -206,7 +169,7 @@
             filesystem_repos = ScmModel().repo_scan()
             added, removed = repo2db_mapper(filesystem_repos, rm_obsolete,
                                             install_git_hooks=install_git_hooks,
-                                            user=c.authuser.username,
+                                            user=request.authuser.username,
                                             overwrite_git_hooks=overwrite_git_hooks)
             h.flash(h.literal(_('Repositories successfully rescanned. Added: %s. Removed: %s.') %
                 (', '.join(h.link_to(safe_unicode(repo_name), h.url('summary_home', repo_name=repo_name))
@@ -217,15 +180,15 @@
             if invalidate_cache:
                 log.debug('invalidating all repositories cache')
                 i = 0
-                for repo in Repository.get_all():
+                for repo in Repository.query():
                     try:
-                        ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
+                        ScmModel().mark_for_invalidation(repo.repo_name)
                         i += 1
                     except VCSError as e:
                         log.warning('VCS error invalidating %s: %s', repo.repo_name, e)
                 h.flash(_('Invalidated %s repositories') % i, category='success')
 
-            return redirect(url('admin_settings_mapping'))
+            raise HTTPFound(location=url('admin_settings_mapping'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -236,10 +199,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_global(self):
-        """GET /admin/settings/global: All items in the collection"""
-        # url('admin_settings_global')
         c.active = 'global'
         if request.POST:
             application_form = ApplicationSettingsForm()()
@@ -255,25 +216,14 @@
                     force_defaults=False)
 
             try:
-                sett1 = Setting.create_or_update('title',
-                                            form_result['title'])
-                Session().add(sett1)
-
-                sett2 = Setting.create_or_update('realm',
-                                            form_result['realm'])
-                Session().add(sett2)
-
-                sett3 = Setting.create_or_update('ga_code',
-                                            form_result['ga_code'])
-                Session().add(sett3)
-
-                sett4 = Setting.create_or_update('captcha_public_key',
-                                    form_result['captcha_public_key'])
-                Session().add(sett4)
-
-                sett5 = Setting.create_or_update('captcha_private_key',
-                                    form_result['captcha_private_key'])
-                Session().add(sett5)
+                for setting in (
+                    'title',
+                    'realm',
+                    'ga_code',
+                    'captcha_public_key',
+                    'captcha_private_key',
+                ):
+                    Setting.create_or_update(setting, form_result[setting])
 
                 Session().commit()
                 set_app_settings(config)
@@ -285,7 +235,7 @@
                           'application settings'),
                           category='error')
 
-            return redirect(url('admin_settings_global'))
+            raise HTTPFound(location=url('admin_settings_global'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -296,10 +246,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_visual(self):
-        """GET /admin/settings/visual: All items in the collection"""
-        # url('admin_settings_visual')
         c.active = 'visual'
         if request.POST:
             application_form = ApplicationVisualisationForm()()
@@ -318,7 +266,7 @@
                 settings = [
                     ('show_public_icon', 'show_public_icon', 'bool'),
                     ('show_private_icon', 'show_private_icon', 'bool'),
-                    ('stylify_metatags', 'stylify_metatags', 'bool'),
+                    ('stylify_metalabels', 'stylify_metalabels', 'bool'),
                     ('repository_fields', 'repository_fields', 'bool'),
                     ('dashboard_items', 'dashboard_items', 'int'),
                     ('admin_grid_items', 'admin_grid_items', 'int'),
@@ -328,9 +276,7 @@
                     ('clone_uri_tmpl', 'clone_uri_tmpl', 'unicode'),
                 ]
                 for setting, form_key, type_ in settings:
-                    sett = Setting.create_or_update(setting,
-                                        form_result[form_key], type_)
-                    Session().add(sett)
+                    Setting.create_or_update(setting, form_result[form_key], type_)
 
                 Session().commit()
                 set_app_settings(config)
@@ -343,7 +289,7 @@
                           'visualisation settings'),
                         category='error')
 
-            return redirect(url('admin_settings_visual'))
+            raise HTTPFound(location=url('admin_settings_visual'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -354,10 +300,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_email(self):
-        """GET /admin/settings/email: All items in the collection"""
-        # url('admin_settings_email')
         c.active = 'email'
         if request.POST:
             test_email = request.POST.get('test_email')
@@ -366,22 +310,22 @@
                                'Kallithea version: %s' % c.kallithea_version)
             if not test_email:
                 h.flash(_('Please enter email address'), category='error')
-                return redirect(url('admin_settings_email'))
+                raise HTTPFound(location=url('admin_settings_email'))
 
-            test_email_txt_body = EmailNotificationModel()\
+            test_email_txt_body = EmailNotificationModel() \
                 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
                                 'txt', body=test_body)
-            test_email_html_body = EmailNotificationModel()\
+            test_email_html_body = EmailNotificationModel() \
                 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
                                 'html', body=test_body)
 
             recipients = [test_email] if test_email else None
 
-            run_task(tasks.send_email, recipients, test_email_subj,
-                     test_email_txt_body, test_email_html_body)
+            tasks.send_email(recipients, test_email_subj,
+                             test_email_txt_body, test_email_html_body)
 
             h.flash(_('Send email task created'), category='success')
-            return redirect(url('admin_settings_email'))
+            raise HTTPFound(location=url('admin_settings_email'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -395,10 +339,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_hooks(self):
-        """GET /admin/settings/hooks: All items in the collection"""
-        # url('admin_settings_hooks')
         c.active = 'hooks'
         if request.POST:
             if c.visual.allow_custom_hooks_settings:
@@ -409,7 +351,11 @@
 
                 try:
                     ui_key = ui_key and ui_key.strip()
-                    if ui_value and ui_key:
+                    if ui_key in (x.ui_key for x in Ui.get_custom_hooks()):
+                        h.flash(_('Hook already exists'), category='error')
+                    elif ui_key in (x.ui_key for x in Ui.get_builtin_hooks()):
+                        h.flash(_('Builtin hooks are read-only. Please use another hook name.'), category='error')
+                    elif ui_value and ui_key:
                         Ui.create_or_update_hook(ui_key, ui_value)
                         h.flash(_('Added new hook'), category='success')
                     elif hook_id:
@@ -419,10 +365,12 @@
                     # check for edits
                     update = False
                     _d = request.POST.dict_of_lists()
-                    for k, v in zip(_d.get('hook_ui_key', []),
-                                    _d.get('hook_ui_value_new', [])):
-                        Ui.create_or_update_hook(k, v)
-                        update = True
+                    for k, v, ov in zip(_d.get('hook_ui_key', []),
+                                        _d.get('hook_ui_value_new', []),
+                                        _d.get('hook_ui_value', [])):
+                        if v != ov:
+                            Ui.create_or_update_hook(k, v)
+                            update = True
 
                     if update:
                         h.flash(_('Updated hooks'), category='success')
@@ -432,7 +380,7 @@
                     h.flash(_('Error occurred during hook creation'),
                             category='error')
 
-                return redirect(url('admin_settings_hooks'))
+                raise HTTPFound(location=url('admin_settings_hooks'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -446,17 +394,15 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_search(self):
-        """GET /admin/settings/search: All items in the collection"""
-        # url('admin_settings_search')
         c.active = 'search'
         if request.POST:
             repo_location = self._get_hg_ui_settings()['paths_root_path']
             full_index = request.POST.get('full_index', False)
-            run_task(tasks.whoosh_index, repo_location, full_index)
+            tasks.whoosh_index(repo_location, full_index)
             h.flash(_('Whoosh reindex task scheduled'), category='success')
-            return redirect(url('admin_settings_search'))
+            raise HTTPFound(location=url('admin_settings_search'))
 
         defaults = Setting.get_app_settings()
         defaults.update(self._get_hg_ui_settings())
@@ -467,10 +413,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_system(self):
-        """GET /admin/settings/system: All items in the collection"""
-        # url('admin_settings_system')
         c.active = 'system'
 
         defaults = Setting.get_app_settings()
@@ -489,10 +433,8 @@
             encoding="UTF-8",
             force_defaults=False)
 
-    @HasPermissionAllDecorator('hg.admin')
+    @HasPermissionAnyDecorator('hg.admin')
     def settings_system_update(self):
-        """GET /admin/settings/system/updates: All items in the collection"""
-        # url('admin_settings_system_update')
         import json
         import urllib2
         from kallithea.lib.verlib import NormalizedVersion
@@ -503,7 +445,7 @@
         _update_url = defaults.get('update_url', '')
         _update_url = "" # FIXME: disabled
 
-        _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
+        _err = lambda s: '<div class="alert alert-danger">%s</div>' % (s)
         try:
             import kallithea
             ver = kallithea.__version__
--- a/kallithea/controllers/admin/user_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/user_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,7 @@
 kallithea.controllers.admin.user_groups
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-User Groups crud controller for pylons
+User Groups crud controller
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -30,21 +30,22 @@
 import formencode
 
 from formencode import htmlfill
-from pylons import request, tmpl_context as c, url, config
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c, config, app_globals
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
 from sqlalchemy.orm import joinedload
 from sqlalchemy.sql.expression import func
 from webob.exc import HTTPInternalServerError
 
 import kallithea
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib.exceptions import UserGroupsAssignedException, \
     RepoGroupAssignmentError
 from kallithea.lib.utils2 import safe_unicode, safe_int
 from kallithea.lib.auth import LoginRequired, \
-    HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
+    HasUserGroupPermissionLevelDecorator, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, render
 from kallithea.model.scm import UserGroupList
 from kallithea.model.user_group import UserGroupModel
@@ -55,7 +56,6 @@
     CustomDefaultPermissionsForm
 from kallithea.model.meta import Session
 from kallithea.lib.utils import action_logger
-from kallithea.lib.compat import json
 
 log = logging.getLogger(__name__)
 
@@ -63,9 +63,9 @@
 class UserGroupsController(BaseController):
     """REST Controller styled on the Atom Publishing Protocol"""
 
-    @LoginRequired()
-    def __before__(self):
-        super(UserGroupsController, self).__before__()
+    @LoginRequired(allow_default_user=True)
+    def _before(self, *args, **kwargs):
+        super(UserGroupsController, self)._before(*args, **kwargs)
         c.available_permissions = config['available_permissions']
 
     def __load_data(self, user_group_id):
@@ -88,15 +88,13 @@
         return data
 
     def index(self, format='html'):
-        """GET /users_groups: All items in the collection"""
-        # url('users_groups')
-        _list = UserGroup.query()\
-                        .order_by(func.lower(UserGroup.users_group_name))\
+        _list = UserGroup.query() \
+                        .order_by(func.lower(UserGroup.users_group_name)) \
                         .all()
-        group_iter = UserGroupList(_list, perm_set=['usergroup.admin'])
+        group_iter = UserGroupList(_list, perm_level='admin')
         user_groups_data = []
         total_records = len(group_iter)
-        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
+        _tmpl_lookup = app_globals.mako_lookup
         template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 
         user_group_name = lambda user_group_id, user_group_name: (
@@ -116,37 +114,32 @@
                 "desc": h.escape(user_gr.user_group_description),
                 "members": len(user_gr.members),
                 "active": h.boolicon(user_gr.users_group_active),
-                "owner": h.person(user_gr.user.username),
+                "owner": h.person(user_gr.owner.username),
                 "action": user_group_actions(user_gr.users_group_id, user_gr.users_group_name)
             })
 
-        c.data = json.dumps({
-            "totalRecords": total_records,
-            "startIndex": 0,
+        c.data = {
             "sort": None,
             "dir": "asc",
             "records": user_groups_data
-        })
+        }
 
         return render('admin/user_groups/user_groups.html')
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
     def create(self):
-        """POST /users_groups: Create a new item"""
-        # url('users_groups')
-
         users_group_form = UserGroupForm()()
         try:
             form_result = users_group_form.to_python(dict(request.POST))
             ug = UserGroupModel().create(name=form_result['users_group_name'],
                                          description=form_result['user_group_description'],
-                                         owner=self.authuser.user_id,
+                                         owner=request.authuser.user_id,
                                          active=form_result['users_group_active'])
 
             gr = form_result['users_group_name']
-            action_logger(self.authuser,
+            action_logger(request.authuser,
                           'admin_created_users_group:%s' % gr,
-                          None, self.ip_addr, self.sa)
+                          None, request.ip_addr)
             h.flash(h.literal(_('Created user group %s') % h.link_to(h.escape(gr), url('edit_users_group', id=ug.users_group_id))),
                 category='success')
             Session().commit()
@@ -160,27 +153,17 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of user group %s') \
+            h.flash(_('Error occurred during creation of user group %s')
                     % request.POST.get('users_group_name'), category='error')
 
-        return redirect(url('users_groups'))
+        raise HTTPFound(location=url('users_groups'))
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
     def new(self, format='html'):
-        """GET /user_groups/new: Form to create a new item"""
-        # url('new_users_group')
         return render('admin/user_groups/user_group_add.html')
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def update(self, id):
-        """PUT /user_groups/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('users_group', id=ID),
-        #           method='put')
-        # url('users_group', id=ID)
-
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'settings'
         self.__load_data(id)
@@ -195,9 +178,9 @@
             form_result = users_group_form.to_python(request.POST)
             UserGroupModel().update(c.user_group, form_result)
             gr = form_result['users_group_name']
-            action_logger(self.authuser,
+            action_logger(request.authuser,
                           'admin_updated_users_group:%s' % gr,
-                          None, self.ip_addr, self.sa)
+                          None, request.ip_addr)
             h.flash(_('Updated user group %s') % gr, category='success')
             Session().commit()
         except formencode.Invalid as errors:
@@ -209,7 +192,6 @@
                                                       'hg.create.repository'),
                 'fork_repo_perm': ug_model.has_perm(id,
                                                     'hg.fork.repository'),
-                '_method': 'put'
             })
 
             return htmlfill.render(
@@ -221,20 +203,13 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user group %s') \
+            h.flash(_('Error occurred during update of user group %s')
                     % request.POST.get('users_group_name'), category='error')
 
-        return redirect(url('edit_users_group', id=id))
+        raise HTTPFound(location=url('edit_users_group', id=id))
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def delete(self, id):
-        """DELETE /user_groups/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('users_group', id=ID),
-        #           method='delete')
-        # url('users_group', id=ID)
         usr_gr = UserGroup.get_or_404(id)
         try:
             UserGroupModel().delete(usr_gr)
@@ -246,17 +221,10 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of user group'),
                     category='error')
-        return redirect(url('users_groups'))
-
-    def show(self, id, format='html'):
-        """GET /user_groups/id: Show a specific item"""
-        # url('users_group', id=ID)
+        raise HTTPFound(location=url('users_groups'))
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def edit(self, id, format='html'):
-        """GET /user_groups/id/edit: Form to edit an existing item"""
-        # url('edit_users_group', id=ID)
-
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'settings'
         self.__load_data(id)
@@ -270,15 +238,11 @@
             force_defaults=False
         )
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def edit_perms(self, id):
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'perms'
 
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.user_groups_array = repo_model.get_user_groups_js()
-
         defaults = {}
         # fill user group users
         for p in c.user_group.user_user_group_to_perm:
@@ -296,7 +260,7 @@
             force_defaults=False
         )
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def update_perms(self, id):
         """
         grant permission for given usergroup
@@ -312,21 +276,16 @@
                                                  form['perms_updates'])
         except RepoGroupAssignmentError:
             h.flash(_('Target group cannot be the same'), category='error')
-            return redirect(url('edit_user_group_perms', id=id))
-        #TODO: implement this
-        #action_logger(self.authuser, 'admin_changed_repo_permissions',
-        #              repo_name, self.ip_addr, self.sa)
+            raise HTTPFound(location=url('edit_user_group_perms', id=id))
+        # TODO: implement this
+        #action_logger(request.authuser, 'admin_changed_repo_permissions',
+        #              repo_name, request.ip_addr)
         Session().commit()
         h.flash(_('User group permissions updated'), category='success')
-        return redirect(url('edit_user_group_perms', id=id))
+        raise HTTPFound(location=url('edit_user_group_perms', id=id))
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def delete_perms(self, id):
-        """
-        DELETE an existing repository group permission user
-
-        :param group_name:
-        """
         try:
             obj_type = request.POST.get('obj_type')
             obj_id = None
@@ -335,8 +294,8 @@
             elif obj_type == 'user_group':
                 obj_id = safe_int(request.POST.get('user_group_id'))
 
-            if not c.authuser.is_admin:
-                if obj_type == 'user' and c.authuser.user_id == obj_id:
+            if not request.authuser.is_admin:
+                if obj_type == 'user' and request.authuser.user_id == obj_id:
                     msg = _('Cannot revoke permission for yourself as admin')
                     h.flash(msg, category='warning')
                     raise Exception('revoke admin permission on self')
@@ -353,7 +312,7 @@
                     category='error')
             raise HTTPInternalServerError()
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def edit_default_perms(self, id):
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'default_perms'
@@ -362,20 +321,20 @@
             'repositories': {},
             'repositories_groups': {}
         }
-        ugroup_repo_perms = UserGroupRepoToPerm.query()\
-            .options(joinedload(UserGroupRepoToPerm.permission))\
-            .options(joinedload(UserGroupRepoToPerm.repository))\
-            .filter(UserGroupRepoToPerm.users_group_id == id)\
+        ugroup_repo_perms = UserGroupRepoToPerm.query() \
+            .options(joinedload(UserGroupRepoToPerm.permission)) \
+            .options(joinedload(UserGroupRepoToPerm.repository)) \
+            .filter(UserGroupRepoToPerm.users_group_id == id) \
             .all()
 
         for gr in ugroup_repo_perms:
             permissions['repositories'][gr.repository.repo_name]  \
                 = gr.permission.permission_name
 
-        ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
-            .options(joinedload(UserGroupRepoGroupToPerm.permission))\
-            .options(joinedload(UserGroupRepoGroupToPerm.group))\
-            .filter(UserGroupRepoGroupToPerm.users_group_id == id)\
+        ugroup_group_perms = UserGroupRepoGroupToPerm.query() \
+            .options(joinedload(UserGroupRepoGroupToPerm.permission)) \
+            .options(joinedload(UserGroupRepoGroupToPerm.group)) \
+            .filter(UserGroupRepoGroupToPerm.users_group_id == id) \
             .all()
 
         for gr in ugroup_group_perms:
@@ -402,11 +361,8 @@
             force_defaults=False
         )
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def update_default_perms(self, id):
-        """PUT /users_perm/id: Update an existing item"""
-        # url('users_group_perm', id=ID, method='put')
-
         user_group = UserGroup.get_or_404(id)
 
         try:
@@ -415,11 +371,10 @@
 
             inherit_perms = form_result['inherit_default_permissions']
             user_group.inherit_default_permissions = inherit_perms
-            Session().add(user_group)
             usergroup_model = UserGroupModel()
 
-            defs = UserGroupToPerm.query()\
-                .filter(UserGroupToPerm.users_group == user_group)\
+            defs = UserGroupToPerm.query() \
+                .filter(UserGroupToPerm.users_group == user_group) \
                 .all()
             for ug in defs:
                 Session().delete(ug)
@@ -444,9 +399,9 @@
             h.flash(_('An error occurred during permissions saving'),
                     category='error')
 
-        return redirect(url('edit_user_group_default_perms', id=id))
+        raise HTTPFound(location=url('edit_user_group_default_perms', id=id))
 
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def edit_advanced(self, id):
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'advanced'
@@ -454,8 +409,7 @@
                                      key=lambda u: u.username.lower())
         return render('admin/user_groups/user_group_edit.html')
 
-
-    @HasUserGroupPermissionAnyDecorator('usergroup.admin')
+    @HasUserGroupPermissionLevelDecorator('admin')
     def edit_members(self, id):
         c.user_group = UserGroup.get_or_404(id)
         c.active = 'members'
--- a/kallithea/controllers/admin/users.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/admin/users.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,7 @@
 kallithea.controllers.admin.users
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Users crud controller for pylons
+Users crud controller
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -30,20 +30,19 @@
 import formencode
 
 from formencode import htmlfill
-from pylons import request, tmpl_context as c, url, config
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c, config, app_globals
+from tg.i18n import ugettext as _
 from sqlalchemy.sql.expression import func
-from webob.exc import HTTPNotFound
+from webob.exc import HTTPFound, HTTPNotFound
 
 import kallithea
+from kallithea.config.routing import url
 from kallithea.lib.exceptions import DefaultUserException, \
     UserOwnsReposException, UserCreationError
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
+from kallithea.lib.auth import LoginRequired, HasPermissionAnyDecorator, \
     AuthUser
 from kallithea.lib import auth_modules
-from kallithea.lib.auth_modules import auth_internal
 from kallithea.lib.base import BaseController, render
 from kallithea.model.api_key import ApiKeyModel
 
@@ -52,7 +51,6 @@
 from kallithea.model.user import UserModel
 from kallithea.model.meta import Session
 from kallithea.lib.utils import action_logger
-from kallithea.lib.compat import json
 from kallithea.lib.utils2 import datetime_to_time, safe_int, generate_api_key
 
 log = logging.getLogger(__name__)
@@ -62,24 +60,20 @@
     """REST Controller styled on the Atom Publishing Protocol"""
 
     @LoginRequired()
-    @HasPermissionAllDecorator('hg.admin')
-    def __before__(self):
-        super(UsersController, self).__before__()
+    @HasPermissionAnyDecorator('hg.admin')
+    def _before(self, *args, **kwargs):
+        super(UsersController, self)._before(*args, **kwargs)
         c.available_permissions = config['available_permissions']
-        c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL
 
     def index(self, format='html'):
-        """GET /users: All items in the collection"""
-        # url('users')
-
-        c.users_list = User.query().order_by(User.username)\
-                        .filter(User.username != User.DEFAULT_USER)\
-                        .order_by(func.lower(User.username))\
+        c.users_list = User.query().order_by(User.username) \
+                        .filter_by(is_default_user=False) \
+                        .order_by(func.lower(User.username)) \
                         .all()
 
         users_data = []
         total_records = len(c.users_list)
-        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
+        _tmpl_lookup = app_globals.mako_lookup
         template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 
         grav_tmpl = '<div class="gravatar">%s</div>'
@@ -108,30 +102,25 @@
                 "action": user_actions(user.user_id, user.username),
             })
 
-        c.data = json.dumps({
-            "totalRecords": total_records,
-            "startIndex": 0,
+        c.data = {
             "sort": None,
             "dir": "asc",
             "records": users_data
-        })
+        }
 
         return render('admin/users/users.html')
 
     def create(self):
-        """POST /users: Create a new item"""
-        # url('users')
-        c.default_extern_type = auth_internal.KallitheaAuthPlugin.name
-        c.default_extern_name = auth_internal.KallitheaAuthPlugin.name
+        c.default_extern_type = User.DEFAULT_AUTH_TYPE
+        c.default_extern_name = ''
         user_model = UserModel()
         user_form = UserForm()()
         try:
             form_result = user_form.to_python(dict(request.POST))
             user = user_model.create(form_result)
-            usr = form_result['username']
-            action_logger(self.authuser, 'admin_created_user:%s' % usr,
-                          None, self.ip_addr, self.sa)
-            h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
+            action_logger(request.authuser, 'admin_created_user:%s' % user.username,
+                          None, request.ip_addr)
+            h.flash(_('Created user %s') % user.username,
                     category='success')
             Session().commit()
         except formencode.Invalid as errors:
@@ -146,25 +135,16 @@
             h.flash(e, 'error')
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during creation of user %s') \
+            h.flash(_('Error occurred during creation of user %s')
                     % request.POST.get('username'), category='error')
-        return redirect(url('users'))
+        raise HTTPFound(location=url('edit_user', id=user.user_id))
 
     def new(self, format='html'):
-        """GET /users/new: Form to create a new item"""
-        # url('new_user')
-        c.default_extern_type = auth_internal.KallitheaAuthPlugin.name
-        c.default_extern_name = auth_internal.KallitheaAuthPlugin.name
+        c.default_extern_type = User.DEFAULT_AUTH_TYPE
+        c.default_extern_name = ''
         return render('admin/users/user_add.html')
 
     def update(self, id):
-        """PUT /users/id: Update an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="PUT" />
-        # Or using helpers:
-        #    h.form(url('update_user', id=ID),
-        #           method='put')
-        # url('user', id=ID)
         user_model = UserModel()
         user = user_model.get(id)
         _form = UserForm(edit=True, old_data={'user_id': id,
@@ -177,8 +157,8 @@
 
             user_model.update(id, form_result, skip_attrs=skip_attrs)
             usr = form_result['username']
-            action_logger(self.authuser, 'admin_updated_user:%s' % usr,
-                          None, self.ip_addr, self.sa)
+            action_logger(request.authuser, 'admin_updated_user:%s' % usr,
+                          None, request.ip_addr)
             h.flash(_('User updated successfully'), category='success')
             Session().commit()
         except formencode.Invalid as errors:
@@ -188,7 +168,6 @@
                 'create_repo_perm': user_model.has_perm(id,
                                                         'hg.create.repository'),
                 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
-                '_method': 'put'
             })
             return htmlfill.render(
                 self._render_edit_profile(user),
@@ -199,18 +178,11 @@
                 force_defaults=False)
         except Exception:
             log.error(traceback.format_exc())
-            h.flash(_('Error occurred during update of user %s') \
+            h.flash(_('Error occurred during update of user %s')
                     % form_result.get('username'), category='error')
-        return redirect(url('edit_user', id=id))
+        raise HTTPFound(location=url('edit_user', id=id))
 
     def delete(self, id):
-        """DELETE /users/id: Delete an existing item"""
-        # Forms posted to this method should contain a hidden field:
-        #    <input type="hidden" name="_method" value="DELETE" />
-        # Or using helpers:
-        #    h.form(url('delete_user', id=ID),
-        #           method='delete')
-        # url('user', id=ID)
         usr = User.get_or_404(id)
         try:
             UserModel().delete(usr)
@@ -222,12 +194,7 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during deletion of user'),
                     category='error')
-        return redirect(url('users'))
-
-    def show(self, id, format='html'):
-        """GET /users/id: Show a specific item"""
-        # url('user', id=ID)
-        User.get_or_404(-1)
+        raise HTTPFound(location=url('users'))
 
     def _get_user_or_raise_if_default(self, id):
         try:
@@ -240,14 +207,11 @@
         c.user = user
         c.active = 'profile'
         c.perm_user = AuthUser(dbuser=user)
-        c.ip_addr = self.ip_addr
         managed_fields = auth_modules.get_managed_fields(user)
         c.readonly = lambda n: 'readonly' if n in managed_fields else None
         return render('admin/users/user_edit.html')
 
     def edit(self, id, format='html'):
-        """GET /users/id/edit: Form to edit an existing item"""
-        # url('edit_user', id=ID)
         user = self._get_user_or_raise_if_default(id)
         defaults = user.get_dict()
 
@@ -260,8 +224,7 @@
     def edit_advanced(self, id):
         c.user = self._get_user_or_raise_if_default(id)
         c.active = 'advanced'
-        c.perm_user = AuthUser(user_id=id)
-        c.ip_addr = self.ip_addr
+        c.perm_user = AuthUser(dbuser=c.user)
 
         umodel = UserModel()
         defaults = c.user.get_dict()
@@ -306,25 +269,22 @@
         ApiKeyModel().create(c.user.user_id, description, lifetime)
         Session().commit()
         h.flash(_("API key successfully created"), category='success')
-        return redirect(url('edit_user_api_keys', id=c.user.user_id))
+        raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
 
     def delete_api_key(self, id):
         c.user = self._get_user_or_raise_if_default(id)
 
         api_key = request.POST.get('del_api_key')
         if request.POST.get('del_api_key_builtin'):
-            user = User.get(c.user.user_id)
-            if user is not None:
-                user.api_key = generate_api_key()
-                Session().add(user)
-                Session().commit()
-                h.flash(_("API key successfully reset"), category='success')
+            c.user.api_key = generate_api_key()
+            Session().commit()
+            h.flash(_("API key successfully reset"), category='success')
         elif api_key:
             ApiKeyModel().delete(api_key, c.user.user_id)
             Session().commit()
             h.flash(_("API key successfully deleted"), category='success')
 
-        return redirect(url('edit_user_api_keys', id=c.user.user_id))
+        raise HTTPFound(location=url('edit_user_api_keys', id=c.user.user_id))
 
     def update_account(self, id):
         pass
@@ -332,8 +292,7 @@
     def edit_perms(self, id):
         c.user = self._get_user_or_raise_if_default(id)
         c.active = 'perms'
-        c.perm_user = AuthUser(user_id=id)
-        c.ip_addr = self.ip_addr
+        c.perm_user = AuthUser(dbuser=c.user)
 
         umodel = UserModel()
         defaults = c.user.get_dict()
@@ -350,8 +309,6 @@
             force_defaults=False)
 
     def update_perms(self, id):
-        """PUT /users_perm/id: Update an existing item"""
-        # url('user_perm', id=ID, method='put')
         user = self._get_user_or_raise_if_default(id)
 
         try:
@@ -360,11 +317,10 @@
 
             inherit_perms = form_result['inherit_default_permissions']
             user.inherit_default_permissions = inherit_perms
-            Session().add(user)
             user_model = UserModel()
 
-            defs = UserToPerm.query()\
-                .filter(UserToPerm.user == user)\
+            defs = UserToPerm.query() \
+                .filter(UserToPerm.user == user) \
                 .all()
             for ug in defs:
                 Session().delete(ug)
@@ -387,12 +343,12 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during permissions saving'),
                     category='error')
-        return redirect(url('edit_user_perms', id=id))
+        raise HTTPFound(location=url('edit_user_perms', id=id))
 
     def edit_emails(self, id):
         c.user = self._get_user_or_raise_if_default(id)
         c.active = 'emails'
-        c.user_email_map = UserEmailMap.query()\
+        c.user_email_map = UserEmailMap.query() \
             .filter(UserEmailMap.user == c.user).all()
 
         defaults = c.user.get_dict()
@@ -403,8 +359,6 @@
             force_defaults=False)
 
     def add_email(self, id):
-        """POST /user_emails:Add an existing item"""
-        # url('user_emails', id=ID, method='put')
         user = self._get_user_or_raise_if_default(id)
         email = request.POST.get('new_email')
         user_model = UserModel()
@@ -420,27 +374,25 @@
             log.error(traceback.format_exc())
             h.flash(_('An error occurred during email saving'),
                     category='error')
-        return redirect(url('edit_user_emails', id=id))
+        raise HTTPFound(location=url('edit_user_emails', id=id))
 
     def delete_email(self, id):
-        """DELETE /user_emails_delete/id: Delete an existing item"""
-        # url('user_emails_delete', id=ID, method='delete')
         user = self._get_user_or_raise_if_default(id)
         email_id = request.POST.get('del_email_id')
         user_model = UserModel()
         user_model.delete_extra_email(id, email_id)
         Session().commit()
         h.flash(_("Removed email from user"), category='success')
-        return redirect(url('edit_user_emails', id=id))
+        raise HTTPFound(location=url('edit_user_emails', id=id))
 
     def edit_ips(self, id):
         c.user = self._get_user_or_raise_if_default(id)
         c.active = 'ips'
-        c.user_ip_map = UserIpMap.query()\
+        c.user_ip_map = UserIpMap.query() \
             .filter(UserIpMap.user == c.user).all()
 
         c.inherit_default_ips = c.user.inherit_default_permissions
-        c.default_user_ip_map = UserIpMap.query()\
+        c.default_user_ip_map = UserIpMap.query() \
             .filter(UserIpMap.user == User.get_default_user()).all()
 
         defaults = c.user.get_dict()
@@ -451,9 +403,6 @@
             force_defaults=False)
 
     def add_ip(self, id):
-        """POST /user_ips:Add an existing item"""
-        # url('user_ips', id=ID, method='put')
-
         ip = request.POST.get('new_ip')
         user_model = UserModel()
 
@@ -470,12 +419,10 @@
                     category='error')
 
         if 'default_user' in request.POST:
-            return redirect(url('admin_permissions_ips'))
-        return redirect(url('edit_user_ips', id=id))
+            raise HTTPFound(location=url('admin_permissions_ips'))
+        raise HTTPFound(location=url('edit_user_ips', id=id))
 
     def delete_ip(self, id):
-        """DELETE /user_ips_delete/id: Delete an existing item"""
-        # url('user_ips_delete', id=ID, method='delete')
         ip_id = request.POST.get('del_ip_id')
         user_model = UserModel()
         user_model.delete_extra_ip(id, ip_id)
@@ -483,5 +430,5 @@
         h.flash(_("Removed IP address from user whitelist"), category='success')
 
         if 'default_user' in request.POST:
-            return redirect(url('admin_permissions_ips'))
-        return redirect(url('edit_user_ips', id=id))
+            raise HTTPFound(location=url('admin_permissions_ips'))
+        raise HTTPFound(location=url('edit_user_ips', id=id))
--- a/kallithea/controllers/api/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/api/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,15 +30,15 @@
 import types
 import traceback
 import time
+import itertools
 
-from paste.response import replace_header
-from pylons.controllers import WSGIController
+from tg import Response, response, request, TGController
 
-from webob.exc import HTTPError
+from webob.exc import HTTPError, HTTPException, WSGIHTTPException
 
 from kallithea.model.db import User
 from kallithea.model import meta
-from kallithea.lib.compat import izip_longest, json
+from kallithea.lib.compat import json
 from kallithea.lib.auth import AuthUser
 from kallithea.lib.base import _get_ip_addr as _get_ip, _get_access_path
 from kallithea.lib.utils2 import safe_unicode, safe_str
@@ -56,23 +56,20 @@
         return safe_str(self.message)
 
 
-def jsonrpc_error(message, retid=None, code=None):
+class JSONRPCErrorResponse(Response, HTTPException):
     """
     Generate a Response object with a JSON-RPC error body
+    """
 
-    :param code:
-    :param retid:
-    :param message:
-    """
-    from pylons.controllers.util import Response
-    return Response(
-        body=json.dumps(dict(id=retid, result=None, error=message)),
-        status=code,
-        content_type='application/json'
-    )
+    def __init__(self, message=None, retid=None, code=None):
+        HTTPException.__init__(self, message, self)
+        Response.__init__(self,
+                          json_body=dict(id=retid, result=None, error=message),
+                          status=code,
+                          content_type='application/json')
 
 
-class JSONRPCController(WSGIController):
+class JSONRPCController(TGController):
     """
      A WSGI-speaking JSON-RPC controller class
 
@@ -96,32 +93,30 @@
         """
         return self._rpc_args
 
-    def __call__(self, environ, start_response):
+    def _dispatch(self, state, remainder=None):
         """
         Parse the request body as JSON, look up the method on the
         controller and if it exists, dispatch to it.
         """
-        try:
-            return self._handle_request(environ, start_response)
-        finally:
-            meta.Session.remove()
+        # Since we are here we should respond as JSON
+        response.content_type = 'application/json'
 
-    def _handle_request(self, environ, start_response):
+        environ = state.request.environ
         start = time.time()
-        ip_addr = self.ip_addr = self._get_ip_addr(environ)
+        ip_addr = request.ip_addr = self._get_ip_addr(environ)
         self._req_id = None
         if 'CONTENT_LENGTH' not in environ:
             log.debug("No Content-Length")
-            return jsonrpc_error(retid=self._req_id,
-                                 message="No Content-Length in request")
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message="No Content-Length in request")
         else:
             length = environ['CONTENT_LENGTH'] or 0
             length = int(environ['CONTENT_LENGTH'])
             log.debug('Content-Length: %s', length)
 
         if length == 0:
-            return jsonrpc_error(retid=self._req_id,
-                                 message="Content-Length is 0")
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message="Content-Length is 0")
 
         raw_body = environ['wsgi.input'].read(length)
 
@@ -129,9 +124,9 @@
             json_body = json.loads(raw_body)
         except ValueError as e:
             # catch JSON errors Here
-            return jsonrpc_error(retid=self._req_id,
-                                 message="JSON parse error ERR:%s RAW:%r"
-                                 % (e, raw_body))
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message="JSON parse error ERR:%s RAW:%r"
+                                                % (e, raw_body))
 
         # check AUTH based on API key
         try:
@@ -142,38 +137,36 @@
             if not isinstance(self._request_params, dict):
                 self._request_params = {}
 
-            log.debug(
-                'method: %s, params: %s', self._req_method,
-                                            self._request_params
-            )
+            log.debug('method: %s, params: %s',
+                      self._req_method, self._request_params)
         except KeyError as e:
-            return jsonrpc_error(retid=self._req_id,
-                                 message='Incorrect JSON query missing %s' % e)
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message='Incorrect JSON query missing %s' % e)
 
         # check if we can find this session using api_key
         try:
             u = User.get_by_api_key(self._req_api_key)
             if u is None:
-                return jsonrpc_error(retid=self._req_id,
-                                     message='Invalid API key')
+                raise JSONRPCErrorResponse(retid=self._req_id,
+                                           message='Invalid API key')
 
             auth_u = AuthUser(dbuser=u)
             if not AuthUser.check_ip_allowed(auth_u, ip_addr):
-                return jsonrpc_error(retid=self._req_id,
-                        message='request from IP:%s not allowed' % (ip_addr,))
+                raise JSONRPCErrorResponse(retid=self._req_id,
+                                           message='request from IP:%s not allowed' % (ip_addr,))
             else:
                 log.info('Access for IP:%s allowed', ip_addr)
 
         except Exception as e:
-            return jsonrpc_error(retid=self._req_id,
-                                 message='Invalid API key')
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message='Invalid API key')
 
         self._error = None
         try:
             self._func = self._find_method()
         except AttributeError as e:
-            return jsonrpc_error(retid=self._req_id,
-                                 message=str(e))
+            raise JSONRPCErrorResponse(retid=self._req_id,
+                                       message=str(e))
 
         # now that we have a method, add self._req_params to
         # self.kargs and dispatch control to WGIController
@@ -183,26 +176,18 @@
         default_empty = types.NotImplementedType
 
         # kw arguments required by this method
-        func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
-                                        fillvalue=default_empty))
+        func_kwargs = dict(itertools.izip_longest(reversed(arglist), reversed(defaults),
+                                                  fillvalue=default_empty))
 
         # this is little trick to inject logged in user for
         # perms decorators to work they expect the controller class to have
         # authuser attribute set
-        self.authuser = auth_u
+        request.authuser = auth_u
 
         # This attribute will need to be first param of a method that uses
         # api_key, which is translated to instance of user at that name
         USER_SESSION_ATTR = 'apiuser'
 
-        if USER_SESSION_ATTR not in arglist:
-            return jsonrpc_error(
-                retid=self._req_id,
-                message='This method [%s] does not support '
-                         'authentication (missing %s param)' % (
-                                    self._func.__name__, USER_SESSION_ATTR)
-            )
-
         # get our arglist and check if we provided them as args
         for arg, default in func_kwargs.iteritems():
             if arg == USER_SESSION_ATTR:
@@ -213,47 +198,40 @@
             # skip the required param check if it's default value is
             # NotImplementedType (default_empty)
             if default == default_empty and arg not in self._request_params:
-                return jsonrpc_error(
+                raise JSONRPCErrorResponse(
                     retid=self._req_id,
-                    message=(
-                        'Missing non optional `%s` arg in JSON DATA' % arg
-                    )
+                    message='Missing non optional `%s` arg in JSON DATA' % arg,
                 )
 
-        self._rpc_args = {USER_SESSION_ATTR: u}
+        extra = set(self._request_params).difference(func_kwargs)
+        if extra:
+                raise JSONRPCErrorResponse(
+                    retid=self._req_id,
+                    message='Unknown %s arg in JSON DATA' %
+                            ', '.join('`%s`' % arg for arg in extra),
+                )
 
+        self._rpc_args = {}
         self._rpc_args.update(self._request_params)
-
         self._rpc_args['action'] = self._req_method
         self._rpc_args['environ'] = environ
-        self._rpc_args['start_response'] = start_response
 
-        status = []
-        headers = []
-        exc_info = []
-
-        def change_content(new_status, new_headers, new_exc_info=None):
-            status.append(new_status)
-            headers.extend(new_headers)
-            exc_info.append(new_exc_info)
-
-        output = WSGIController.__call__(self, environ, change_content)
-        output = list(output) # expand iterator - just to ensure exact timing
-        replace_header(headers, 'Content-Type', 'application/json')
-        start_response(status[0], headers, exc_info[0])
         log.info('IP: %s Request to %s time: %.3fs' % (
             self._get_ip_addr(environ),
             safe_unicode(_get_access_path(environ)), time.time() - start)
         )
-        return output
 
-    def _dispatch_call(self):
+        state.set_action(self._rpc_call, [])
+        state.set_params(self._rpc_args)
+        return state
+
+    def _rpc_call(self, action, environ, **rpc_args):
         """
-        Implement dispatch interface specified by WSGIController
+        Call the specified RPC Method
         """
         raw_response = ''
         try:
-            raw_response = self._inspect_call(self._func)
+            raw_response = getattr(self, action)(**rpc_args)
             if isinstance(raw_response, HTTPError):
                 self._error = str(raw_response)
         except JSONRPCError as e:
--- a/kallithea/controllers/api/api.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/api/api.py	Sun Mar 31 21:28:56 2019 +0200
@@ -25,18 +25,20 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-
 import time
 import traceback
 import logging
+
+from datetime import datetime
 from sqlalchemy import or_
 
-from kallithea import EXTERN_TYPE_INTERNAL
+from tg import request
+
 from kallithea.controllers.api import JSONRPCController, JSONRPCError
 from kallithea.lib.auth import (
-    PasswordGenerator, AuthUser, HasPermissionAllDecorator,
-    HasPermissionAnyDecorator, HasPermissionAnyApi, HasRepoPermissionAnyApi,
-    HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAny)
+    PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
+    HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
+    HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
 from kallithea.lib.utils import map_groups, repo2db_mapper
 from kallithea.lib.utils2 import (
     str2bool, time_to_datetime, safe_int, Optional, OAttr)
@@ -47,12 +49,18 @@
 from kallithea.model.user import UserModel
 from kallithea.model.user_group import UserGroupModel
 from kallithea.model.gist import GistModel
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.comment import ChangesetCommentsModel
+from kallithea.model.pull_request import PullRequestModel
 from kallithea.model.db import (
     Repository, Setting, UserIpMap, Permission, User, Gist,
-    RepoGroup)
+    RepoGroup, UserGroup, PullRequest, ChangesetStatus)
 from kallithea.lib.compat import json
 from kallithea.lib.exceptions import (
     DefaultUserException, UserGroupsAssignedException)
+from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
+from kallithea.lib.vcs.backends.base import EmptyChangeset
+from kallithea.lib.utils import action_logger
 
 log = logging.getLogger(__name__)
 
@@ -96,7 +104,7 @@
 
     :param repogroupid:
     """
-    repo_group = RepoGroupModel()._get_repo_group(repogroupid)
+    repo_group = RepoGroup.guess_instance(repogroupid)
     if repo_group is None:
         raise JSONRPCError(
             'repository group `%s` does not exist' % (repogroupid,))
@@ -147,34 +155,32 @@
     """
     API Controller
 
-    Each method takes USER as first argument. This is then, based on given
-    API_KEY propagated as instance of user object who's making the call.
-
-    example function::
-
-        def func(apiuser,arg1, arg2,...):
+    The authenticated user can be found as request.authuser.
+
+    Example function::
+
+        def func(arg1, arg2,...):
             pass
 
     Each function should also **raise** JSONRPCError for any
     errors that happens.
-
     """
 
-    @HasPermissionAllDecorator('hg.admin')
-    def test(self, apiuser, args):
+    @HasPermissionAnyDecorator('hg.admin')
+    def test(self, args):
         return args
 
-    @HasPermissionAllDecorator('hg.admin')
-    def pull(self, apiuser, repoid):
+    @HasPermissionAnyDecorator('hg.admin')
+    def pull(self, repoid, clone_uri=Optional(None)):
         """
         Triggers a pull from remote location on given repo. Can be used to
         automatically keep remote repos up to date. This command can be executed
         only using api_key belonging to user with admin rights
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
+        :param clone_uri: repository URI to pull from (optional)
+        :type clone_uri: str
 
         OUTPUT::
 
@@ -199,7 +205,8 @@
 
         try:
             ScmModel().pull_changes(repo.repo_name,
-                                    self.authuser.username)
+                                    request.authuser.username,
+                                    clone_uri=Optional.extract(clone_uri))
             return dict(
                 msg='Pulled from `%s`' % repo.repo_name,
                 repository=repo.repo_name
@@ -210,16 +217,14 @@
                 'Unable to pull changes from `%s`' % repo.repo_name
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
+    @HasPermissionAnyDecorator('hg.admin')
+    def rescan_repos(self, remove_obsolete=Optional(False)):
         """
         Triggers rescan repositories action. If remove_obsolete is set
         than also delete repos that are in database but not in the filesystem.
         aka "clean zombies". This command can be executed only using api_key
         belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param remove_obsolete: deletes repositories from
             database that are not found on the filesystem
         :type remove_obsolete: Optional(bool)
@@ -254,14 +259,12 @@
                 'Error occurred during rescan repositories action'
             )
 
-    def invalidate_cache(self, apiuser, repoid):
+    def invalidate_cache(self, repoid):
         """
         Invalidate cache for repository.
         This command can be executed only using api_key belonging to user with admin
         rights or regular user that have write or admin or write access to repository.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
 
@@ -284,11 +287,8 @@
 
         """
         repo = get_repo_or_error(repoid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            if not HasRepoPermissionAnyApi('repository.admin',
-                                           'repository.write')(
-                    user=apiuser, repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('write')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         try:
@@ -304,7 +304,7 @@
             )
 
     # permission check inside
-    def lock(self, apiuser, repoid, locked=Optional(None),
+    def lock(self, repoid, locked=Optional(None),
              userid=Optional(OAttr('apiuser'))):
         """
         Set locking state on given repository by given user. If userid param
@@ -314,8 +314,6 @@
         to user with admin rights or regular user that have admin or write
         access to repository.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param locked: lock state to be set
@@ -350,14 +348,12 @@
 
         """
         repo = get_repo_or_error(repoid)
-        if HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if HasPermissionAny('hg.admin')():
             pass
-        elif HasRepoPermissionAnyApi('repository.admin',
-                                     'repository.write')(user=apiuser,
-                                                         repo_name=repo.repo_name):
+        elif HasRepoPermissionLevel('write')(repo.repo_name):
             # make sure normal user does not pass someone else userid,
             # he is not allowed to do that
-            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+            if not isinstance(userid, Optional) and userid != request.authuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
@@ -365,7 +361,7 @@
             raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         if isinstance(userid, Optional):
-            userid = apiuser.user_id
+            userid = request.authuser.user_id
 
         user = get_user_or_error(userid)
 
@@ -423,14 +419,12 @@
                     'Error occurred locking repository `%s`' % repo.repo_name
                 )
 
-    def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
+    def get_locks(self, userid=Optional(OAttr('apiuser'))):
         """
         Get all repositories with locks for given userid, if
         this command is run by non-admin account userid is set to user
         who is calling this method, thus returning locks for himself.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: User to get locks for
         :type userid: Optional(str or int)
 
@@ -443,10 +437,10 @@
           error :  null
         """
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if not HasPermissionAny('hg.admin')():
             # make sure normal user does not pass someone else userid,
             # he is not allowed to do that
-            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+            if not isinstance(userid, Optional) and userid != request.authuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
@@ -458,7 +452,7 @@
             user = get_user_or_error(userid)
 
         # show all locks
-        for r in Repository.getAll():
+        for r in Repository.query():
             userid, time_ = r.locked
             if time_:
                 _api_data = r.get_api_data()
@@ -471,8 +465,8 @@
 
         return ret
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_ip(self, apiuser, userid=Optional(OAttr('apiuser'))):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_ip(self, userid=Optional(OAttr('apiuser'))):
         """
         Shows IP address as seen from Kallithea server, together with all
         defined IP addresses for given user. If userid is not passed data is
@@ -480,8 +474,6 @@
         This command can be executed only using api_key belonging to user with
         admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: username to show ips for
         :type userid: Optional(str or int)
 
@@ -501,24 +493,22 @@
 
         """
         if isinstance(userid, Optional):
-            userid = apiuser.user_id
+            userid = request.authuser.user_id
         user = get_user_or_error(userid)
         ips = UserIpMap.query().filter(UserIpMap.user == user).all()
         return dict(
-            server_ip_addr=self.ip_addr,
+            server_ip_addr=request.ip_addr,
             user_ips=ips
         )
 
     # alias for old
     show_ip = get_ip
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_server_info(self, apiuser):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_server_info(self):
         """
         return server info, including Kallithea version and installed packages
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
 
         OUTPUT::
 
@@ -533,7 +523,7 @@
         """
         return Setting.get_server_info()
 
-    def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
+    def get_user(self, userid=Optional(OAttr('apiuser'))):
         """
         Gets a user by username or user_id, Returns empty result if user is
         not found. If userid param is skipped it is set to id of user who is
@@ -541,8 +531,6 @@
         belonging to user with admin rights, or regular users that cannot
         specify different userid than theirs
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: user to get data for
         :type userid: Optional(str or int)
 
@@ -577,30 +565,28 @@
             error:  null
 
         """
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if not HasPermissionAny('hg.admin')():
             # make sure normal user does not pass someone else userid,
             # he is not allowed to do that
-            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+            if not isinstance(userid, Optional) and userid != request.authuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
 
         if isinstance(userid, Optional):
-            userid = apiuser.user_id
+            userid = request.authuser.user_id
 
         user = get_user_or_error(userid)
         data = user.get_api_data()
         data['permissions'] = AuthUser(user_id=user.user_id).permissions
         return data
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_users(self, apiuser):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_users(self):
         """
         Lists all existing users. This command can be executed only using api_key
         belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
 
         OUTPUT::
 
@@ -609,26 +595,23 @@
             error:  null
         """
 
-        result = []
-        users_list = User.query().order_by(User.username) \
-            .filter(User.username != User.DEFAULT_USER) \
-            .all()
-        for user in users_list:
-            result.append(user.get_api_data())
-        return result
-
-    @HasPermissionAllDecorator('hg.admin')
-    def create_user(self, apiuser, username, email, password=Optional(''),
-                    firstname=Optional(''), lastname=Optional(''),
+        return [
+            user.get_api_data()
+            for user in User.query()
+                .order_by(User.username)
+                .filter_by(is_default_user=False)
+        ]
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def create_user(self, username, email, password=Optional(''),
+                    firstname=Optional(u''), lastname=Optional(u''),
                     active=Optional(True), admin=Optional(False),
-                    extern_name=Optional(EXTERN_TYPE_INTERNAL),
-                    extern_type=Optional(EXTERN_TYPE_INTERNAL)):
+                    extern_type=Optional(User.DEFAULT_AUTH_TYPE),
+                    extern_name=Optional('')):
         """
         Creates new user. Returns new user object. This command can
         be executed only using api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param username: new username
         :type username: str or int
         :param email: email
@@ -675,13 +658,9 @@
         if User.get_by_username(username):
             raise JSONRPCError("user `%s` already exist" % (username,))
 
-        if User.get_by_email(email, case_insensitive=True):
+        if User.get_by_email(email):
             raise JSONRPCError("email `%s` already exist" % (email,))
 
-        if Optional.extract(extern_name):
-            # generate temporary password if user is external
-            password = PasswordGenerator().gen_password(length=8)
-
         try:
             user = UserModel().create_or_update(
                 username=Optional.extract(username),
@@ -703,18 +682,16 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to create user `%s`' % (username,))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def update_user(self, apiuser, userid, username=Optional(None),
-                    email=Optional(None),password=Optional(None),
+    @HasPermissionAnyDecorator('hg.admin')
+    def update_user(self, userid, username=Optional(None),
+                    email=Optional(None), password=Optional(None),
                     firstname=Optional(None), lastname=Optional(None),
                     active=Optional(None), admin=Optional(None),
-                    extern_type=Optional(None), extern_name=Optional(None),):
+                    extern_type=Optional(None), extern_name=Optional(None)):
         """
         updates given user if such user exists. This command can
         be executed only using api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: userid to update
         :type userid: str or int
         :param username: new username
@@ -786,14 +763,12 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to update user `%s`' % (userid,))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def delete_user(self, apiuser, userid):
+    @HasPermissionAnyDecorator('hg.admin')
+    def delete_user(self, userid):
         """
         deletes given user if such user exists. This command can
         be executed only using api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: user to delete
         :type userid: str or int
 
@@ -831,14 +806,12 @@
                                % (user.user_id, user.username))
 
     # permission check inside
-    def get_user_group(self, apiuser, usergroupid):
+    def get_user_group(self, usergroupid):
         """
         Gets an existing user group. This command can be executed only using api_key
         belonging to user with admin rights or user who has at least
         read access to user group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param usergroupid: id of user_group to edit
         :type usergroupid: str or int
 
@@ -856,25 +829,20 @@
 
         """
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have at least read permission for this user group !
-            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         data = user_group.get_api_data()
         return data
 
     # permission check inside
-    def get_user_groups(self, apiuser):
+    def get_user_groups(self):
         """
         Lists all existing user groups. This command can be executed only using
         api_key belonging to user with admin rights or user who has at least
         read access to user group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
 
         OUTPUT::
 
@@ -883,24 +851,19 @@
             error : null
         """
 
-        result = []
-        _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-        extras = {'user': apiuser}
-        for user_group in UserGroupList(UserGroupModel().get_all(),
-                                        perm_set=_perms, extra_kwargs=extras):
-            result.append(user_group.get_api_data())
-        return result
+        return [
+            user_group.get_api_data()
+            for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
+        ]
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
-    def create_user_group(self, apiuser, group_name, description=Optional(''),
+    def create_user_group(self, group_name, description=Optional(u''),
                           owner=Optional(OAttr('apiuser')), active=Optional(True)):
         """
         Creates new user group. This command can be executed only using api_key
         belonging to user with admin rights or an user who has create user group
         permission
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param group_name: name of new user group
         :type group_name: str
         :param description: group description
@@ -936,7 +899,7 @@
 
         try:
             if isinstance(owner, Optional):
-                owner = apiuser.user_id
+                owner = request.authuser.user_id
 
             owner = get_user_or_error(owner)
             active = Optional.extract(active)
@@ -953,15 +916,13 @@
             raise JSONRPCError('failed to create group `%s`' % (group_name,))
 
     # permission check inside
-    def update_user_group(self, apiuser, usergroupid, group_name=Optional(''),
+    def update_user_group(self, usergroupid, group_name=Optional(''),
                           description=Optional(''), owner=Optional(None),
                           active=Optional(True)):
         """
         Updates given usergroup.  This command can be executed only using api_key
         belonging to user with admin rights or an admin of given user group
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param usergroupid: id of user group to update
         :type usergroupid: str or int
         :param group_name: name of new user group
@@ -992,11 +953,8 @@
 
         """
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this user group !
-            _perms = ('usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         if not isinstance(owner, Optional):
@@ -1005,7 +963,7 @@
         updates = {}
         store_update(updates, group_name, 'users_group_name')
         store_update(updates, description, 'user_group_description')
-        store_update(updates, owner, 'user')
+        store_update(updates, owner, 'owner')
         store_update(updates, active, 'users_group_active')
         try:
             UserGroupModel().update(user_group, updates)
@@ -1020,14 +978,12 @@
             raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
 
     # permission check inside
-    def delete_user_group(self, apiuser, usergroupid):
+    def delete_user_group(self, usergroupid):
         """
         Delete given user group by user group id or name.
         This command can be executed only using api_key
         belonging to user with admin rights or an admin of given user group
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param usergroupid:
         :type usergroupid: int
 
@@ -1051,11 +1007,8 @@
 
         """
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this user group !
-            _perms = ('usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
@@ -1074,17 +1027,15 @@
             raise JSONRPCError('failed to delete user group ID:%s %s' %
                                (user_group.users_group_id,
                                 user_group.users_group_name)
-            )
+                               )
 
     # permission check inside
-    def add_user_to_user_group(self, apiuser, usergroupid, userid):
+    def add_user_to_user_group(self, usergroupid, userid):
         """
         Adds a user to a user group. If user exists in that group success will be
         `false`. This command can be executed only using api_key
         belonging to user with admin rights  or an admin of given user group
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param usergroupid:
         :type usergroupid: int
         :param userid:
@@ -1112,11 +1063,8 @@
         """
         user = get_user_or_error(userid)
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this user group !
-            _perms = ('usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
@@ -1141,14 +1089,12 @@
             )
 
     # permission check inside
-    def remove_user_from_user_group(self, apiuser, usergroupid, userid):
+    def remove_user_from_user_group(self, usergroupid, userid):
         """
         Removes a user from a user group. If user is not in given group success will
         be `false`. This command can be executed only
         using api_key belonging to user with admin rights or an admin of given user group
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param usergroupid:
         :param userid:
 
@@ -1166,11 +1112,8 @@
         """
         user = get_user_or_error(userid)
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this user group !
-            _perms = ('usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
@@ -1190,15 +1133,15 @@
             )
 
     # permission check inside
-    def get_repo(self, apiuser, repoid):
+    def get_repo(self, repoid,
+                 with_revision_names=Optional(False),
+                 with_pullrequests=Optional(False)):
         """
         Gets an existing repository by it's name or repository_id. Members will return
         either users_group or user associated to that repository. This command can be
         executed only using api_key belonging to user with admin
         rights or regular user that have at least read access to repository.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
 
@@ -1242,8 +1185,20 @@
                                   },

                                 ]
-                 "followers":   [<user_obj>, ...]
-                 ]
+                 "followers":   [<user_obj>, ...],
+                 <if with_revision_names == True>
+                 "tags": {
+                            "<tagname>": "<raw_id>",
+                            ...
+                         },
+                 "branches": {
+                            "<branchname>": "<raw_id>",
+                            ...
+                         },
+                 "bookmarks": {
+                            "<bookmarkname>": "<raw_id>",
+                            ...
+                         },
             }
           }
           error :  null
@@ -1251,14 +1206,11 @@
         """
         repo = get_repo_or_error(repoid)
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            perms = ('repository.admin', 'repository.write', 'repository.read')
-            if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('read')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         members = []
-        followers = []
         for user in repo.repo_to_perm:
             perm = user.permission.permission_name
             user = user.user
@@ -1279,23 +1231,24 @@
             }
             members.append(user_group_data)
 
-        for user in repo.followers:
-            followers.append(user.user.get_api_data())
-
-        data = repo.get_api_data()
+        followers = [
+            uf.user.get_api_data()
+            for uf in repo.followers
+        ]
+
+        data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names),
+                                 with_pullrequests=Optional.extract(with_pullrequests))
         data['members'] = members
         data['followers'] = followers
         return data
 
     # permission check inside
-    def get_repos(self, apiuser):
+    def get_repos(self):
         """
         Lists all existing repositories. This command can be executed only using
         api_key belonging to user with admin rights or regular user that have
         admin, write or read access to repository.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
 
         OUTPUT::
 
@@ -1320,18 +1273,18 @@
                     ]
             error:  null
         """
-        result = []
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            repos = RepoModel().get_all_user_repos(user=apiuser)
+        if not HasPermissionAny('hg.admin')():
+            repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
         else:
-            repos = RepoModel().get_all()
-
-        for repo in repos:
-            result.append(repo.get_api_data())
-        return result
+            repos = Repository.query()
+
+        return [
+            repo.get_api_data()
+            for repo in repos
+        ]
 
     # permission check inside
-    def get_repo_nodes(self, apiuser, repoid, revision, root_path,
+    def get_repo_nodes(self, repoid, revision, root_path,
                        ret_type=Optional('all')):
         """
         returns a list of nodes and it's children in a flat list for a given path
@@ -1339,8 +1292,6 @@
         `dirs`.  This command can be executed only using api_key belonging to
         user with admin rights or regular user that have at least read access to repository.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param revision: revision for which listing should be done
@@ -1365,10 +1316,8 @@
         """
         repo = get_repo_or_error(repoid)
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            perms = ('repository.admin', 'repository.write', 'repository.read')
-            if not HasRepoPermissionAnyApi(*perms)(user=apiuser, repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('read')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         ret_type = Optional.extract(ret_type)
@@ -1392,7 +1341,7 @@
             )
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
-    def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
+    def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
                     repo_type=Optional('hg'), description=Optional(''),
                     private=Optional(False), clone_uri=Optional(None),
                     landing_rev=Optional('rev:tip'),
@@ -1408,8 +1357,6 @@
         belonging to user with admin rights or regular user that have create
         repository permission. Regular users cannot specify owner parameter
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repo_name: repository name
         :type repo_name: str
         :param owner: user_id or username
@@ -1453,14 +1400,14 @@
           }
 
         """
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if not HasPermissionAny('hg.admin')():
             if not isinstance(owner, Optional):
-                #forbid setting owner for non-admins
+                # forbid setting owner for non-admins
                 raise JSONRPCError(
                     'Only Kallithea admin can specify `owner` param'
                 )
         if isinstance(owner, Optional):
-            owner = apiuser.user_id
+            owner = request.authuser.user_id
 
         owner = get_user_or_error(owner)
 
@@ -1509,10 +1456,7 @@
             )
 
             task = RepoModel().create(form_data=data, cur_user=owner)
-            from celery.result import BaseAsyncResult
-            task_id = None
-            if isinstance(task, BaseAsyncResult):
-                task_id = task.task_id
+            task_id = task.task_id
             # no commit, it's done in RepoModel, or async via celery
             return dict(
                 msg="Created new repository `%s`" % (repo_name,),
@@ -1526,7 +1470,7 @@
                 'failed to create repository `%s`' % (repo_name,))
 
     # permission check inside
-    def update_repo(self, apiuser, repoid, name=Optional(None),
+    def update_repo(self, repoid, name=Optional(None),
                     owner=Optional(OAttr('apiuser')),
                     group=Optional(None),
                     description=Optional(''), private=Optional(False),
@@ -1538,8 +1482,6 @@
         """
         Updates repo
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param name:
@@ -1554,19 +1496,17 @@
         :param enable_downloads:
         """
         repo = get_repo_or_error(repoid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
-                                                               repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('admin')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
             if (name != repo.repo_name and
-                not HasPermissionAnyApi('hg.create.repository')(user=apiuser)
+                not HasPermissionAny('hg.create.repository')()
                 ):
                 raise JSONRPCError('no permission to create (or move) repositories')
 
             if not isinstance(owner, Optional):
-                #forbid setting owner for non-admins
+                # forbid setting owner for non-admins
                 raise JSONRPCError(
                     'Only Kallithea admin can specify `owner` param'
                 )
@@ -1579,7 +1519,7 @@
         try:
             store_update(updates, name, 'repo_name')
             store_update(updates, repo_group, 'repo_group')
-            store_update(updates, owner, 'user')
+            store_update(updates, owner, 'owner')
             store_update(updates, description, 'repo_description')
             store_update(updates, private, 'repo_private')
             store_update(updates, clone_uri, 'clone_uri')
@@ -1599,7 +1539,7 @@
             raise JSONRPCError('failed to update repo `%s`' % repoid)
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
-    def fork_repo(self, apiuser, repoid, fork_name,
+    def fork_repo(self, repoid, fork_name,
                   owner=Optional(OAttr('apiuser')),
                   description=Optional(''), copy_permissions=Optional(False),
                   private=Optional(False), landing_rev=Optional('rev:tip')):
@@ -1610,8 +1550,6 @@
         user with admin rights or regular user that have fork permission, and at least
         read access to forking repository. Regular users cannot specify owner parameter.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param fork_name:
@@ -1654,25 +1592,22 @@
             type_ = 'fork' if _repo.fork else 'repo'
             raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
 
-        if HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if HasPermissionAny('hg.admin')():
             pass
-        elif HasRepoPermissionAnyApi('repository.admin',
-                                     'repository.write',
-                                     'repository.read')(user=apiuser,
-                                                        repo_name=repo.repo_name):
+        elif HasRepoPermissionLevel('read')(repo.repo_name):
             if not isinstance(owner, Optional):
-                #forbid setting owner for non-admins
+                # forbid setting owner for non-admins
                 raise JSONRPCError(
                     'Only Kallithea admin can specify `owner` param'
                 )
 
-            if not HasPermissionAnyApi('hg.create.repository')(user=apiuser):
+            if not HasPermissionAny('hg.create.repository')():
                 raise JSONRPCError('no permission to create repositories')
         else:
             raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         if isinstance(owner, Optional):
-            owner = apiuser.user_id
+            owner = request.authuser.user_id
 
         owner = get_user_or_error(owner)
 
@@ -1699,10 +1634,7 @@
             )
             task = RepoModel().create_fork(form_data, cur_user=owner)
             # no commit, it's done in RepoModel, or async via celery
-            from celery.result import BaseAsyncResult
-            task_id = None
-            if isinstance(task, BaseAsyncResult):
-                task_id = task.task_id
+            task_id = task.task_id
             return dict(
                 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
                                                       fork_name),
@@ -1718,15 +1650,13 @@
             )
 
     # permission check inside
-    def delete_repo(self, apiuser, repoid, forks=Optional('')):
+    def delete_repo(self, repoid, forks=Optional('')):
         """
         Deletes a repository. This command can be executed only using api_key belonging
         to user with admin rights or regular user that have admin access to repository.
         When `forks` param is set it's possible to detach or delete forks of deleting
         repository
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param forks: `detach` or `delete`, what do do with attached forks for repo
@@ -1744,10 +1674,8 @@
         """
         repo = get_repo_or_error(repoid)
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            if not HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
-                                                           repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('admin')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
         try:
@@ -1776,15 +1704,13 @@
                 'failed to delete repository `%s`' % (repo.repo_name,)
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def grant_user_permission(self, apiuser, repoid, userid, perm):
+    @HasPermissionAnyDecorator('hg.admin')
+    def grant_user_permission(self, repoid, userid, perm):
         """
         Grant permission for user on given repository, or update existing one
         if found. This command can be executed only using api_key belonging to user
         with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param userid:
@@ -1823,14 +1749,12 @@
                 )
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def revoke_user_permission(self, apiuser, repoid, userid):
+    @HasPermissionAnyDecorator('hg.admin')
+    def revoke_user_permission(self, repoid, userid):
         """
         Revoke permission for user on given repository. This command can be executed
         only using api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param userid:
@@ -1866,14 +1790,12 @@
             )
 
     # permission check inside
-    def grant_user_group_permission(self, apiuser, repoid, usergroupid, perm):
+    def grant_user_group_permission(self, repoid, usergroupid, perm):
         """
         Grant permission for user group on given repository, or update
         existing one if found. This command can be executed only using
         api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param usergroupid: id of usergroup
@@ -1903,17 +1825,11 @@
         repo = get_repo_or_error(repoid)
         perm = get_perm_or_error(perm)
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            _perms = ('repository.admin',)
-            if not HasRepoPermissionAnyApi(*_perms)(
-                    user=apiuser, repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('admin')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
-            # check if we have at least read permission for this user group !
-            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+            if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
@@ -1939,13 +1855,11 @@
             )
 
     # permission check inside
-    def revoke_user_group_permission(self, apiuser, repoid, usergroupid):
+    def revoke_user_group_permission(self, repoid, usergroupid):
         """
         Revoke permission for user group on given repository. This command can be
         executed only using api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repoid: repository name or repository id
         :type repoid: str or int
         :param usergroupid:
@@ -1961,17 +1875,11 @@
         """
         repo = get_repo_or_error(repoid)
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo !
-            _perms = ('repository.admin',)
-            if not HasRepoPermissionAnyApi(*_perms)(
-                    user=apiuser, repo_name=repo.repo_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoPermissionLevel('admin')(repo.repo_name):
                 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
 
-            # check if we have at least read permission for this user group !
-            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+            if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
                 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
 
         try:
@@ -1994,14 +1902,12 @@
                 )
             )
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_repo_group(self, apiuser, repogroupid):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_repo_group(self, repogroupid):
         """
         Returns given repo group together with permissions, and repositories
         inside the group
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: id/name of repository group
         :type repogroupid: str or int
         """
@@ -2032,21 +1938,19 @@
         data["members"] = members
         return data
 
-    @HasPermissionAllDecorator('hg.admin')
-    def get_repo_groups(self, apiuser):
+    @HasPermissionAnyDecorator('hg.admin')
+    def get_repo_groups(self):
         """
         Returns all repository groups
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         """
-        result = []
-        for repo_group in RepoGroupModel().get_all():
-            result.append(repo_group.get_api_data())
-        return result
-
-    @HasPermissionAllDecorator('hg.admin')
-    def create_repo_group(self, apiuser, group_name, description=Optional(''),
+        return [
+            repo_group.get_api_data()
+            for repo_group in RepoGroup.query()
+        ]
+
+    @HasPermissionAnyDecorator('hg.admin')
+    def create_repo_group(self, group_name, description=Optional(''),
                           owner=Optional(OAttr('apiuser')),
                           parent=Optional(None),
                           copy_permissions=Optional(False)):
@@ -2054,8 +1958,6 @@
         Creates a repository group. This command can be executed only using
         api_key belonging to user with admin rights.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param group_name:
         :type group_name:
         :param description:
@@ -2089,7 +1991,7 @@
             raise JSONRPCError("repo group `%s` already exist" % (group_name,))
 
         if isinstance(owner, Optional):
-            owner = apiuser.user_id
+            owner = request.authuser.user_id
         group_description = Optional.extract(description)
         parent_group = Optional.extract(parent)
         if not isinstance(parent, Optional):
@@ -2114,8 +2016,8 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def update_repo_group(self, apiuser, repogroupid, group_name=Optional(''),
+    @HasPermissionAnyDecorator('hg.admin')
+    def update_repo_group(self, repogroupid, group_name=Optional(''),
                           description=Optional(''),
                           owner=Optional(OAttr('apiuser')),
                           parent=Optional(None), enable_locking=Optional(False)):
@@ -2140,12 +2042,10 @@
             raise JSONRPCError('failed to update repository group `%s`'
                                % (repogroupid,))
 
-    @HasPermissionAllDecorator('hg.admin')
-    def delete_repo_group(self, apiuser, repogroupid):
+    @HasPermissionAnyDecorator('hg.admin')
+    def delete_repo_group(self, repogroupid):
         """
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: name or id of repository group
         :type repogroupid: str or int
 
@@ -2181,10 +2081,10 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to delete repo group ID:%s %s' %
                                (repo_group.group_id, repo_group.group_name)
-            )
+                               )
 
     # permission check inside
-    def grant_user_permission_to_repo_group(self, apiuser, repogroupid, userid,
+    def grant_user_permission_to_repo_group(self, repogroupid, userid,
                                             perm, apply_to_children=Optional('none')):
         """
         Grant permission for user on given repository group, or update existing
@@ -2192,8 +2092,6 @@
         to user with admin rights, or user who has admin right to given repository
         group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: name or id of repository group
         :type repogroupid: str or int
         :param userid:
@@ -2223,10 +2121,8 @@
 
         repo_group = get_repo_group_or_error(repogroupid)
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo group !
-            if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser,
-                                                               group_name=repo_group.group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
                 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
 
         user = get_user_or_error(userid)
@@ -2253,15 +2149,13 @@
                     userid, repo_group.name))
 
     # permission check inside
-    def revoke_user_permission_from_repo_group(self, apiuser, repogroupid, userid,
+    def revoke_user_permission_from_repo_group(self, repogroupid, userid,
                                                apply_to_children=Optional('none')):
         """
         Revoke permission for user on given repository group. This command can
         be executed only using api_key belonging to user with admin rights, or
         user who has admin right to given repository group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: name or id of repository group
         :type repogroupid: str or int
         :param userid:
@@ -2290,10 +2184,8 @@
 
         repo_group = get_repo_group_or_error(repogroupid)
 
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo group !
-            if not HasRepoGroupPermissionAnyApi('group.admin')(user=apiuser,
-                                                               group_name=repo_group.group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
                 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
 
         user = get_user_or_error(userid)
@@ -2320,16 +2212,14 @@
 
     # permission check inside
     def grant_user_group_permission_to_repo_group(
-            self, apiuser, repogroupid, usergroupid, perm,
-            apply_to_children=Optional('none'),):
+            self, repogroupid, usergroupid, perm,
+            apply_to_children=Optional('none')):
         """
         Grant permission for user group on given repository group, or update
         existing one if found. This command can be executed only using
         api_key belonging to user with admin rights, or user who has admin
         right to given repository group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: name or id of repository group
         :type repogroupid: str or int
         :param usergroupid: id of usergroup
@@ -2361,18 +2251,12 @@
         repo_group = get_repo_group_or_error(repogroupid)
         perm = get_perm_or_error(perm, prefix='group.')
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo group !
-            _perms = ('group.admin',)
-            if not HasRepoGroupPermissionAnyApi(*_perms)(
-                    user=apiuser, group_name=repo_group.group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
                 raise JSONRPCError(
                     'repository group `%s` does not exist' % (repogroupid,))
 
-            # check if we have at least read permission for this user group !
-            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+            if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
                 raise JSONRPCError(
                     'user group `%s` does not exist' % (usergroupid,))
 
@@ -2387,9 +2271,9 @@
             Session().commit()
             return dict(
                 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
-                        perm.permission_name, apply_to_children,
-                        user_group.users_group_name, repo_group.name
-                    ),
+                    perm.permission_name, apply_to_children,
+                    user_group.users_group_name, repo_group.name
+                ),
                 success=True
             )
         except Exception:
@@ -2403,15 +2287,13 @@
 
     # permission check inside
     def revoke_user_group_permission_from_repo_group(
-            self, apiuser, repogroupid, usergroupid,
+            self, repogroupid, usergroupid,
             apply_to_children=Optional('none')):
         """
         Revoke permission for user group on given repository. This command can be
         executed only using api_key belonging to user with admin rights, or
         user who has admin right to given repository group.
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param repogroupid: name or id of repository group
         :type repogroupid: str or int
         :param usergroupid:
@@ -2439,18 +2321,12 @@
         """
         repo_group = get_repo_group_or_error(repogroupid)
         user_group = get_user_group_or_error(usergroupid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            # check if we have admin permission for this repo group !
-            _perms = ('group.admin',)
-            if not HasRepoGroupPermissionAnyApi(*_perms)(
-                    user=apiuser, group_name=repo_group.group_name):
+        if not HasPermissionAny('hg.admin')():
+            if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
                 raise JSONRPCError(
                     'repository group `%s` does not exist' % (repogroupid,))
 
-            # check if we have at least read permission for this user group !
-            _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
-            if not HasUserGroupPermissionAny(*_perms)(
-                    user=apiuser, user_group_name=user_group.users_group_name):
+            if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
                 raise JSONRPCError(
                     'user group `%s` does not exist' % (usergroupid,))
 
@@ -2476,62 +2352,55 @@
                 )
             )
 
-    def get_gist(self, apiuser, gistid):
+    def get_gist(self, gistid):
         """
         Get given gist by id
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param gistid: id of private or public gist
         :type gistid: str
         """
         gist = get_gist_or_error(gistid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            if gist.gist_owner != apiuser.user_id:
+        if not HasPermissionAny('hg.admin')():
+            if gist.owner_id != request.authuser.user_id:
                 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
         return gist.get_api_data()
 
-    def get_gists(self, apiuser, userid=Optional(OAttr('apiuser'))):
+    def get_gists(self, userid=Optional(OAttr('apiuser'))):
         """
         Get all gists for given user. If userid is empty returned gists
         are for user who called the api
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param userid: user to get gists for
         :type userid: Optional(str or int)
         """
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
+        if not HasPermissionAny('hg.admin')():
             # make sure normal user does not pass someone else userid,
             # he is not allowed to do that
-            if not isinstance(userid, Optional) and userid != apiuser.user_id:
+            if not isinstance(userid, Optional) and userid != request.authuser.user_id:
                 raise JSONRPCError(
                     'userid is not the same as your user'
                 )
 
         if isinstance(userid, Optional):
-            user_id = apiuser.user_id
+            user_id = request.authuser.user_id
         else:
             user_id = get_user_or_error(userid).user_id
 
-        gists = []
-        _gists = Gist().query()\
-            .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
-            .filter(Gist.gist_owner == user_id)\
-            .order_by(Gist.created_on.desc())
-        for gist in _gists:
-            gists.append(gist.get_api_data())
-        return gists
-
-    def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')),
+        return [
+            gist.get_api_data()
+            for gist in Gist().query()
+                .filter_by(is_expired=False)
+                .filter(Gist.owner_id == user_id)
+                .order_by(Gist.created_on.desc())
+        ]
+
+    def create_gist(self, files, owner=Optional(OAttr('apiuser')),
                     gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
                     description=Optional('')):
 
         """
         Creates new Gist
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param files: files to be added to gist
             {'filename': {'content':'...', 'lexer': null},
              'filename2': {'content':'...', 'lexer': null}}
@@ -2565,7 +2434,7 @@
         """
         try:
             if isinstance(owner, Optional):
-                owner = apiuser.user_id
+                owner = request.authuser.user_id
 
             owner = get_user_or_error(owner)
             description = Optional.extract(description)
@@ -2586,19 +2455,17 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to create gist')
 
-    # def update_gist(self, apiuser, gistid, files, owner=Optional(OAttr('apiuser')),
+    # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
     #                 gist_type=Optional(Gist.GIST_PUBLIC),
     #                 gist_lifetime=Optional(-1), gist_description=Optional('')):
     #     gist = get_gist_or_error(gistid)
     #     updates = {}
 
     # permission check inside
-    def delete_gist(self, apiuser, gistid):
+    def delete_gist(self, gistid):
         """
         Deletes existing gist
 
-        :param apiuser: filled automatically from apikey
-        :type apiuser: AuthUser
         :param gistid: id of gist to delete
         :type gistid: str
 
@@ -2621,8 +2488,8 @@
 
         """
         gist = get_gist_or_error(gistid)
-        if not HasPermissionAnyApi('hg.admin')(user=apiuser):
-            if gist.gist_owner != apiuser.user_id:
+        if not HasPermissionAny('hg.admin')():
+            if gist.owner_id != request.authuser.user_id:
                 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
 
         try:
@@ -2636,3 +2503,103 @@
             log.error(traceback.format_exc())
             raise JSONRPCError('failed to delete gist ID:%s'
                                % (gist.gist_access_id,))
+
+    # permission check inside
+    def get_changesets(self, repoid, start=None, end=None, start_date=None,
+                       end_date=None, branch_name=None, reverse=False, with_file_list=False, max_revisions=None):
+        repo = get_repo_or_error(repoid)
+        if not HasRepoPermissionLevel('read')(repo.repo_name):
+            raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
+
+        format = "%Y-%m-%dT%H:%M:%S"
+        try:
+            return [e.__json__(with_file_list) for e in
+                repo.scm_instance.get_changesets(start,
+                                                 end,
+                                                 datetime.strptime(start_date, format) if start_date else None,
+                                                 datetime.strptime(end_date, format) if end_date else None,
+                                                 branch_name,
+                                                 reverse, max_revisions)]
+        except EmptyRepositoryError as e:
+            raise JSONRPCError(e.message)
+
+    # permission check inside
+    def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
+        repo = get_repo_or_error(repoid)
+        if not HasRepoPermissionLevel('read')(repo.repo_name):
+            raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
+        changeset = repo.get_changeset(raw_id)
+        if isinstance(changeset, EmptyChangeset):
+            raise JSONRPCError('Changeset %s does not exist' % raw_id)
+
+        info = dict(changeset.as_dict())
+
+        with_reviews = Optional.extract(with_reviews)
+        if with_reviews:
+                reviews = ChangesetStatusModel().get_statuses(
+                                    repo.repo_name, raw_id)
+                info["reviews"] = reviews
+
+        return info
+
+    # permission check inside
+    def get_pullrequest(self, pullrequest_id):
+        """
+        Get given pull request by id
+        """
+        pull_request = PullRequest.get(pullrequest_id)
+        if pull_request is None:
+            raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
+        if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
+            raise JSONRPCError('not allowed')
+        return pull_request.get_api_data()
+
+    # permission check inside
+    def comment_pullrequest(self, pull_request_id, comment_msg=u'', status=None, close_pr=False):
+        """
+        Add comment, close and change status of pull request.
+        """
+        apiuser = get_user_or_error(request.authuser.user_id)
+        pull_request = PullRequest.get(pull_request_id)
+        if pull_request is None:
+            raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
+        if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
+            raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
+                               ' to the source repository.')
+        owner = apiuser.user_id == pull_request.owner_id
+        reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
+        if close_pr and not (apiuser.admin or owner):
+            raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
+        if status and not (apiuser.admin or owner or reviewer):
+            raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
+        if pull_request.is_closed():
+            raise JSONRPCError('pull request is already closed')
+
+        comment = ChangesetCommentsModel().create(
+            text=comment_msg,
+            repo=pull_request.org_repo.repo_id,
+            author=apiuser.user_id,
+            pull_request=pull_request.pull_request_id,
+            f_path=None,
+            line_no=None,
+            status_change=(ChangesetStatus.get_status_lbl(status)),
+            closing_pr=close_pr
+        )
+        action_logger(apiuser,
+                      'user_commented_pull_request:%s' % pull_request_id,
+                      pull_request.org_repo, request.ip_addr)
+        if status:
+            ChangesetStatusModel().set_status(
+                pull_request.org_repo_id,
+                status,
+                apiuser.user_id,
+                comment,
+                pull_request=pull_request_id
+            )
+        if close_pr:
+            PullRequestModel().close_pull_request(pull_request_id)
+            action_logger(apiuser,
+                          'user_closed_pull_request:%s' % pull_request_id,
+                          pull_request.org_repo, request.ip_addr)
+        Session().commit()
+        return True
--- a/kallithea/controllers/bookmarks.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +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.controllers.bookmarks
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Bookmarks controller for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Dec 1, 2011
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-
-from pylons import tmpl_context as c
-
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.compat import OrderedDict
-from webob.exc import HTTPNotFound
-
-log = logging.getLogger(__name__)
-
-
-class BookmarksController(BaseRepoController):
-
-    def __before__(self):
-        super(BookmarksController, self).__before__()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def index(self):
-        if c.db_repo_scm_instance.alias != 'hg':
-            raise HTTPNotFound()
-
-        c.repo_bookmarks = OrderedDict()
-
-        bookmarks = [(name, c.db_repo_scm_instance.get_changeset(hash_)) for \
-                 name, hash_ in c.db_repo_scm_instance._repo._bookmarks.items()]
-        ordered_tags = sorted(bookmarks, key=lambda x: x[1].date, reverse=True)
-        for name, cs_book in ordered_tags:
-            c.repo_bookmarks[name] = cs_book
-
-        return render('bookmarks/bookmarks.html')
--- a/kallithea/controllers/branches.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +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.controllers.branches
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-branches controller for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 21, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-import binascii
-
-from pylons import tmpl_context as c
-
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.compat import OrderedDict
-from kallithea.lib.utils2 import safe_unicode
-
-log = logging.getLogger(__name__)
-
-
-class BranchesController(BaseRepoController):
-
-    def __before__(self):
-        super(BranchesController, self).__before__()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def index(self):
-
-        def _branchtags(localrepo):
-            bt_closed = {}
-            for bn, heads in localrepo.branchmap().iteritems():
-                tip = heads[-1]
-                if 'close' in localrepo.changelog.read(tip)[5]:
-                    bt_closed[bn] = tip
-            return bt_closed
-
-        cs_g = c.db_repo_scm_instance.get_changeset
-
-        c.repo_closed_branches = {}
-        if c.db_repo.repo_type == 'hg':
-            bt_closed = _branchtags(c.db_repo_scm_instance._repo)
-            _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),)
-                                for n, h in bt_closed.items()]
-
-            c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
-                                                    key=lambda ctx: ctx[0],
-                                                    reverse=False))
-
-        _branches = [(safe_unicode(n), cs_g(h))
-                     for n, h in c.db_repo_scm_instance.branches.items()]
-        c.repo_branches = OrderedDict(sorted(_branches,
-                                             key=lambda ctx: ctx[0],
-                                             reverse=False))
-
-        return render('branches/branches.html')
--- a/kallithea/controllers/changelog.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/changelog.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,18 +28,17 @@
 import logging
 import traceback
 
-from pylons import request, url, session, tmpl_context as c
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
-from webob.exc import HTTPNotFound, HTTPBadRequest
+from tg import request, session, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound, HTTPNotFound, HTTPBadRequest
 
 import kallithea.lib.helpers as h
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from kallithea.config.routing import url
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.helpers import RepoPage
-from kallithea.lib.compat import json
 from kallithea.lib.graphmod import graph_data
-from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
+from kallithea.lib.page import RepoPage
+from kallithea.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
     ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
 from kallithea.lib.utils2 import safe_int, safe_str
 
@@ -47,28 +46,10 @@
 log = logging.getLogger(__name__)
 
 
-def _load_changelog_summary():
-    p = safe_int(request.GET.get('page'), 1)
-    size = safe_int(request.GET.get('size'), 10)
-
-    def url_generator(**kw):
-        return url('changelog_summary_home',
-                   repo_name=c.db_repo.repo_name, size=size, **kw)
-
-    collection = c.db_repo_scm_instance
-
-    c.repo_changesets = RepoPage(collection, page=p,
-                                 items_per_page=size,
-                                 url=url_generator)
-    page_revisions = [x.raw_id for x in list(c.repo_changesets)]
-    c.comments = c.db_repo.get_comments(page_revisions)
-    c.statuses = c.db_repo.statuses(page_revisions)
-
-
 class ChangelogController(BaseRepoController):
 
-    def __before__(self):
-        super(ChangelogController, self).__before__()
+    def _before(self, *args, **kwargs):
+        super(ChangelogController, self)._before(*args, **kwargs)
         c.affected_files_cut_off = 60
 
     @staticmethod
@@ -89,18 +70,9 @@
             h.flash(safe_str(e), category='error')
         raise HTTPBadRequest()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def index(self, repo_name, revision=None, f_path=None):
-        # Fix URL after page size form submission via GET
-        # TODO: Somehow just don't send this extra junk in the GET URL
-        if request.GET.get('set'):
-            request.GET.pop('set', None)
-            if revision is None:
-                return redirect(url('changelog_home', repo_name=repo_name, **request.GET))
-            return redirect(url('changelog_file_home', repo_name=repo_name, revision=revision, f_path=f_path, **request.GET))
-
         limit = 2000
         default = 100
         if request.GET.get('size'):
@@ -111,13 +83,13 @@
             c.size = int(session.get('changelog_size', default))
         # min size must be 1
         c.size = max(c.size, 1)
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
         branch_name = request.GET.get('branch', None)
         if (branch_name and
             branch_name not in c.db_repo_scm_instance.branches and
             branch_name not in c.db_repo_scm_instance.closed_branches and
             not revision):
-            return redirect(url('changelog_file_home', repo_name=c.repo_name,
+            raise HTTPFound(location=url('changelog_file_home', repo_name=c.repo_name,
                                     revision=branch_name, f_path=f_path or ''))
 
         if revision == 'tip':
@@ -133,32 +105,32 @@
                 try:
                     collection = tip_cs.get_file_history(f_path)
                 except (NodeDoesNotExistError, ChangesetError):
-                    #this node is not present at tip !
+                    # this node is not present at tip !
                     try:
                         cs = self.__get_cs(revision, repo_name)
                         collection = cs.get_file_history(f_path)
                     except RepositoryError as e:
                         h.flash(safe_str(e), category='warning')
-                        redirect(h.url('changelog_home', repo_name=repo_name))
+                        raise HTTPFound(location=h.url('changelog_home', repo_name=repo_name))
                 collection = list(reversed(collection))
             else:
                 collection = c.db_repo_scm_instance.get_changesets(start=0, end=revision,
                                                         branch_name=branch_name)
             c.total_cs = len(collection)
 
-            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
+            c.cs_pagination = RepoPage(collection, page=p, item_count=c.total_cs,
                                     items_per_page=c.size, branch=branch_name,)
 
-            page_revisions = [x.raw_id for x in c.pagination]
-            c.comments = c.db_repo.get_comments(page_revisions)
-            c.statuses = c.db_repo.statuses(page_revisions)
+            page_revisions = [x.raw_id for x in c.cs_pagination]
+            c.cs_comments = c.db_repo.get_comments(page_revisions)
+            c.cs_statuses = c.db_repo.statuses(page_revisions)
         except EmptyRepositoryError as e:
             h.flash(safe_str(e), category='warning')
-            return redirect(url('summary_home', repo_name=c.repo_name))
+            raise HTTPFound(location=url('summary_home', repo_name=c.repo_name))
         except (RepositoryError, ChangesetDoesNotExistError, Exception) as e:
             log.error(traceback.format_exc())
             h.flash(safe_str(e), category='error')
-            return redirect(url('changelog_home', repo_name=c.repo_name))
+            raise HTTPFound(location=url('changelog_home', repo_name=c.repo_name))
 
         c.branch_name = branch_name
         c.branch_filters = [('', _('None'))] + \
@@ -169,28 +141,17 @@
                 [(k, prefix + k) for k in c.db_repo_scm_instance.closed_branches.keys()]
         revs = []
         if not f_path:
-            revs = [x.revision for x in c.pagination]
-        c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
+            revs = [x.revision for x in c.cs_pagination]
+        c.jsdata = graph_data(c.db_repo_scm_instance, revs)
 
         c.revision = revision # requested revision ref
-        c.first_revision = c.pagination[0] # pagination is never empty here!
+        c.first_revision = c.cs_pagination[0] # pagination is never empty here!
         return render('changelog/changelog.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def changelog_details(self, cs):
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             c.cs = c.db_repo_scm_instance.get_changeset(cs)
             return render('changelog/changelog_details.html')
         raise HTTPNotFound()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def changelog_summary(self, repo_name):
-        if request.environ.get('HTTP_X_PARTIAL_XHR'):
-            _load_changelog_summary()
-
-            return render('changelog/changelog_summary_data.html')
-        raise HTTPNotFound()
--- a/kallithea/controllers/changeset.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/changeset.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,8 +15,7 @@
 kallithea.controllers.changeset
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-changeset controller for pylons showing changes between
-revisions
+changeset controller showing changes between revisions
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -29,21 +28,17 @@
 import logging
 import traceback
 from collections import defaultdict
-from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
 
-from pylons import tmpl_context as c, request, response
-from pylons.i18n.translation import _
-from pylons.controllers.util import redirect
-from kallithea.lib.utils import jsonify
+from tg import tmpl_context as c, request, response
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound, HTTPForbidden, HTTPBadRequest, HTTPNotFound
 
 from kallithea.lib.vcs.exceptions import RepositoryError, \
-    ChangesetDoesNotExistError
+    ChangesetDoesNotExistError, EmptyRepositoryError
 
-from kallithea.lib.compat import json
 import kallithea.lib.helpers as h
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
-    NotAnonymous
-from kallithea.lib.base import BaseRepoController, render
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
+from kallithea.lib.base import BaseRepoController, render, jsonify
 from kallithea.lib.utils import action_logger
 from kallithea.lib.compat import OrderedDict
 from kallithea.lib import diffs
@@ -51,9 +46,8 @@
 from kallithea.model.comment import ChangesetCommentsModel
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.meta import Session
+from kallithea.model.pull_request import PullRequestModel
 from kallithea.model.repo import RepoModel
-from kallithea.lib.diffs import LimitedDiffContainer
-from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.utils2 import safe_unicode
 from kallithea.lib.graphmod import graph_data
@@ -110,7 +104,7 @@
 
     params['anchor'] = fileid
     icon = h.literal('<i class="icon-strike"></i>')
-    return h.link_to(icon, h.url.current(**params), title=lbl, class_='tooltip')
+    return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'})
 
 
 def get_line_ctx(fid, GET):
@@ -119,7 +113,7 @@
         ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
     else:
         _ln_ctx = filter(lambda k: k.startswith('C'), GET)
-        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx  else ln_ctx_global
+        ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
         if ln_ctx:
             ln_ctx = [ln_ctx]
 
@@ -170,26 +164,141 @@
 
     params['anchor'] = fileid
     icon = h.literal('<i class="icon-sort"></i>')
-    return h.link_to(icon, h.url.current(**params), title=lbl, class_='tooltip')
+    return h.link_to(icon, h.url.current(**params), title=lbl, **{'data-toggle': 'tooltip'})
+
+
+def create_cs_pr_comment(repo_name, revision=None, pull_request=None, allowed_to_change_status=True):
+    """
+    Add a comment to the specified changeset or pull request, using POST values
+    from the request.
+
+    Comments can be inline (when a file path and line number is specified in
+    POST) or general comments.
+    A comment can be accompanied by a review status change (accepted, rejected,
+    etc.). Pull requests can be closed or deleted.
+
+    Parameter 'allowed_to_change_status' is used for both status changes and
+    closing of pull requests. For deleting of pull requests, more specific
+    checks are done.
+    """
+
+    assert request.environ.get('HTTP_X_PARTIAL_XHR')
+    if pull_request:
+        pull_request_id = pull_request.pull_request_id
+    else:
+        pull_request_id = None
+
+    status = request.POST.get('changeset_status')
+    close_pr = request.POST.get('save_close')
+    delete = request.POST.get('save_delete')
+    f_path = request.POST.get('f_path')
+    line_no = request.POST.get('line')
+
+    if (status or close_pr or delete) and (f_path or line_no):
+        # status votes and closing is only possible in general comments
+        raise HTTPBadRequest()
+
+    if not allowed_to_change_status:
+        if status or close_pr:
+            h.flash(_('No permission to change status'), 'error')
+            raise HTTPForbidden()
+
+    if pull_request and delete == "delete":
+        if (pull_request.owner_id == request.authuser.user_id or
+            h.HasPermissionAny('hg.admin')() or
+            h.HasRepoPermissionLevel('admin')(pull_request.org_repo.repo_name) or
+            h.HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name)
+            ) and not pull_request.is_closed():
+            PullRequestModel().delete(pull_request)
+            Session().commit()
+            h.flash(_('Successfully deleted pull request %s') % pull_request_id,
+                    category='success')
+            return {
+               'location': h.url('my_pullrequests'), # or repo pr list?
+            }
+            raise HTTPFound(location=h.url('my_pullrequests')) # or repo pr list?
+        raise HTTPForbidden()
+
+    text = request.POST.get('text', '').strip()
 
+    comment = ChangesetCommentsModel().create(
+        text=text,
+        repo=c.db_repo.repo_id,
+        author=request.authuser.user_id,
+        revision=revision,
+        pull_request=pull_request_id,
+        f_path=f_path or None,
+        line_no=line_no or None,
+        status_change=ChangesetStatus.get_status_lbl(status) if status else None,
+        closing_pr=close_pr,
+    )
+
+    if status:
+        ChangesetStatusModel().set_status(
+            c.db_repo.repo_id,
+            status,
+            request.authuser.user_id,
+            comment,
+            revision=revision,
+            pull_request=pull_request_id,
+        )
+
+    if pull_request:
+        action = 'user_commented_pull_request:%s' % pull_request_id
+    else:
+        action = 'user_commented_revision:%s' % revision
+    action_logger(request.authuser, action, c.db_repo, request.ip_addr)
+
+    if pull_request and close_pr:
+        PullRequestModel().close_pull_request(pull_request_id)
+        action_logger(request.authuser,
+                      'user_closed_pull_request:%s' % pull_request_id,
+                      c.db_repo, request.ip_addr)
+
+    Session().commit()
+
+    data = {
+       'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
+    }
+    if comment is not None:
+        c.comment = comment
+        data.update(comment.get_dict())
+        data.update({'rendered_text':
+                     render('changeset/changeset_comment_block.html')})
+
+    return data
+
+def delete_cs_pr_comment(repo_name, comment_id):
+    """Delete a comment from a changeset or pull request"""
+    co = ChangesetComment.get_or_404(comment_id)
+    if co.repo.repo_name != repo_name:
+        raise HTTPNotFound()
+    if co.pull_request and co.pull_request.is_closed():
+        # don't allow deleting comments on closed pull request
+        raise HTTPForbidden()
+
+    owner = co.author_id == request.authuser.user_id
+    repo_admin = h.HasRepoPermissionLevel('admin')(repo_name)
+    if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
+        ChangesetCommentsModel().delete(comment=co)
+        Session().commit()
+        return True
+    else:
+        raise HTTPForbidden()
 
 class ChangesetController(BaseRepoController):
 
-    def __before__(self):
-        super(ChangesetController, self).__before__()
+    def _before(self, *args, **kwargs):
+        super(ChangesetController, self)._before(*args, **kwargs)
         c.affected_files_cut_off = 60
 
-    def __load_data(self):
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.user_groups_array = repo_model.get_user_groups_js()
-
     def _index(self, revision, method):
+        c.pull_request = None
         c.anchor_url = anchor_url
         c.ignorews_url = _ignorews_url
         c.context_url = _context_url
-        c.fulldiff = fulldiff = request.GET.get('fulldiff')
-        #get ranges of revisions if preset
+        c.fulldiff = request.GET.get('fulldiff') # for reporting number of changed files
+        # get ranges of revisions if preset
         rev_range = revision.split('...')[:2]
         enable_comments = True
         c.cs_repo = c.db_repo
@@ -207,7 +316,7 @@
             if not c.cs_ranges:
                 raise RepositoryError('Changeset range returned empty result')
 
-        except(ChangesetDoesNotExistError,), e:
+        except (ChangesetDoesNotExistError, EmptyRepositoryError):
             log.debug(traceback.format_exc())
             msg = _('Such revision does not exist for this repository')
             h.flash(msg, category='error')
@@ -226,7 +335,6 @@
 
         # Iterate over ranges (default changeset view is always one changeset)
         for changeset in c.cs_ranges:
-            inlines = []
             if method == 'show':
                 c.statuses.extend([ChangesetStatusModel().get_status(
                             c.db_repo.repo_id, changeset.raw_id)])
@@ -238,53 +346,46 @@
                                               revision=changeset.raw_id))
 
                 # Status change comments - mostly from pull requests
-                comments.update((st.changeset_comment_id, st.comment)
+                comments.update((st.comment_id, st.comment)
                                 for st in ChangesetStatusModel()
                                 .get_statuses(c.db_repo.repo_id,
                                               changeset.raw_id, with_revisions=True)
-                                if st.changeset_comment_id is not None)
+                                if st.comment_id is not None)
 
-                inlines = ChangesetCommentsModel()\
+                inlines = ChangesetCommentsModel() \
                             .get_inline_comments(c.db_repo.repo_id,
                                                  revision=changeset.raw_id)
                 c.inline_comments.extend(inlines)
 
-            c.changes[changeset.raw_id] = []
-
             cs2 = changeset.raw_id
             cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset().raw_id
             context_lcl = get_line_ctx('', request.GET)
-            ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
+            ign_whitespace_lcl = get_ignore_ws('', request.GET)
 
-            _diff = c.db_repo_scm_instance.get_diff(cs1, cs2,
+            raw_diff = diffs.get_diff(c.db_repo_scm_instance, cs1, cs2,
                 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
-            diff_limit = self.cut_off_limit if not fulldiff else None
-            diff_processor = diffs.DiffProcessor(_diff,
-                                                 vcs=c.db_repo_scm_instance.alias,
-                                                 format='gitdiff',
-                                                 diff_limit=diff_limit)
-            cs_changes = OrderedDict()
+            diff_limit = None if c.fulldiff else self.cut_off_limit
+            file_diff_data = []
             if method == 'show':
-                _parsed = diff_processor.prepare()
-                c.limited_diff = False
-                if isinstance(_parsed, LimitedDiffContainer):
-                    c.limited_diff = True
-                for f in _parsed:
+                diff_processor = diffs.DiffProcessor(raw_diff,
+                                                     vcs=c.db_repo_scm_instance.alias,
+                                                     diff_limit=diff_limit)
+                c.limited_diff = diff_processor.limited_diff
+                for f in diff_processor.parsed:
                     st = f['stats']
                     c.lines_added += st['added']
                     c.lines_deleted += st['deleted']
-                    fid = h.FID(changeset.raw_id, f['filename'])
-                    diff = diff_processor.as_html(enable_comments=enable_comments,
-                                                  parsed_lines=[f])
-                    cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
-                                       diff, st]
+                    filename = f['filename']
+                    fid = h.FID(changeset.raw_id, filename)
+                    url_fid = h.FID('', filename)
+                    html_diff = diffs.as_html(enable_comments=enable_comments, parsed_lines=[f])
+                    file_diff_data.append((fid, url_fid, f['operation'], f['old_filename'], filename, html_diff, st))
             else:
                 # downloads/raw we only need RAW diff nothing else
-                diff = diff_processor.as_raw()
-                cs_changes[''] = [None, None, None, None, diff, None]
-            c.changes[changeset.raw_id] = cs_changes
+                file_diff_data.append(('', None, None, None, raw_diff, None))
+            c.changes[changeset.raw_id] = (cs1, cs2, file_diff_data)
 
-        #sort comments in creation order
+        # sort comments in creation order
         c.comments = [com for com_id, com in sorted(comments.items())]
 
         # count inline comments
@@ -300,144 +401,58 @@
             response.content_type = 'text/plain'
             response.content_disposition = 'attachment; filename=%s.diff' \
                                             % revision[:12]
-            return diff
+            return raw_diff
         elif method == 'patch':
             response.content_type = 'text/plain'
-            c.diff = safe_unicode(diff)
+            c.diff = safe_unicode(raw_diff)
             return render('changeset/patch_changeset.html')
         elif method == 'raw':
             response.content_type = 'text/plain'
-            return diff
+            return raw_diff
         elif method == 'show':
-            self.__load_data()
             if len(c.cs_ranges) == 1:
                 return render('changeset/changeset.html')
             else:
                 c.cs_ranges_org = None
                 c.cs_comments = {}
                 revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-                c.jsdata = json.dumps(graph_data(c.db_repo_scm_instance, revs))
+                c.jsdata = graph_data(c.db_repo_scm_instance, revs)
                 return render('changeset/changeset_range.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def index(self, revision, method='show'):
         return self._index(revision, method=method)
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def changeset_raw(self, revision):
         return self._index(revision, method='raw')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def changeset_patch(self, revision):
         return self._index(revision, method='patch')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def changeset_download(self, revision):
         return self._index(revision, method='download')
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def comment(self, repo_name, revision):
-        status = request.POST.get('changeset_status')
-        text = request.POST.get('text', '').strip()
-
-        c.comment = comment = ChangesetCommentsModel().create(
-            text=text,
-            repo=c.db_repo.repo_id,
-            user=c.authuser.user_id,
-            revision=revision,
-            f_path=request.POST.get('f_path'),
-            line_no=request.POST.get('line'),
-            status_change=(ChangesetStatus.get_status_lbl(status)
-                           if status else None)
-        )
-
-        # get status if set !
-        if status:
-            # if latest status was from pull request and it's closed
-            # disallow changing status !
-            # dont_allow_on_closed_pull_request = True !
-
-            try:
-                ChangesetStatusModel().set_status(
-                    c.db_repo.repo_id,
-                    status,
-                    c.authuser.user_id,
-                    comment,
-                    revision=revision,
-                    dont_allow_on_closed_pull_request=True
-                )
-            except StatusChangeOnClosedPullRequestError:
-                log.debug(traceback.format_exc())
-                msg = _('Changing status on a changeset associated with '
-                        'a closed pull request is not allowed')
-                h.flash(msg, category='warning')
-                return redirect(h.url('changeset_home', repo_name=repo_name,
-                                      revision=revision))
-        action_logger(self.authuser,
-                      'user_commented_revision:%s' % revision,
-                      c.db_repo, self.ip_addr, self.sa)
-
-        Session().commit()
-
-        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
-            return redirect(h.url('changeset_home', repo_name=repo_name,
-                                  revision=revision))
-        #only ajax below
-        data = {
-           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
-        }
-        if comment is not None:
-            data.update(comment.get_dict())
-            data.update({'rendered_text':
-                         render('changeset/changeset_comment_block.html')})
-
-        return data
+        return create_cs_pr_comment(repo_name, revision=revision)
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def preview_comment(self):
-        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
-            raise HTTPBadRequest()
-        text = request.POST.get('text')
-        if text:
-            return h.rst_w_mentions(text)
-        return ''
-
-    @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def delete_comment(self, repo_name, comment_id):
-        co = ChangesetComment.get_or_404(comment_id)
-        if co.repo.repo_name != repo_name:
-            raise HTTPNotFound()
-        owner = co.author.user_id == c.authuser.user_id
-        repo_admin = h.HasRepoPermissionAny('repository.admin')(repo_name)
-        if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
-            ChangesetCommentsModel().delete(comment=co)
-            Session().commit()
-            return True
-        else:
-            raise HTTPForbidden()
+        return delete_cs_pr_comment(repo_name, comment_id)
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def changeset_info(self, repo_name, revision):
         if request.is_xhr:
@@ -448,9 +463,8 @@
         else:
             raise HTTPBadRequest()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def changeset_children(self, repo_name, revision):
         if request.is_xhr:
@@ -462,9 +476,8 @@
         else:
             raise HTTPBadRequest()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def changeset_parents(self, repo_name, revision):
         if request.is_xhr:
--- a/kallithea/controllers/compare.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/compare.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,7 @@
 kallithea.controllers.compare
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-compare controller for pylons showing differences between two
+compare controller showing differences between two
 repos, branches, bookmarks or tips
 
 This file was forked by the Kallithea project in July 2014.
@@ -30,29 +30,48 @@
 import logging
 import re
 
-from webob.exc import HTTPBadRequest
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound, HTTPBadRequest, HTTPNotFound
 
+from kallithea.config.routing import url
+from kallithea.lib.utils2 import safe_str, safe_int
 from kallithea.lib.vcs.utils.hgcompat import unionrepo
 from kallithea.lib import helpers as h
 from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib import diffs
 from kallithea.model.db import Repository
-from kallithea.lib.diffs import LimitedDiffContainer
 from kallithea.controllers.changeset import _ignorews_url, _context_url
 from kallithea.lib.graphmod import graph_data
-from kallithea.lib.compat import json
 
 log = logging.getLogger(__name__)
 
 
 class CompareController(BaseRepoController):
 
-    def __before__(self):
-        super(CompareController, self).__before__()
+    def _before(self, *args, **kwargs):
+        super(CompareController, self)._before(*args, **kwargs)
+
+        # The base repository has already been retrieved.
+        c.a_repo = c.db_repo
+
+        # Retrieve the "changeset" repository (default: same as base).
+        other_repo = request.GET.get('other_repo', None)
+        if other_repo is None:
+            c.cs_repo = c.a_repo
+        else:
+            c.cs_repo = Repository.get_by_repo_name(other_repo)
+            if c.cs_repo is None:
+                msg = _('Could not find other repository %s') % other_repo
+                h.flash(msg, category='error')
+                raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
+
+        # Verify that it's even possible to compare these two repositories.
+        if c.a_repo.scm_instance.alias != c.cs_repo.scm_instance.alias:
+            msg = _('Cannot compare repositories of different types')
+            h.flash(msg, category='error')
+            raise HTTPFound(location=url('compare_home', repo_name=c.a_repo.repo_name))
 
     @staticmethod
     def _get_changesets(alias, org_repo, org_rev, other_repo, other_rev):
@@ -60,7 +79,7 @@
         Returns lists of changesets that can be merged from org_repo@org_rev
         to other_repo@other_rev
         ... and the other way
-        ... and the ancestor that would be used for merge
+        ... and the ancestors that would be used for merge
 
         :param org_repo: repo object, that is most likely the original repo we forked from
         :param org_rev: the revision we want our compare to be made
@@ -68,44 +87,43 @@
             all changesets that we need to obtain
         :param other_rev: revision we want out compare to be made on other_repo
         """
-        ancestor = None
+        ancestors = None
         if org_rev == other_rev:
             org_changesets = []
             other_changesets = []
-            ancestor = org_rev
 
         elif alias == 'hg':
-            #case two independent repos
+            # case two independent repos
             if org_repo != other_repo:
-                hgrepo = unionrepo.unionrepository(other_repo.baseui,
-                                                   other_repo.path,
-                                                   org_repo.path)
+                try:
+                    hgrepo = unionrepo.makeunionrepository(other_repo.baseui,
+                                                           other_repo.path,
+                                                           org_repo.path)
+                except AttributeError: # makeunionrepository was introduced in Mercurial 4.8 23f2299e9e53
+                    hgrepo = unionrepo.unionrepository(other_repo.baseui,
+                                                       other_repo.path,
+                                                       org_repo.path)
                 # all ancestors of other_rev will be in other_repo and
                 # rev numbers from hgrepo can be used in other_repo - org_rev ancestors cannot
 
-            #no remote compare do it on the same repository
+            # no remote compare do it on the same repository
             else:
                 hgrepo = other_repo._repo
 
-            if org_repo.EMPTY_CHANGESET in (org_rev, other_rev):
-                # work around unexpected behaviour in Mercurial < 3.4
-                ancestor = org_repo.EMPTY_CHANGESET
+            ancestors = [hgrepo[ancestor].hex() for ancestor in
+                         hgrepo.revs("id(%s) & ::id(%s)", other_rev, org_rev)]
+            if ancestors:
+                log.debug("shortcut found: %s is already an ancestor of %s", other_rev, org_rev)
             else:
-                ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
-                if ancestors:
-                    # FIXME: picks arbitrary ancestor - but there is usually only one
-                    try:
-                        ancestor = hgrepo[ancestors.first()].hex()
-                    except AttributeError:
-                        # removed in hg 3.2
-                        ancestor = hgrepo[ancestors[0]].hex()
+                log.debug("no shortcut found: %s is not an ancestor of %s", other_rev, org_rev)
+                ancestors = [hgrepo[ancestor].hex() for ancestor in
+                             hgrepo.revs("heads(::id(%s) & ::id(%s))", org_rev, other_rev)] # FIXME: expensive!
 
             other_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
                                      other_rev, org_rev, org_rev)
             other_changesets = [other_repo.get_changeset(rev) for rev in other_revs]
             org_revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
                                    org_rev, other_rev, other_rev)
-
             org_changesets = [org_repo.get_changeset(hgrepo[rev].hex()) for rev in org_revs]
 
         elif alias == 'git':
@@ -114,22 +132,25 @@
                 from dulwich.client import SubprocessGitClient
 
                 gitrepo = Repo(org_repo.path)
-                SubprocessGitClient(thin_packs=False).fetch(other_repo.path, gitrepo)
+                SubprocessGitClient(thin_packs=False).fetch(safe_str(other_repo.path), gitrepo)
 
                 gitrepo_remote = Repo(other_repo.path)
-                SubprocessGitClient(thin_packs=False).fetch(org_repo.path, gitrepo_remote)
+                SubprocessGitClient(thin_packs=False).fetch(safe_str(org_repo.path), gitrepo_remote)
 
-                revs = []
-                for x in gitrepo_remote.get_walker(include=[other_rev],
-                                                   exclude=[org_rev]):
-                    revs.append(x.commit.id)
-
+                revs = [
+                    x.commit.id
+                    for x in gitrepo_remote.get_walker(include=[other_rev],
+                                                       exclude=[org_rev])
+                ]
                 other_changesets = [other_repo.get_changeset(rev) for rev in reversed(revs)]
                 if other_changesets:
-                    ancestor = other_changesets[0].parents[0].raw_id
+                    ancestors = [other_changesets[0].parents[0].raw_id]
                 else:
                     # no changesets from other repo, ancestor is the other_rev
-                    ancestor = other_rev
+                    ancestors = [other_rev]
+
+                gitrepo.close()
+                gitrepo_remote.close()
 
             else:
                 so, se = org_repo.run_git_command(
@@ -141,35 +162,27 @@
                 so, se = org_repo.run_git_command(
                     ['merge-base', org_rev, other_rev]
                 )
-                ancestor = re.findall(r'[0-9a-fA-F]{40}', so)[0]
+                ancestors = [re.findall(r'[0-9a-fA-F]{40}', so)[0]]
             org_changesets = []
 
         else:
             raise Exception('Bad alias only git and hg is allowed')
 
-        return other_changesets, org_changesets, ancestor
+        return other_changesets, org_changesets, ancestors
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def index(self, repo_name):
         c.compare_home = True
-        org_repo = c.db_repo.repo_name
-        other_repo = request.GET.get('other_repo', org_repo)
-        c.a_repo = Repository.get_by_repo_name(org_repo)
-        c.cs_repo = Repository.get_by_repo_name(other_repo)
-        c.a_ref_name = c.cs_ref_name = _('Select changeset')
+        c.a_ref_name = c.cs_ref_name = None
         return render('compare/compare_diff.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def compare(self, repo_name, org_ref_type, org_ref_name, other_ref_type, other_ref_name):
         org_ref_name = org_ref_name.strip()
         other_ref_name = other_ref_name.strip()
 
-        org_repo = c.db_repo.repo_name
-        other_repo = request.GET.get('other_repo', org_repo)
         # If merge is True:
         #   Show what org would get if merged with other:
         #   List changesets that are ancestors of other but not of org.
@@ -180,16 +193,16 @@
         #   Changesets in one and not in the other will be ignored
         merge = bool(request.GET.get('merge'))
         # fulldiff disables cut_off_limit
-        c.fulldiff = request.GET.get('fulldiff')
+        fulldiff = request.GET.get('fulldiff')
         # partial uses compare_cs.html template directly
         partial = request.environ.get('HTTP_X_PARTIAL_XHR')
-        # as_form puts hidden input field with changeset revisions
-        c.as_form = partial and request.GET.get('as_form')
-        # swap url for compare_diff page - never partial and never as_form
+        # is_ajax_preview puts hidden input field with changeset revisions
+        c.is_ajax_preview = partial and request.GET.get('is_ajax_preview')
+        # swap url for compare_diff page - never partial and never is_ajax_preview
         c.swap_url = h.url('compare_url',
-            repo_name=other_repo,
+            repo_name=c.cs_repo.repo_name,
             org_ref_type=other_ref_type, org_ref_name=other_ref_name,
-            other_repo=org_repo,
+            other_repo=c.a_repo.repo_name,
             other_ref_type=org_ref_type, other_ref_name=org_ref_name,
             merge=merge or '')
 
@@ -197,64 +210,56 @@
         c.ignorews_url = _ignorews_url
         c.context_url = _context_url
         ignore_whitespace = request.GET.get('ignorews') == '1'
-        line_context = request.GET.get('context', 3)
-
-        org_repo = Repository.get_by_repo_name(org_repo)
-        other_repo = Repository.get_by_repo_name(other_repo)
-
-        if org_repo is None:
-            msg = 'Could not find org repo %s' % org_repo
-            log.error(msg)
-            h.flash(msg, category='error')
-            return redirect(url('compare_home', repo_name=c.repo_name))
+        line_context = safe_int(request.GET.get('context'), 3)
 
-        if other_repo is None:
-            msg = 'Could not find other repo %s' % other_repo
-            log.error(msg)
-            h.flash(msg, category='error')
-            return redirect(url('compare_home', repo_name=c.repo_name))
-
-        if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
-            msg = 'compare of two different kind of remote repos not available'
-            log.error(msg)
-            h.flash(msg, category='error')
-            return redirect(url('compare_home', repo_name=c.repo_name))
-
-        c.a_rev = self._get_ref_rev(org_repo, org_ref_type, org_ref_name,
+        c.a_rev = self._get_ref_rev(c.a_repo, org_ref_type, org_ref_name,
             returnempty=True)
-        c.cs_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
+        c.cs_rev = self._get_ref_rev(c.cs_repo, other_ref_type, other_ref_name)
 
         c.compare_home = False
-        c.a_repo = org_repo
         c.a_ref_name = org_ref_name
         c.a_ref_type = org_ref_type
-        c.cs_repo = other_repo
         c.cs_ref_name = other_ref_name
         c.cs_ref_type = other_ref_type
 
-        c.cs_ranges, c.cs_ranges_org, c.ancestor = self._get_changesets(
-            org_repo.scm_instance.alias, org_repo.scm_instance, c.a_rev,
-            other_repo.scm_instance, c.cs_rev)
+        c.cs_ranges, c.cs_ranges_org, c.ancestors = self._get_changesets(
+            c.a_repo.scm_instance.alias, c.a_repo.scm_instance, c.a_rev,
+            c.cs_repo.scm_instance, c.cs_rev)
         raw_ids = [x.raw_id for x in c.cs_ranges]
-        c.cs_comments = other_repo.get_comments(raw_ids)
-        c.statuses = other_repo.statuses(raw_ids)
+        c.cs_comments = c.cs_repo.get_comments(raw_ids)
+        c.cs_statuses = c.cs_repo.statuses(raw_ids)
 
         revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-        c.jsdata = json.dumps(graph_data(c.cs_repo.scm_instance, revs))
+        c.jsdata = graph_data(c.cs_repo.scm_instance, revs)
 
         if partial:
             return render('compare/compare_cs.html')
-        if merge and c.ancestor:
+
+        org_repo = c.a_repo
+        other_repo = c.cs_repo
+
+        if merge:
+            rev1 = msg = None
+            if not c.cs_ranges:
+                msg = _('Cannot show empty diff')
+            elif not c.ancestors:
+                msg = _('No ancestor found for merge diff')
+            elif len(c.ancestors) == 1:
+                rev1 = c.ancestors[0]
+            else:
+                msg = _('Multiple merge ancestors found for merge compare')
+            if rev1 is None:
+                h.flash(msg, category='error')
+                log.error(msg)
+                raise HTTPNotFound
+
             # case we want a simple diff without incoming changesets,
             # previewing what will be merged.
             # Make the diff on the other repo (which is known to have other_rev)
             log.debug('Using ancestor %s as rev1 instead of %s',
-                      c.ancestor, c.a_rev)
-            rev1 = c.ancestor
+                      rev1, c.a_rev)
             org_repo = other_repo
         else: # comparing tips, not necessarily linearly related
-            if merge:
-                log.error('Unable to find ancestor revision')
             if org_repo != other_repo:
                 # TODO: we could do this by using hg unionrepo
                 log.error('cannot compare across repos %s and %s', org_repo, other_repo)
@@ -262,34 +267,26 @@
                 raise HTTPBadRequest
             rev1 = c.a_rev
 
-        diff_limit = self.cut_off_limit if not c.fulldiff else None
+        diff_limit = None if fulldiff else self.cut_off_limit
 
         log.debug('running diff between %s and %s in %s',
                   rev1, c.cs_rev, org_repo.scm_instance.path)
-        txtdiff = org_repo.scm_instance.get_diff(rev1=rev1, rev2=c.cs_rev,
+        raw_diff = diffs.get_diff(org_repo.scm_instance, rev1=rev1, rev2=c.cs_rev,
                                       ignore_whitespace=ignore_whitespace,
                                       context=line_context)
 
-        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
-                                             diff_limit=diff_limit)
-        _parsed = diff_processor.prepare()
-
-        c.limited_diff = False
-        if isinstance(_parsed, LimitedDiffContainer):
-            c.limited_diff = True
-
-        c.files = []
-        c.changes = {}
+        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
+        c.limited_diff = diff_processor.limited_diff
+        c.file_diff_data = []
         c.lines_added = 0
         c.lines_deleted = 0
-        for f in _parsed:
+        for f in diff_processor.parsed:
             st = f['stats']
-            if not st['binary']:
-                c.lines_added += st['added']
-                c.lines_deleted += st['deleted']
-            fid = h.FID('', f['filename'])
-            c.files.append([fid, f['operation'], f['filename'], f['stats']])
-            htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
-            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
+            c.lines_added += st['added']
+            c.lines_deleted += st['deleted']
+            filename = f['filename']
+            fid = h.FID('', filename)
+            html_diff = diffs.as_html(enable_comments=False, parsed_lines=[f])
+            c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, html_diff, st))
 
         return render('compare/compare_diff.html')
--- a/kallithea/controllers/error.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/error.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,11 +28,9 @@
 import os
 import cgi
 import logging
-import paste.fileapp
 
-from pylons import tmpl_context as c, request, config
-from pylons.i18n.translation import _
-from pylons.middleware import media_path
+from tg import tmpl_context as c, request, config, expose
+from tg.i18n import ugettext as _
 
 from kallithea.lib.base import BaseController, render
 
@@ -49,12 +47,13 @@
     ErrorDocuments middleware in your config/middleware.py file.
     """
 
-    def __before__(self):
+    def _before(self, *args, **kwargs):
         # disable all base actions since we don't need them here
         pass
 
-    def document(self):
-        resp = request.environ.get('pylons.original_response')
+    @expose('/errors/error_document.html')
+    def document(self, *args, **kwargs):
+        resp = request.environ.get('tg.original_response')
         c.site_name = config.get('title')
 
         log.debug('### %s ###', resp and resp.status or 'no response')
@@ -71,22 +70,7 @@
             c.error_message = _('No response')
             c.error_explanation = _('Unknown error')
 
-        return render('/errors/error_document.html')
-
-    def img(self, id):
-        """Serve Pylons' stock images"""
-        return self._serve_file(os.path.join(media_path, 'img', id))
-
-    def style(self, id):
-        """Serve Pylons' stock stylesheets"""
-        return self._serve_file(os.path.join(media_path, 'style', id))
-
-    def _serve_file(self, path):
-        """Call Paste's FileApp (a WSGI application) to serve the file
-        at the specified path
-        """
-        fapp = paste.fileapp.FileApp(path)
-        return fapp(request.environ, self.start_response)
+        return dict()
 
     def get_error_explanation(self, code):
         """ get the error explanations of int codes
--- a/kallithea/controllers/feed.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/feed.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,77 +28,65 @@
 
 import logging
 
-from pylons import response, tmpl_context as c
-from pylons.i18n.translation import _
+from tg import response, tmpl_context as c
+from tg.i18n import ugettext as _
 
 from beaker.cache import cache_region, region_invalidate
 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
+from kallithea import CONFIG
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib.base import BaseRepoController
-from kallithea.lib.diffs import DiffProcessor, LimitedDiffContainer
+from kallithea.lib.diffs import DiffProcessor
 from kallithea.model.db import CacheInvalidation
 from kallithea.lib.utils2 import safe_int, str2bool, safe_unicode
 
 log = logging.getLogger(__name__)
 
 
+language = 'en-us'
+ttl = "5"
+
+
 class FeedController(BaseRepoController):
 
-    @LoginRequired(api_access=True)
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def __before__(self):
-        super(FeedController, self).__before__()
-        #common values for feeds
-        self.description = _('Changes on %s repository')
-        self.title = self.title = _('%s %s feed') % (c.site_name, '%s')
-        self.language = 'en-us'
-        self.ttl = "5"
-        import kallithea
-        CONF = kallithea.CONFIG
-        self.include_diff = str2bool(CONF.get('rss_include_diff', False))
-        self.feed_nr = safe_int(CONF.get('rss_items_per_page', 20))
-        # we need to protect from parsing huge diffs here other way
-        # we can kill the server
-        self.feed_diff_limit = safe_int(CONF.get('rss_cut_off_limit', 32 * 1024))
+    @LoginRequired(api_access=True, allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
+    def _before(self, *args, **kwargs):
+        super(FeedController, self)._before(*args, **kwargs)
 
     def _get_title(self, cs):
         return h.shorter(cs.message, 160)
 
-    def __changes(self, cs):
+    def __get_desc(self, cs):
+        desc_msg = [(_('%s committed on %s')
+                     % (h.person(cs.author), h.fmt_date(cs.date))) + '<br/>']
+        # branches, tags, bookmarks
+        for branch in cs.branches:
+            desc_msg.append('branch: %s<br/>' % branch)
+        for book in cs.bookmarks:
+            desc_msg.append('bookmark: %s<br/>' % book)
+        for tag in cs.tags:
+            desc_msg.append('tag: %s<br/>' % tag)
+
         changes = []
-        diff_processor = DiffProcessor(cs.diff(),
-                                       diff_limit=self.feed_diff_limit)
-        _parsed = diff_processor.prepare(inline_diff=False)
-        limited_diff = False
-        if isinstance(_parsed, LimitedDiffContainer):
-            limited_diff = True
+        diff_limit = safe_int(CONFIG.get('rss_cut_off_limit', 32 * 1024))
+        raw_diff = cs.diff()
+        diff_processor = DiffProcessor(raw_diff,
+                                       diff_limit=diff_limit,
+                                       inline_diff=False)
 
-        for st in _parsed:
+        for st in diff_processor.parsed:
             st.update({'added': st['stats']['added'],
                        'removed': st['stats']['deleted']})
             changes.append('\n %(operation)s %(filename)s '
                            '(%(added)s lines added, %(removed)s lines removed)'
                             % st)
-        if limited_diff:
+        if diff_processor.limited_diff:
             changes = changes + ['\n ' +
                                  _('Changeset was too big and was cut off...')]
-        return diff_processor, changes
 
-    def __get_desc(self, cs):
-        desc_msg = [(_('%s committed on %s')
-                     % (h.person(cs.author), h.fmt_date(cs.date))) + '<br/>']
-        #branches, tags, bookmarks
-        if cs.branch:
-            desc_msg.append('branch: %s<br/>' % cs.branch)
-        if h.is_hg(c.db_repo_scm_instance):
-            for book in cs.bookmarks:
-                desc_msg.append('bookmark: %s<br/>' % book)
-        for tag in cs.tags:
-            desc_msg.append('tag: %s<br/>' % tag)
-        diff_processor, changes = self.__changes(cs)
         # rev link
         _url = h.canonical_url('changeset_home', repo_name=c.db_repo.repo_name,
                    revision=cs.raw_id)
@@ -108,26 +96,27 @@
         desc_msg.append(h.urlify_text(cs.message))
         desc_msg.append('\n')
         desc_msg.extend(changes)
-        if self.include_diff:
+        if str2bool(CONFIG.get('rss_include_diff', False)):
             desc_msg.append('\n\n')
-            desc_msg.append(diff_processor.as_raw())
+            desc_msg.append(raw_diff)
         desc_msg.append('</pre>')
         return map(safe_unicode, desc_msg)
 
     def atom(self, repo_name):
         """Produce an atom-1.0 feed via feedgenerator module"""
 
-        @cache_region('long_term')
+        @cache_region('long_term', '_get_feed_from_cache')
         def _get_feed_from_cache(key, kind):
             feed = Atom1Feed(
-                 title=self.title % repo_name,
-                 link=h.canonical_url('summary_home', repo_name=repo_name),
-                 description=self.description % repo_name,
-                 language=self.language,
-                 ttl=self.ttl
+                title=_('%s %s feed') % (c.site_name, repo_name),
+                link=h.canonical_url('summary_home', repo_name=repo_name),
+                description=_('Changes on %s repository') % repo_name,
+                language=language,
+                ttl=ttl
             )
 
-            for cs in reversed(list(c.db_repo_scm_instance[-self.feed_nr:])):
+            rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
+            for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
                 feed.add_item(title=self._get_title(cs),
                               link=h.canonical_url('changeset_home', repo_name=repo_name,
                                        revision=cs.raw_id),
@@ -142,23 +131,24 @@
         kind = 'ATOM'
         valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
         if not valid:
-            region_invalidate(_get_feed_from_cache, None, repo_name, kind)
+            region_invalidate(_get_feed_from_cache, None, '_get_feed_from_cache', repo_name, kind)
         return _get_feed_from_cache(repo_name, kind)
 
     def rss(self, repo_name):
         """Produce an rss2 feed via feedgenerator module"""
 
-        @cache_region('long_term')
+        @cache_region('long_term', '_get_feed_from_cache')
         def _get_feed_from_cache(key, kind):
             feed = Rss201rev2Feed(
-                title=self.title % repo_name,
+                title=_('%s %s feed') % (c.site_name, repo_name),
                 link=h.canonical_url('summary_home', repo_name=repo_name),
-                description=self.description % repo_name,
-                language=self.language,
-                ttl=self.ttl
+                description=_('Changes on %s repository') % repo_name,
+                language=language,
+                ttl=ttl
             )
 
-            for cs in reversed(list(c.db_repo_scm_instance[-self.feed_nr:])):
+            rss_items_per_page = safe_int(CONFIG.get('rss_items_per_page', 20))
+            for cs in reversed(list(c.db_repo_scm_instance[-rss_items_per_page:])):
                 feed.add_item(title=self._get_title(cs),
                               link=h.canonical_url('changeset_home', repo_name=repo_name,
                                        revision=cs.raw_id),
@@ -173,5 +163,5 @@
         kind = 'RSS'
         valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
         if not valid:
-            region_invalidate(_get_feed_from_cache, None, repo_name, kind)
+            region_invalidate(_get_feed_from_cache, None, '_get_feed_from_cache', repo_name, kind)
         return _get_feed_from_cache(repo_name, kind)
--- a/kallithea/controllers/files.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/files.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,29 +26,31 @@
 """
 
 import os
+import posixpath
 import logging
 import traceback
 import tempfile
 import shutil
 
-from pylons import request, response, tmpl_context as c, url
-from pylons.i18n.translation import _
-from pylons.controllers.util import redirect
-from kallithea.lib.utils import jsonify, action_logger
+from tg import request, response, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
+from kallithea.config.routing import url
+from kallithea.lib.utils import action_logger
 from kallithea.lib import diffs
 from kallithea.lib import helpers as h
 
 from kallithea.lib.compat import OrderedDict
-from kallithea.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
-    str2bool
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseRepoController, render
+from kallithea.lib.utils2 import convert_line_endings, detect_mode, safe_str, \
+    str2bool, safe_int
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
+from kallithea.lib.base import BaseRepoController, render, jsonify
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.exceptions import RepositoryError, \
     ChangesetDoesNotExistError, EmptyRepositoryError, \
-    ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
+    ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError, \
     NodeDoesNotExistError, ChangesetError, NodeError
 from kallithea.lib.vcs.nodes import FileNode
 
@@ -56,7 +58,7 @@
 from kallithea.model.scm import ScmModel
 from kallithea.model.db import Repository
 
-from kallithea.controllers.changeset import anchor_url, _ignorews_url,\
+from kallithea.controllers.changeset import anchor_url, _ignorews_url, \
     _context_url, get_line_ctx, get_ignore_ws
 from webob.exc import HTTPNotFound
 from kallithea.lib.exceptions import NonRelativePathError
@@ -67,9 +69,8 @@
 
 class FilesController(BaseRepoController):
 
-    def __before__(self):
-        super(FilesController, self).__before__()
-        c.cut_off_limit = self.cut_off_limit
+    def _before(self, *args, **kwargs):
+        super(FilesController, self)._before(*args, **kwargs)
 
     def __get_cs(self, rev, silent_empty=False):
         """
@@ -92,7 +93,7 @@
             h.flash(h.literal(_('There are no files yet. %s') % add_new),
                     category='warning')
             raise HTTPNotFound()
-        except(ChangesetDoesNotExistError, LookupError), e:
+        except (ChangesetDoesNotExistError, LookupError):
             msg = _('Such revision does not exist for this repository')
             h.flash(msg, category='error')
             raise HTTPNotFound()
@@ -112,7 +113,7 @@
             file_node = cs.get_node(path)
             if file_node.is_dir():
                 raise RepositoryError('given path is a directory')
-        except(ChangesetDoesNotExistError,), e:
+        except ChangesetDoesNotExistError:
             msg = _('Such revision does not exist for this repository')
             h.flash(msg, category='error')
             raise HTTPNotFound()
@@ -122,9 +123,8 @@
 
         return file_node
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def index(self, repo_name, revision, f_path, annotate=False):
         # redirect to given revision from form if given
         post_revision = request.POST.get('at_rev', None)
@@ -137,6 +137,9 @@
         c.f_path = f_path
         c.annotate = annotate
         cur_rev = c.changeset.revision
+        # used in files_source.html:
+        c.cut_off_limit = self.cut_off_limit
+        c.fulldiff = request.GET.get('fulldiff')
 
         # prev link
         try:
@@ -162,13 +165,11 @@
         try:
             c.file = c.changeset.get_node(f_path)
 
-            if c.file.is_file():
+            if c.file.is_submodule():
+                raise HTTPFound(location=c.file.url)
+            elif c.file.is_file():
                 c.load_full_history = False
-                file_last_cs = c.file.last_changeset
-                c.file_changeset = (c.changeset
-                                    if c.changeset.revision < file_last_cs.revision
-                                    else file_last_cs)
-                #determine if we're on branch head
+                # determine if we're on branch head
                 _branches = c.db_repo_scm_instance.branches
                 c.on_branch_head = revision in _branches.keys() + _branches.values()
                 _hist = []
@@ -190,7 +191,7 @@
 
         # TODO: tags and bookmarks?
         c.revision_options = [(c.changeset.raw_id,
-                              _('%s at %s') % (c.changeset.branch, h.short_id(c.changeset.raw_id)))] + \
+                              _('%s at %s') % (b, h.short_id(c.changeset.raw_id))) for b in c.changeset.branches] + \
             [(n, b) for b, n in c.db_repo_scm_instance.branches.items()]
         if c.db_repo_scm_instance.closed_branches:
             prefix = _('(closed)') + ' '
@@ -199,13 +200,11 @@
 
         return render('files/files.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def history(self, repo_name, revision, f_path):
         changeset = self.__get_cs(revision)
-        f_path = f_path
         _file = changeset.get_node(f_path)
         if _file.is_file():
             file_history, _hist = self._get_node_history(changeset, f_path)
@@ -223,12 +222,10 @@
             }
             return data
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def authors(self, repo_name, revision, f_path):
         changeset = self.__get_cs(revision)
-        f_path = f_path
         _file = changeset.get_node(f_path)
         if _file.is_file():
             file_history, _hist = self._get_node_history(changeset, f_path)
@@ -237,9 +234,8 @@
                 c.authors.append((h.email(a), h.person(a)))
             return render('files/files_history_box.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def rawfile(self, repo_name, revision, f_path):
         cs = self.__get_cs(revision)
         file_node = self.__get_filenode(cs, f_path)
@@ -250,9 +246,8 @@
         response.content_type = file_node.mimetype
         return file_node.content
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def raw(self, repo_name, revision, f_path):
         cs = self.__get_cs(revision)
         file_node = self.__get_filenode(cs, f_path)
@@ -298,7 +293,7 @@
         return file_node.content
 
     @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+    @HasRepoPermissionLevelDecorator('write')
     def delete(self, repo_name, revision, f_path):
         repo = c.db_repo
         if repo.enable_locking and repo.locked[0]:
@@ -306,7 +301,7 @@
                 % (h.person_by_id(repo.locked[0]),
                    h.fmt_date(h.time_to_datetime(repo.locked[1]))),
                 'warning')
-            return redirect(h.url('files_home',
+            raise HTTPFound(location=h.url('files_home',
                                   repo_name=repo_name, revision='tip'))
 
         # check if revision is a branch identifier- basically we cannot
@@ -316,7 +311,7 @@
         if revision not in _branches.keys() + _branches.values():
             h.flash(_('You can only delete files with revision '
                       'being a valid branch'), category='warning')
-            return redirect(h.url('files_home',
+            raise HTTPFound(location=h.url('files_home',
                                   repo_name=repo_name, revision='tip',
                                   f_path=f_path))
 
@@ -328,7 +323,7 @@
         c.default_message = _('Deleted file %s via Kallithea') % (f_path)
         c.f_path = f_path
         node_path = f_path
-        author = self.authuser.full_contact
+        author = request.authuser.full_contact
 
         if r_post:
             message = r_post.get('message') or c.default_message
@@ -340,7 +335,7 @@
                     }
                 }
                 self.scm_model.delete_nodes(
-                    user=c.authuser.user_id, repo=c.db_repo,
+                    user=request.authuser.user_id, repo=c.db_repo,
                     message=message,
                     nodes=nodes,
                     parent_cs=c.cs,
@@ -352,13 +347,13 @@
             except Exception:
                 log.error(traceback.format_exc())
                 h.flash(_('Error occurred during commit'), category='error')
-            return redirect(url('changeset_home',
+            raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
         return render('files/files_delete.html')
 
     @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+    @HasRepoPermissionLevelDecorator('write')
     def edit(self, repo_name, revision, f_path):
         repo = c.db_repo
         if repo.enable_locking and repo.locked[0]:
@@ -366,7 +361,7 @@
                 % (h.person_by_id(repo.locked[0]),
                    h.fmt_date(h.time_to_datetime(repo.locked[1]))),
                 'warning')
-            return redirect(h.url('files_home',
+            raise HTTPFound(location=h.url('files_home',
                                   repo_name=repo_name, revision='tip'))
 
         # check if revision is a branch identifier- basically we cannot
@@ -376,7 +371,7 @@
         if revision not in _branches.keys() + _branches.values():
             h.flash(_('You can only edit files with revision '
                       'being a valid branch'), category='warning')
-            return redirect(h.url('files_home',
+            raise HTTPFound(location=h.url('files_home',
                                   repo_name=repo_name, revision='tip',
                                   f_path=f_path))
 
@@ -386,7 +381,7 @@
         c.file = self.__get_filenode(c.cs, f_path)
 
         if c.file.is_binary:
-            return redirect(url('files_home', repo_name=c.repo_name,
+            raise HTTPFound(location=url('files_home', repo_name=c.repo_name,
                             revision=c.cs.raw_id, f_path=f_path))
         c.default_message = _('Edited file %s via Kallithea') % (f_path)
         c.f_path = f_path
@@ -401,16 +396,16 @@
             content = convert_line_endings(r_post.get('content', ''), mode)
 
             message = r_post.get('message') or c.default_message
-            author = self.authuser.full_contact
+            author = request.authuser.full_contact
 
             if content == old_content:
                 h.flash(_('No changes'), category='warning')
-                return redirect(url('changeset_home', repo_name=c.repo_name,
+                raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             try:
                 self.scm_model.commit_change(repo=c.db_repo_scm_instance,
                                              repo_name=repo_name, cs=c.cs,
-                                             user=self.authuser.user_id,
+                                             user=request.authuser.user_id,
                                              author=author, message=message,
                                              content=content, f_path=f_path)
                 h.flash(_('Successfully committed to %s') % f_path,
@@ -418,22 +413,22 @@
             except Exception:
                 log.error(traceback.format_exc())
                 h.flash(_('Error occurred during commit'), category='error')
-            return redirect(url('changeset_home',
+            raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
         return render('files/files_edit.html')
 
     @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
+    @HasRepoPermissionLevelDecorator('write')
     def add(self, repo_name, revision, f_path):
 
-        repo = Repository.get_by_repo_name(repo_name)
+        repo = c.db_repo
         if repo.enable_locking and repo.locked[0]:
             h.flash(_('This repository has been locked by %s on %s')
                 % (h.person_by_id(repo.locked[0]),
                    h.fmt_date(h.time_to_datetime(repo.locked[1]))),
                   'warning')
-            return redirect(h.url('files_home',
+            raise HTTPFound(location=h.url('files_home',
                                   repo_name=repo_name, revision='tip'))
 
         r_post = request.POST
@@ -462,16 +457,16 @@
 
             if not content:
                 h.flash(_('No content'), category='warning')
-                return redirect(url('changeset_home', repo_name=c.repo_name,
+                raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             if not filename:
                 h.flash(_('No filename'), category='warning')
-                return redirect(url('changeset_home', repo_name=c.repo_name,
+                raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
-            #strip all crap out of file, just leave the basename
+            # strip all crap out of file, just leave the basename
             filename = os.path.basename(filename)
-            node_path = os.path.join(location, filename)
-            author = self.authuser.full_contact
+            node_path = posixpath.join(location, filename)
+            author = request.authuser.full_contact
 
             try:
                 nodes = {
@@ -480,7 +475,7 @@
                     }
                 }
                 self.scm_model.create_nodes(
-                    user=c.authuser.user_id, repo=c.db_repo,
+                    user=request.authuser.user_id, repo=c.db_repo,
                     message=message,
                     nodes=nodes,
                     parent_cs=c.cs,
@@ -492,21 +487,20 @@
             except NonRelativePathError as e:
                 h.flash(_('Location must be relative path and must not '
                           'contain .. in path'), category='warning')
-                return redirect(url('changeset_home', repo_name=c.repo_name,
+                raise HTTPFound(location=url('changeset_home', repo_name=c.repo_name,
                                     revision='tip'))
             except (NodeError, NodeAlreadyExistsError) as e:
                 h.flash(_(e), category='error')
             except Exception:
                 log.error(traceback.format_exc())
                 h.flash(_('Error occurred during commit'), category='error')
-            return redirect(url('changeset_home',
+            raise HTTPFound(location=url('changeset_home',
                                 repo_name=c.repo_name, revision='tip'))
 
         return render('files/files_add.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def archivefile(self, repo_name, fname):
         fileformat = None
         revision = None
@@ -548,7 +542,7 @@
         archive_path = None
         cached_archive_path = None
         archive_cache_dir = CONFIG.get('archive_cache_dir')
-        if archive_cache_dir and not subrepos: # TOOD: subrepo caching?
+        if archive_cache_dir and not subrepos: # TODO: subrepo caching?
             if not os.path.isdir(archive_cache_dir):
                 os.makedirs(archive_cache_dir)
             cached_archive_path = os.path.join(archive_cache_dir, archive_name)
@@ -583,34 +577,34 @@
                 log.debug('Destroying temp archive %s', archive_path)
                 os.remove(archive_path)
 
-        action_logger(user=c.authuser,
+        action_logger(user=request.authuser,
                       action='user_downloaded_archive:%s' % (archive_name),
-                      repo=repo_name, ipaddr=self.ip_addr, commit=True)
+                      repo=repo_name, ipaddr=request.ip_addr, commit=True)
 
         response.content_disposition = str('attachment; filename=%s' % (archive_name))
         response.content_type = str(content_type)
         return get_chunked_archive(archive_path)
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def diff(self, repo_name, f_path):
         ignore_whitespace = request.GET.get('ignorews') == '1'
-        line_context = request.GET.get('context', 3)
+        line_context = safe_int(request.GET.get('context'), 3)
         diff2 = request.GET.get('diff2', '')
         diff1 = request.GET.get('diff1', '') or diff2
         c.action = request.GET.get('diff')
         c.no_changes = diff1 == diff2
         c.f_path = f_path
         c.big_diff = False
+        fulldiff = request.GET.get('fulldiff')
         c.anchor_url = anchor_url
         c.ignorews_url = _ignorews_url
         c.context_url = _context_url
         c.changes = OrderedDict()
         c.changes[diff2] = []
 
-        #special case if we want a show rev only, it's impl here
-        #to reduce JS and callbacks
+        # special case if we want a show rev only, it's impl here
+        # to reduce JS and callbacks
 
         if request.GET.get('show_rev'):
             if str2bool(request.GET.get('annotate', 'False')):
@@ -620,7 +614,7 @@
                 _url = url('files_home', repo_name=c.repo_name,
                            revision=diff1, f_path=c.f_path)
 
-            return redirect(_url)
+            raise HTTPFound(location=_url)
         try:
             if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
                 c.changeset_1 = c.db_repo_scm_instance.get_changeset(diff1)
@@ -655,54 +649,45 @@
                 node2 = FileNode(f_path, '', changeset=c.changeset_2)
         except (RepositoryError, NodeError):
             log.error(traceback.format_exc())
-            return redirect(url('files_home', repo_name=c.repo_name,
+            raise HTTPFound(location=url('files_home', repo_name=c.repo_name,
                                 f_path=f_path))
 
         if c.action == 'download':
-            _diff = diffs.get_gitdiff(node1, node2,
+            raw_diff = diffs.get_gitdiff(node1, node2,
                                       ignore_whitespace=ignore_whitespace,
                                       context=line_context)
-            diff = diffs.DiffProcessor(_diff, format='gitdiff')
-
             diff_name = '%s_vs_%s.diff' % (diff1, diff2)
             response.content_type = 'text/plain'
             response.content_disposition = (
                 'attachment; filename=%s' % diff_name
             )
-            return diff.as_raw()
+            return raw_diff
 
         elif c.action == 'raw':
-            _diff = diffs.get_gitdiff(node1, node2,
+            raw_diff = diffs.get_gitdiff(node1, node2,
                                       ignore_whitespace=ignore_whitespace,
                                       context=line_context)
-            diff = diffs.DiffProcessor(_diff, format='gitdiff')
             response.content_type = 'text/plain'
-            return diff.as_raw()
+            return raw_diff
 
         else:
             fid = h.FID(diff2, node2.path)
             line_context_lcl = get_line_ctx(fid, request.GET)
             ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
-            lim = request.GET.get('fulldiff') or self.cut_off_limit
-            _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
+            diff_limit = None if fulldiff else self.cut_off_limit
+            c.a_rev, c.cs_rev, a_path, diff, st, op = diffs.wrapped_diff(filenode_old=node1,
                                          filenode_new=node2,
-                                         cut_off_limit=lim,
+                                         diff_limit=diff_limit,
                                          ignore_whitespace=ign_whitespace_lcl,
                                          line_context=line_context_lcl,
                                          enable_comments=False)
-            op = ''
-            filename = node1.path
-            cs_changes = {
-                'fid': [cs1, cs2, op, filename, diff, st]
-            }
-            c.changes = cs_changes
+            c.file_diff_data = [(fid, fid, op, a_path, node2.path, diff, st)]
 
-        return render('files/file_diff.html')
+            return render('files/file_diff.html')
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def diff_2way(self, repo_name, f_path):
         diff1 = request.GET.get('diff1', '')
         diff2 = request.GET.get('diff2', '')
@@ -764,7 +749,7 @@
             try:
                 changesets = tip_cs.get_file_history(f_path)
             except (NodeDoesNotExistError, ChangesetError):
-                #this node is not present at tip !
+                # this node is not present at tip !
                 changesets = cs.get_file_history(f_path)
         hist_l = []
 
@@ -772,7 +757,7 @@
         branches_group = ([], _("Branches"))
         tags_group = ([], _("Tags"))
         for chs in changesets:
-            #_branch = '(%s)' % chs.branch if (cs.repository.alias == 'hg') else ''
+            # TODO: loop over chs.branches ... but that will not give all the bogus None branches for Git ...
             _branch = chs.branch
             n_desc = '%s (%s)' % (h.show_id(chs), _branch)
             changesets_group[0].append((chs.raw_id, n_desc,))
@@ -788,9 +773,8 @@
 
         return hist_l, changesets
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def nodelist(self, repo_name, revision, f_path):
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
--- a/kallithea/controllers/followers.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/followers.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,29 +27,25 @@
 
 import logging
 
-from pylons import tmpl_context as c, request
+from tg import tmpl_context as c, request
 
-from kallithea.lib.helpers import Page
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
 from kallithea.lib.base import BaseRepoController, render
+from kallithea.lib.page import Page
+from kallithea.lib.utils2 import safe_int
 from kallithea.model.db import UserFollowing
-from kallithea.lib.utils2 import safe_int
 
 log = logging.getLogger(__name__)
 
 
 class FollowersController(BaseRepoController):
 
-    def __before__(self):
-        super(FollowersController, self).__before__()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def followers(self, repo_name):
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
         repo_id = c.db_repo.repo_id
-        d = UserFollowing.get_repo_followers(repo_id)\
+        d = UserFollowing.get_repo_followers(repo_id) \
             .order_by(UserFollowing.follows_from)
         c.followers_pager = Page(d, page=p, items_per_page=20)
 
--- a/kallithea/controllers/forks.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/forks.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,58 +30,55 @@
 import traceback
 from formencode import htmlfill
 
-from pylons import tmpl_context as c, request, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import tmpl_context as c, request
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound
 
 import kallithea.lib.helpers as h
 
-from kallithea.lib.helpers import Page
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
-    NotAnonymous, HasRepoPermissionAny, HasPermissionAnyDecorator, HasPermissionAny
+from kallithea.config.routing import url
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
+    HasRepoPermissionLevel, HasPermissionAnyDecorator, HasPermissionAny
 from kallithea.lib.base import BaseRepoController, render
+from kallithea.lib.page import Page
+from kallithea.lib.utils2 import safe_int
 from kallithea.model.db import Repository, UserFollowing, User, Ui
 from kallithea.model.repo import RepoModel
 from kallithea.model.forms import RepoForkForm
 from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices
-from kallithea.lib.utils2 import safe_int
 
 log = logging.getLogger(__name__)
 
 
 class ForksController(BaseRepoController):
 
-    def __before__(self):
-        super(ForksController, self).__before__()
-
     def __load_defaults(self):
-        repo_group_perms = ['group.admin']
         if HasPermissionAny('hg.create.write_on_repogroup.true')():
-            repo_group_perms.append('group.write')
-        c.repo_groups = AvailableRepoGroupChoices(['hg.create.repository'], repo_group_perms)
+            repo_group_perm_level = 'write'
+        else:
+            repo_group_perm_level = 'admin'
+        c.repo_groups = AvailableRepoGroupChoices(['hg.create.repository'], repo_group_perm_level)
 
         c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs()
 
-        c.can_update = Ui.get_by_key(Ui.HOOK_UPDATE).ui_active
+        c.can_update = Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active
 
-    def __load_data(self, repo_name=None):
+    def __load_data(self):
         """
         Load defaults settings for edit, and update
-
-        :param repo_name:
         """
         self.__load_defaults()
 
-        c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
-        repo = db_repo.scm_instance
+        c.repo_info = c.db_repo
+        repo = c.db_repo.scm_instance
 
         if c.repo_info is None:
-            h.not_mapped_error(repo_name)
-            return redirect(url('repos'))
+            h.not_mapped_error(c.repo_name)
+            raise HTTPFound(location=url('repos'))
 
         c.default_user_id = User.get_default_user().user_id
-        c.in_public_journal = UserFollowing.query()\
-            .filter(UserFollowing.user_id == c.default_user_id)\
+        c.in_public_journal = UserFollowing.query() \
+            .filter(UserFollowing.user_id == c.default_user_id) \
             .filter(UserFollowing.follows_repository == c.repo_info).scalar()
 
         if c.repo_info.stats:
@@ -98,7 +95,7 @@
             c.stats_percentage = '%.2f' % ((float((last_rev)) /
                                             c.repo_last_rev) * 100)
 
-        defaults = RepoModel()._get_defaults(repo_name)
+        defaults = RepoModel()._get_defaults(c.repo_name)
         # alter the description to indicate a fork
         defaults['description'] = ('fork of repository: %s \n%s'
                                    % (defaults['repo_name'],
@@ -108,17 +105,14 @@
 
         return defaults
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def forks(self, repo_name):
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
         repo_id = c.db_repo.repo_id
         d = []
         for r in Repository.get_repo_forks(repo_id):
-            if not HasRepoPermissionAny(
-                'repository.read', 'repository.write', 'repository.admin'
-            )(r.repo_name, 'get forks check'):
+            if not HasRepoPermissionLevel('read')(r.repo_name, 'get forks check'):
                 continue
             d.append(r)
         c.forks_pager = Page(d, page=p, items_per_page=20)
@@ -129,17 +123,15 @@
         return render('/forks/forks.html')
 
     @LoginRequired()
-    @NotAnonymous()
     @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     def fork(self, repo_name):
         c.repo_info = Repository.get_by_repo_name(repo_name)
         if not c.repo_info:
             h.not_mapped_error(repo_name)
-            return redirect(url('home'))
+            raise HTTPFound(location=url('home'))
 
-        defaults = self.__load_data(repo_name)
+        defaults = self.__load_data()
 
         return htmlfill.render(
             render('forks/fork.html'),
@@ -148,10 +140,8 @@
             force_defaults=False)
 
     @LoginRequired()
-    @NotAnonymous()
     @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     def fork_create(self, repo_name):
         self.__load_defaults()
         c.repo_info = Repository.get_by_repo_name(repo_name)
@@ -164,15 +154,13 @@
             form_result = _form.to_python(dict(request.POST))
 
             # an approximation that is better than nothing
-            if not Ui.get_by_key(Ui.HOOK_UPDATE).ui_active:
+            if not Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active:
                 form_result['update_after_clone'] = False
 
             # create fork is done sometimes async on celery, db transaction
             # management is handled there.
-            task = RepoModel().create_fork(form_result, self.authuser.user_id)
-            from celery.result import BaseAsyncResult
-            if isinstance(task, BaseAsyncResult):
-                task_id = task.task_id
+            task = RepoModel().create_fork(form_result, request.authuser.user_id)
+            task_id = task.task_id
         except formencode.Invalid as errors:
             return htmlfill.render(
                 render('forks/fork.html'),
@@ -186,6 +174,6 @@
             h.flash(_('An error occurred during repository forking %s') %
                     repo_name, category='error')
 
-        return redirect(h.url('repo_creating_home',
+        raise HTTPFound(location=h.url('repo_creating_home',
                               repo_name=form_result['repo_name_full'],
                               task_id=task_id))
--- a/kallithea/controllers/home.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/home.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,73 +28,76 @@
 
 import logging
 
-from pylons import tmpl_context as c, request
-from pylons.i18n.translation import _
+from tg import tmpl_context as c, request
+from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest
 from sqlalchemy.sql.expression import func
+from sqlalchemy import or_, and_
 
-from kallithea.lib.utils import jsonify, conditional_cache
-from kallithea.lib.compat import json
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseController, render
-from kallithea.model.db import Repository, RepoGroup
+from kallithea.lib.utils import conditional_cache
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
+from kallithea.lib.base import BaseController, render, jsonify
+from kallithea.lib import helpers as h
+from kallithea.model.db import Repository, RepoGroup, User, UserGroup
 from kallithea.model.repo import RepoModel
-
+from kallithea.model.scm import UserGroupList
 
 log = logging.getLogger(__name__)
 
 
 class HomeController(BaseController):
 
-    def __before__(self):
-        super(HomeController, self).__before__()
-
     def about(self):
         return render('/about.html')
 
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     def index(self):
-        c.groups = self.scm_model.get_repo_groups()
         c.group = None
 
-        c.repos_list = Repository.query()\
-                        .filter(Repository.group_id == None)\
-                        .order_by(func.lower(Repository.repo_name))\
-                        .all()
+        repo_groups_list = self.scm_model.get_repo_groups()
+        repos_list = Repository.query(sorted=True).filter_by(group=None).all()
 
-        repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
-                                                   admin=False)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
+        c.data = RepoModel().get_repos_as_dict(repos_list,
+                                               repo_groups_list=repo_groups_list,
+                                               short_name=True)
 
         return render('/index.html')
 
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     @jsonify
     def repo_switcher_data(self):
-        #wrapper for conditional cache
+        # wrapper for conditional cache
         def _c():
             log.debug('generating switcher repo/groups list')
-            all_repos = Repository.query().order_by(Repository.repo_name).all()
-            repo_iter = self.scm_model.get_repos(all_repos, simple=True)
-            all_groups = RepoGroup.query().order_by(RepoGroup.group_name).all()
+            all_repos = Repository.query(sorted=True).all()
+            repo_iter = self.scm_model.get_repos(all_repos)
+            all_groups = RepoGroup.query(sorted=True).all()
             repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
 
             res = [{
                     'text': _('Groups'),
                     'children': [
-                       {'id': obj.group_name, 'text': obj.group_name,
-                        'type': 'group', 'obj': {}} for obj in repo_groups_iter]
-                   }, {
+                       {'id': obj.group_name,
+                        'text': obj.group_name,
+                        'type': 'group',
+                        'obj': {}}
+                       for obj in repo_groups_iter
+                    ],
+                   },
+                   {
                     'text': _('Repositories'),
                     'children': [
-                       {'id': obj['name'], 'text': obj['name'],
-                        'type': 'repo', 'obj': obj['dbrepo']} for obj in repo_iter]
+                       {'id': obj.repo_name,
+                        'text': obj.repo_name,
+                        'type': 'repo',
+                        'obj': obj.get_dict()}
+                       for obj in repo_iter
+                    ],
                    }]
 
             data = {
                 'more': False,
-                'results': res
+                'results': res,
             }
             return data
 
@@ -106,20 +109,8 @@
         else:
             raise HTTPBadRequest()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def branch_tag_switcher(self, repo_name):
-        if request.is_xhr:
-            c.db_repo = Repository.get_by_repo_name(repo_name)
-            if c.db_repo:
-                c.db_repo_scm_instance = c.db_repo.scm_instance
-                return render('/switch_to_list.html')
-        raise HTTPBadRequest()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def repo_refs_data(self, repo_name):
         repo = Repository.get_by_repo_name(repo_name).scm_instance
@@ -130,6 +121,12 @@
                 'text': _('Branch'),
                 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
             })
+        _closed_branches = repo.closed_branches.items()
+        if _closed_branches:
+            res.append({
+                'text': _('Closed Branches'),
+                'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches]
+            })
         _tags = repo.tags.items()
         if _tags:
             res.append({
@@ -147,3 +144,69 @@
             'results': res
         }
         return data
+
+    @LoginRequired()
+    @jsonify
+    def users_and_groups_data(self):
+        """
+        Returns 'results' with a list of users and user groups.
+
+        You can either use the 'key' GET parameter to get a user by providing
+        the exact user key or you can use the 'query' parameter to
+        search for users by user key, first name and last name.
+        'types' defaults to just 'users' but can be set to 'users,groups' to
+        get both users and groups.
+        No more than 500 results (of each kind) will be returned.
+        """
+        types = request.GET.get('types', 'users').split(',')
+        key = request.GET.get('key', '')
+        query = request.GET.get('query', '')
+        results = []
+        if 'users' in types:
+            user_list = []
+            if key:
+                u = User.get_by_username(key)
+                if u:
+                    user_list = [u]
+            elif query:
+                user_list = User.query() \
+                    .filter(User.is_default_user == False) \
+                    .filter(User.active == True) \
+                    .filter(or_(
+                        User.username.ilike("%%"+query+"%%"),
+                        User.name.ilike("%%"+query+"%%"),
+                        User.lastname.ilike("%%"+query+"%%"),
+                    )) \
+                    .order_by(User.username) \
+                    .limit(500) \
+                    .all()
+            for u in user_list:
+                results.append({
+                    'type': 'user',
+                    'id': u.user_id,
+                    'nname': u.username,
+                    'fname': u.name,
+                    'lname': u.lastname,
+                    'gravatar_lnk': h.gravatar_url(u.email, size=28, default='default'),
+                    'gravatar_size': 14,
+                })
+        if 'groups' in types:
+            grp_list = []
+            if key:
+                grp = UserGroup.get_by_group_name(key)
+                if grp:
+                    grp_list = [grp]
+            elif query:
+                grp_list = UserGroup.query() \
+                    .filter(UserGroup.users_group_name.ilike("%%"+query+"%%")) \
+                    .filter(UserGroup.users_group_active == True) \
+                    .order_by(UserGroup.users_group_name) \
+                    .limit(500) \
+                    .all()
+            for g in UserGroupList(grp_list, perm_level='read'):
+                results.append({
+                    'type': 'group',
+                    'id': g.users_group_id,
+                    'grname': g.users_group_name,
+                })
+        return dict(results=results)
--- a/kallithea/controllers/journal.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/journal.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,7 @@
 kallithea.controllers.journal
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Journal controller for pylons
+Journal controller
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -37,37 +37,39 @@
 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
 from webob.exc import HTTPBadRequest
-from pylons import request, tmpl_context as c, response, url
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c, response
+from tg.i18n import ugettext as _
 
+from kallithea.config.routing import url
 from kallithea.controllers.admin.admin import _journal_filter
 from kallithea.model.db import UserLog, UserFollowing, Repository, User
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 import kallithea.lib.helpers as h
-from kallithea.lib.helpers import Page
-from kallithea.lib.auth import LoginRequired, NotAnonymous
+from kallithea.lib.auth import LoginRequired
 from kallithea.lib.base import BaseController, render
+from kallithea.lib.page import Page
 from kallithea.lib.utils2 import safe_int, AttributeDict
-from kallithea.lib.compat import json
 
 log = logging.getLogger(__name__)
 
 
+language = 'en-us'
+ttl = "5"
+feed_nr = 20
+
+
 class JournalController(BaseController):
 
-    def __before__(self):
-        super(JournalController, self).__before__()
-        self.language = 'en-us'
-        self.ttl = "5"
-        self.feed_nr = 20
+    def _before(self, *args, **kwargs):
+        super(JournalController, self)._before(*args, **kwargs)
         c.search_term = request.GET.get('filter')
 
     def _get_daily_aggregate(self, journal):
         groups = []
         for k, g in groupby(journal, lambda x: x.action_as_day):
             user_group = []
-            #groupby username if it's a present value, else fallback to journal username
+            # groupby username if it's a present value, else fallback to journal username
             for _unused, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
                 l = list(g2)
                 user_group.append((l[0].user, l))
@@ -77,10 +79,10 @@
         return groups
 
     def _get_journal_data(self, following_repos):
-        repo_ids = [x.follows_repository.repo_id for x in following_repos
-                    if x.follows_repository is not None]
-        user_ids = [x.follows_user.user_id for x in following_repos
-                    if x.follows_user is not None]
+        repo_ids = [x.follows_repository_id for x in following_repos
+                    if x.follows_repository_id is not None]
+        user_ids = [x.follows_user_id for x in following_repos
+                    if x.follows_user_id is not None]
 
         filtering_criterion = None
 
@@ -92,12 +94,12 @@
         if not repo_ids and user_ids:
             filtering_criterion = UserLog.user_id.in_(user_ids)
         if filtering_criterion is not None:
-            journal = self.sa.query(UserLog)\
-                .options(joinedload(UserLog.user))\
+            journal = UserLog.query() \
+                .options(joinedload(UserLog.user)) \
                 .options(joinedload(UserLog.repository))
-            #filter
+            # filter
             journal = _journal_filter(journal, c.search_term)
-            journal = journal.filter(filtering_criterion)\
+            journal = journal.filter(filtering_criterion) \
                         .order_by(UserLog.action_date.desc())
         else:
             journal = []
@@ -117,13 +119,13 @@
         feed = Atom1Feed(title=_desc,
                          link=_link,
                          description=_desc,
-                         language=self.language,
-                         ttl=self.ttl)
+                         language=language,
+                         ttl=ttl)
 
-        for entry in journal[:self.feed_nr]:
+        for entry in journal[:feed_nr]:
             user = entry.user
             if user is None:
-                #fix deleted users
+                # fix deleted users
                 user = AttributeDict({'short_contact': entry.username,
                                       'email': '',
                                       'full_contact': ''})
@@ -159,13 +161,13 @@
         feed = Rss201rev2Feed(title=_desc,
                          link=_link,
                          description=_desc,
-                         language=self.language,
-                         ttl=self.ttl)
+                         language=language,
+                         ttl=ttl)
 
-        for entry in journal[:self.feed_nr]:
+        for entry in journal[:feed_nr]:
             user = entry.user
             if user is None:
-                #fix deleted users
+                # fix deleted users
                 user = AttributeDict({'short_contact': entry.username,
                                       'email': '',
                                       'full_contact': ''})
@@ -189,14 +191,13 @@
         return feed.writeString('utf-8')
 
     @LoginRequired()
-    @NotAnonymous()
     def index(self):
         # Return a rendered template
-        p = safe_int(request.GET.get('page', 1), 1)
-        c.user = User.get(self.authuser.user_id)
-        c.following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        p = safe_int(request.GET.get('page'), 1)
+        c.user = User.get(request.authuser.user_id)
+        c.following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
 
         journal = self._get_journal_data(c.following)
@@ -205,119 +206,61 @@
             return url.current(filter=c.search_term, **kw)
 
         c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
-        c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
+        c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager)
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             return render('journal/journal_data.html')
 
-        repos_list = Session().query(Repository)\
-                     .filter(Repository.user_id ==
-                             self.authuser.user_id)\
-                     .order_by(func.lower(Repository.repo_name)).all()
-
-        repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
-                                                   admin=True)
-        #json used to render the grid
-        c.data = json.dumps(repos_data)
-
-        watched_repos_data = []
-
-        ## watched repos
-        _render = RepoModel._render_datatable
-
-        def quick_menu(repo_name):
-            return _render('quick_menu', repo_name)
-
-        def repo_lnk(name, rtype, rstate, private, fork_of):
-            return _render('repo_name', name, rtype, rstate, private, fork_of,
-                           short_name=False, admin=False)
-
-        def last_rev(repo_name, cs_cache):
-            return _render('revision', repo_name, cs_cache.get('revision'),
-                           cs_cache.get('raw_id'), cs_cache.get('author'),
-                           cs_cache.get('message'))
-
-        def desc(desc):
-            from pylons import tmpl_context as c
-            return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
+        repos_list = Repository.query(sorted=True) \
+            .filter_by(owner_id=request.authuser.user_id).all()
 
-        def repo_actions(repo_name):
-            return _render('repo_actions', repo_name)
-
-        def owner_actions(user_id, username):
-            return _render('user_name', user_id, username)
-
-        def toogle_follow(repo_id):
-            return  _render('toggle_follow', repo_id)
+        repos_data = RepoModel().get_repos_as_dict(repos_list, admin=True)
+        # data used to render the grid
+        c.data = repos_data
 
-        for entry in c.following:
-            repo = entry.follows_repository
-            cs_cache = repo.changeset_cache
-            row = {
-                "menu": quick_menu(repo.repo_name),
-                "raw_name": repo.repo_name.lower(),
-                "name": repo_lnk(repo.repo_name, repo.repo_type,
-                                 repo.repo_state, repo.private, repo.fork),
-                "last_changeset": last_rev(repo.repo_name, cs_cache),
-                "last_rev_raw": cs_cache.get('revision'),
-                "action": toogle_follow(repo.repo_id)
-            }
-
-            watched_repos_data.append(row)
-
-        c.watched_data = json.dumps({
-            "totalRecords": len(c.following),
-            "startIndex": 0,
-            "sort": "name",
-            "dir": "asc",
-            "records": watched_repos_data
-        })
         return render('journal/journal.html')
 
     @LoginRequired(api_access=True)
-    @NotAnonymous()
     def journal_atom(self):
         """
         Produce an atom-1.0 feed via feedgenerator module
         """
-        following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
         return self._atom_feed(following, public=False)
 
     @LoginRequired(api_access=True)
-    @NotAnonymous()
     def journal_rss(self):
         """
         Produce an rss feed via feedgenerator module
         """
-        following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
         return self._rss_feed(following, public=False)
 
     @LoginRequired()
-    @NotAnonymous()
     def toggle_following(self):
         user_id = request.POST.get('follows_user_id')
         if user_id:
             try:
                 self.scm_model.toggle_following_user(user_id,
-                                            self.authuser.user_id)
-                Session.commit()
+                                            request.authuser.user_id)
+                Session().commit()
                 return 'ok'
             except Exception:
                 log.error(traceback.format_exc())
                 raise HTTPBadRequest()
 
-        repo_id = request.POST.get('follows_repo_id')
+        repo_id = request.POST.get('follows_repository_id')
         if repo_id:
             try:
                 self.scm_model.toggle_following_repo(repo_id,
-                                            self.authuser.user_id)
-                Session.commit()
+                                            request.authuser.user_id)
+                Session().commit()
                 return 'ok'
             except Exception:
                 log.error(traceback.format_exc())
@@ -325,47 +268,47 @@
 
         raise HTTPBadRequest()
 
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     def public_journal(self):
         # Return a rendered template
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
 
-        c.following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        c.following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
 
         journal = self._get_journal_data(c.following)
 
         c.journal_pager = Page(journal, page=p, items_per_page=20)
 
-        c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
+        c.journal_day_aggregate = self._get_daily_aggregate(c.journal_pager)
 
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             return render('journal/journal_data.html')
 
         return render('journal/public_journal.html')
 
-    @LoginRequired(api_access=True)
+    @LoginRequired(api_access=True, allow_default_user=True)
     def public_journal_atom(self):
         """
         Produce an atom-1.0 feed via feedgenerator module
         """
-        c.following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        c.following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
 
         return self._atom_feed(c.following)
 
-    @LoginRequired(api_access=True)
+    @LoginRequired(api_access=True, allow_default_user=True)
     def public_journal_rss(self):
         """
         Produce an rss2 feed via feedgenerator module
         """
-        c.following = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.user_id == self.authuser.user_id)\
-            .options(joinedload(UserFollowing.follows_repository))\
+        c.following = UserFollowing.query() \
+            .filter(UserFollowing.user_id == request.authuser.user_id) \
+            .options(joinedload(UserFollowing.follows_repository)) \
             .all()
 
         return self._rss_feed(c.following)
--- a/kallithea/controllers/login.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/login.py	Sun Mar 31 21:28:56 2019 +0200
@@ -31,12 +31,12 @@
 import formencode
 
 from formencode import htmlfill
+from tg.i18n import ugettext as _
+from tg import request, session, tmpl_context as c
 from webob.exc import HTTPFound, HTTPBadRequest
-from pylons.i18n.translation import _
-from pylons.controllers.util import redirect
-from pylons import request, session, tmpl_context as c, url
 
 import kallithea.lib.helpers as h
+from kallithea.config.routing import url
 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
 from kallithea.lib.base import BaseController, log_in_user, render
 from kallithea.lib.exceptions import UserCreationError
@@ -53,9 +53,6 @@
 
 class LoginController(BaseController):
 
-    def __before__(self):
-        super(LoginController, self).__before__()
-
     def _validate_came_from(self, came_from,
             _re=re.compile(r"/(?!/)[-!#$%&'()*+,./:;=?@_~0-9A-Za-z]*$")):
         """Return True if came_from is valid and can and should be used.
@@ -79,25 +76,18 @@
         else:
             c.came_from = url('home')
 
-        not_default = self.authuser.username != User.DEFAULT_USER
-        ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr)
-
-        # redirect if already logged in
-        if self.authuser.is_authenticated and not_default and ip_allowed:
-            raise HTTPFound(location=c.came_from)
-
         if request.POST:
             # import Login Form validator class
-            login_form = LoginForm()
+            login_form = LoginForm()()
             try:
                 c.form_result = login_form.to_python(dict(request.POST))
                 # form checks for username/password, now we're authenticated
                 username = c.form_result['username']
-                user = User.get_by_username(username, case_insensitive=True)
+                user = User.get_by_username_or_email(username, case_insensitive=True)
             except formencode.Invalid as errors:
                 defaults = errors.value
                 # remove password from filling in form again
-                del defaults['password']
+                defaults.pop('password', None)
                 return htmlfill.render(
                     render('/login.html'),
                     defaults=errors.value,
@@ -115,14 +105,18 @@
                 log_in_user(user, c.form_result['remember'],
                     is_external_auth=False)
                 raise HTTPFound(location=c.came_from)
+        else:
+            # redirect if already logged in
+            if request.authuser.is_authenticated:
+                raise HTTPFound(location=c.came_from)
 
         return render('/login.html')
 
     @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
                                'hg.register.manual_activate')
     def register(self):
-        c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
-            .AuthUser.permissions['global']
+        def_user_perms = AuthUser(dbuser=User.get_default_user()).permissions['global']
+        c.auto_active = 'hg.register.auto_activate' in def_user_perms
 
         settings = Setting.get_app_settings()
         captcha_private_key = settings.get('captcha_private_key')
@@ -137,11 +131,10 @@
 
                 if c.captcha_active:
                     from kallithea.lib.recaptcha import submit
-                    response = submit(request.POST.get('recaptcha_challenge_field'),
-                                      request.POST.get('recaptcha_response_field'),
+                    response = submit(request.POST.get('g-recaptcha-response'),
                                       private_key=captcha_private_key,
-                                      remoteip=self.ip_addr)
-                    if c.captcha_active and not response.is_valid:
+                                      remoteip=request.ip_addr)
+                    if not response.is_valid:
                         _value = form_result
                         _msg = _('Bad captcha')
                         error_dict = {'recaptcha_field': _msg}
@@ -149,10 +142,10 @@
                                                  error_dict=error_dict)
 
                 UserModel().create_registration(form_result)
-                h.flash(_('You have successfully registered into Kallithea'),
+                h.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'),
                         category='success')
                 Session().commit()
-                return redirect(url('login_home'))
+                raise HTTPFound(location=url('login_home'))
 
             except formencode.Invalid as errors:
                 return htmlfill.render(
@@ -183,11 +176,10 @@
                 form_result = password_reset_form.to_python(dict(request.POST))
                 if c.captcha_active:
                     from kallithea.lib.recaptcha import submit
-                    response = submit(request.POST.get('recaptcha_challenge_field'),
-                                      request.POST.get('recaptcha_response_field'),
+                    response = submit(request.POST.get('g-recaptcha-response'),
                                       private_key=captcha_private_key,
-                                      remoteip=self.ip_addr)
-                    if c.captcha_active and not response.is_valid:
+                                      remoteip=request.ip_addr)
+                    if not response.is_valid:
                         _value = form_result
                         _msg = _('Bad captcha')
                         error_dict = {'recaptcha_field': _msg}
@@ -196,7 +188,7 @@
                 redirect_link = UserModel().send_reset_password_email(form_result)
                 h.flash(_('A password reset confirmation code has been sent'),
                             category='success')
-                return redirect(redirect_link)
+                raise HTTPFound(location=redirect_link)
 
             except formencode.Invalid as errors:
                 return htmlfill.render(
@@ -249,12 +241,12 @@
 
         UserModel().reset_password(form_result['email'], form_result['password'])
         h.flash(_('Successfully updated password'), category='success')
-        return redirect(url('login_home'))
+        raise HTTPFound(location=url('login_home'))
 
     def logout(self):
         session.delete()
         log.info('Logging out and deleting session for user')
-        redirect(url('home'))
+        raise HTTPFound(location=url('home'))
 
     def authentication_token(self):
         """Return the CSRF protection token for the session - just like it
--- a/kallithea/controllers/pullrequests.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/pullrequests.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,43 +28,52 @@
 import logging
 import traceback
 import formencode
-import re
-
-from webob.exc import HTTPNotFound, HTTPForbidden, HTTPBadRequest
 
-from pylons import request, tmpl_context as c, url
-from pylons.controllers.util import redirect
-from pylons.i18n.translation import _
+from tg import request, tmpl_context as c
+from tg.i18n import ugettext as _
+from webob.exc import HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest
 
-from kallithea.lib.vcs.utils.hgcompat import unionrepo
-from kallithea.lib.compat import json
-from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
-    NotAnonymous
-from kallithea.lib.helpers import Page
+from kallithea.config.routing import url
 from kallithea.lib import helpers as h
 from kallithea.lib import diffs
-from kallithea.lib.exceptions import UserInvalidException
-from kallithea.lib.utils import action_logger, jsonify
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
+from kallithea.lib.base import BaseRepoController, render, jsonify
+from kallithea.lib.page import Page
+from kallithea.lib.utils import action_logger
+from kallithea.lib.vcs.exceptions import EmptyRepositoryError, ChangesetDoesNotExistError
 from kallithea.lib.vcs.utils import safe_str
-from kallithea.lib.vcs.exceptions import EmptyRepositoryError
-from kallithea.lib.diffs import LimitedDiffContainer
-from kallithea.model.db import PullRequest, ChangesetStatus, ChangesetComment,\
-    PullRequestReviewers, User
-from kallithea.model.pull_request import PullRequestModel
+from kallithea.lib.vcs.utils.hgcompat import unionrepo
+from kallithea.model.db import PullRequest, ChangesetStatus, \
+    PullRequestReviewer, Repository, User
+from kallithea.model.pull_request import CreatePullRequestAction, CreatePullRequestIterationAction, PullRequestModel
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.comment import ChangesetCommentsModel
 from kallithea.model.changeset_status import ChangesetStatusModel
 from kallithea.model.forms import PullRequestForm, PullRequestPostForm
 from kallithea.lib.utils2 import safe_int
-from kallithea.controllers.changeset import _ignorews_url, _context_url
+from kallithea.controllers.changeset import _ignorews_url, _context_url, \
+    create_cs_pr_comment, delete_cs_pr_comment
 from kallithea.controllers.compare import CompareController
 from kallithea.lib.graphmod import graph_data
 
 log = logging.getLogger(__name__)
 
 
+def _get_reviewer(user_id):
+    """Look up user by ID and validate it as a potential reviewer."""
+    try:
+        user = User.get(int(user_id))
+    except ValueError:
+        user = None
+
+    if user is None or user.is_default_user:
+        h.flash(_('Invalid reviewer "%s" specified') % user_id, category='error')
+        raise HTTPBadRequest()
+
+    return user
+
+
 class PullrequestsController(BaseRepoController):
 
     def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
@@ -93,11 +102,11 @@
             for i in repo._repo.revs(
                 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
                 branch_rev, branch_rev):
-                abranch = repo.get_changeset(i).branch
-                if abranch not in peerbranches:
-                    n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
-                    peers.append((n, abranch))
-                    peerbranches.add(abranch)
+                for abranch in repo.get_changeset(i).branches:
+                    if abranch not in peerbranches:
+                        n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
+                        peers.append((n, abranch))
+                        peerbranches.add(abranch)
 
         selected = None
         tiprev = repo.tags.get('tip')
@@ -140,8 +149,7 @@
                 continue
             n = 'tag:%s:%s' % (tag, tagrev)
             tags.append((n, tag))
-            if rev == tagrev:
-                selected = n
+            # note: even if rev == tagrev, don't select the static tag - it must be chosen explicitly
 
         # prio 1: rev was selected as existing entry above
 
@@ -178,65 +186,72 @@
                   ]
         return [g for g in groups if g[0]], selected
 
-    def _get_is_allowed_change_status(self, pull_request):
+    def _is_allowed_to_change_status(self, pull_request):
         if pull_request.is_closed():
             return False
 
-        owner = self.authuser.user_id == pull_request.user_id
-        reviewer = self.authuser.user_id in [x.user_id for x in
-                                                   pull_request.reviewers]
-        return self.authuser.admin or owner or reviewer
+        owner = request.authuser.user_id == pull_request.owner_id
+        reviewer = PullRequestReviewer.query() \
+            .filter(PullRequestReviewer.pull_request == pull_request) \
+            .filter(PullRequestReviewer.user_id == request.authuser.user_id) \
+            .count() != 0
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+        return request.authuser.admin or owner or reviewer
+
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def show_all(self, repo_name):
         c.from_ = request.GET.get('from_') or ''
         c.closed = request.GET.get('closed') or ''
-        c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed)
-        c.repo_name = repo_name
-        p = safe_int(request.GET.get('page', 1), 1)
+        p = safe_int(request.GET.get('page'), 1)
+
+        q = PullRequest.query(include_closed=c.closed, sorted=True)
+        if c.from_:
+            q = q.filter_by(org_repo=c.db_repo)
+        else:
+            q = q.filter_by(other_repo=c.db_repo)
+        c.pull_requests = q.all()
 
         c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=100)
 
         return render('/pullrequests/pullrequest_show_all.html')
 
     @LoginRequired()
-    @NotAnonymous()
     def show_my(self):
         c.closed = request.GET.get('closed') or ''
 
-        def _filter(pr):
-            s = sorted(pr, key=lambda o: o.created_on, reverse=True)
-            if not c.closed:
-                s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
-            return s
+        c.my_pull_requests = PullRequest.query(
+            include_closed=c.closed,
+            sorted=True,
+        ).filter_by(owner_id=request.authuser.user_id).all()
 
-        c.my_pull_requests = _filter(PullRequest.query()\
-                                .filter(PullRequest.user_id ==
-                                        self.authuser.user_id)\
-                                .all())
-
-        c.participate_in_pull_requests = _filter(PullRequest.query()\
-                                .join(PullRequestReviewers)\
-                                .filter(PullRequestReviewers.user_id ==
-                                        self.authuser.user_id)\
-                                                 )
+        c.participate_in_pull_requests = []
+        c.participate_in_pull_requests_todo = []
+        done_status = set([ChangesetStatus.STATUS_APPROVED, ChangesetStatus.STATUS_REJECTED])
+        for pr in PullRequest.query(
+            include_closed=c.closed,
+            reviewer_id=request.authuser.user_id,
+            sorted=True,
+        ):
+            status = pr.user_review_status(request.authuser.user_id) # very inefficient!!!
+            if status in done_status:
+                c.participate_in_pull_requests.append(pr)
+            else:
+                c.participate_in_pull_requests_todo.append(pr)
 
         return render('/pullrequests/pullrequest_show_my.html')
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     def index(self):
         org_repo = c.db_repo
         org_scm_instance = org_repo.scm_instance
         try:
             org_scm_instance.get_changeset()
         except EmptyRepositoryError as e:
-            h.flash(_('There are no changesets yet'), category='warning')
-            redirect(url('summary_home', repo_name=org_repo.repo_name))
+            h.flash(_('There are no changesets yet'),
+                    category='warning')
+            raise HTTPFound(location=url('summary_home', repo_name=org_repo.repo_name))
 
         org_rev = request.GET.get('rev_end')
         # rev_start is not directly useful - its parent could however be used
@@ -282,12 +297,10 @@
         return render('/pullrequests/pullrequest.html')
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def repo_info(self, repo_name):
-        repo = RepoModel()._get_repo(repo_name)
+        repo = c.db_repo
         refs, selected_ref = self._get_repo_refs(repo.scm_instance)
         return {
             'description': repo.description.split('\n', 1)[0],
@@ -296,11 +309,9 @@
             }
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     def create(self, repo_name):
-        repo = RepoModel()._get_repo(repo_name)
+        repo = c.db_repo
         try:
             _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
         except formencode.Invalid as errors:
@@ -311,234 +322,136 @@
             raise HTTPBadRequest
 
         # heads up: org and other might seem backward here ...
-        org_repo_name = _form['org_repo']
         org_ref = _form['org_ref'] # will have merge_rev as rev but symbolic name
-        org_repo = RepoModel()._get_repo(org_repo_name)
-        (org_ref_type,
-         org_ref_name,
-         org_rev) = org_ref.split(':')
-        if org_ref_type == 'rev':
-            org_ref_type = 'branch'
-            cs = org_repo.scm_instance.get_changeset(org_rev)
-            org_ref = '%s:%s:%s' % (org_ref_type, cs.branch, cs.raw_id)
+        org_repo = Repository.guess_instance(_form['org_repo'])
 
-        other_repo_name = _form['other_repo']
         other_ref = _form['other_ref'] # will have symbolic name and head revision
-        other_repo = RepoModel()._get_repo(other_repo_name)
-        (other_ref_type,
-         other_ref_name,
-         other_rev) = other_ref.split(':')
+        other_repo = Repository.guess_instance(_form['other_repo'])
 
-        cs_ranges, _cs_ranges_not, ancestor_rev = \
-            CompareController._get_changesets(org_repo.scm_instance.alias,
-                                              other_repo.scm_instance, other_rev, # org and other "swapped"
-                                              org_repo.scm_instance, org_rev,
-                                              )
-        if ancestor_rev is None:
-            ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET
-        revisions = [cs_.raw_id for cs_ in cs_ranges]
-
-        # hack: ancestor_rev is not an other_rev but we want to show the
-        # requested destination and have the exact ancestor
-        other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
-
-        reviewers = _form['review_members']
+        reviewers = []
 
         title = _form['pullrequest_title']
-        if not title:
-            if org_repo_name == other_repo_name:
-                title = '%s to %s' % (h.short_ref(org_ref_type, org_ref_name),
-                                      h.short_ref(other_ref_type, other_ref_name))
-            else:
-                title = '%s#%s to %s#%s' % (org_repo_name, h.short_ref(org_ref_type, org_ref_name),
-                                            other_repo_name, h.short_ref(other_ref_type, other_ref_name))
-        description = _form['pullrequest_desc'].strip() or _('No description')
+        description = _form['pullrequest_desc'].strip()
+        owner = User.get(request.authuser.user_id)
+
         try:
-            pull_request = PullRequestModel().create(
-                self.authuser.user_id, org_repo_name, org_ref, other_repo_name,
-                other_ref, revisions, reviewers, title, description
-            )
+            cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers)
+        except CreatePullRequestAction.ValidationError as e:
+            h.flash(str(e), category='error', logf=log.error)
+            raise HTTPNotFound
+
+        try:
+            pull_request = cmd.execute()
             Session().commit()
-            h.flash(_('Successfully opened new pull request'),
-                    category='success')
-        except UserInvalidException as u:
-            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
-            raise HTTPBadRequest()
         except Exception:
             h.flash(_('Error occurred while creating pull request'),
                     category='error')
             log.error(traceback.format_exc())
-            return redirect(url('pullrequest_home', repo_name=repo_name))
-
-        return redirect(pull_request.url())
-
-    def create_update(self, old_pull_request, updaterev, title, description, reviewers_ids):
-        org_repo = RepoModel()._get_repo(old_pull_request.org_repo.repo_name)
-        org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':')
-        new_org_rev = self._get_ref_rev(org_repo, 'rev', updaterev)
-
-        other_repo = RepoModel()._get_repo(old_pull_request.other_repo.repo_name)
-        other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor
-        #assert other_ref_type == 'branch', other_ref_type # TODO: what if not?
-        new_other_rev = self._get_ref_rev(other_repo, other_ref_type, other_ref_name)
+            raise HTTPFound(location=url('pullrequest_home', repo_name=repo_name))
 
-        cs_ranges, _cs_ranges_not, ancestor_rev = CompareController._get_changesets(org_repo.scm_instance.alias,
-            other_repo.scm_instance, new_other_rev, # org and other "swapped"
-            org_repo.scm_instance, new_org_rev)
-
-        old_revisions = set(old_pull_request.revisions)
-        revisions = [cs.raw_id for cs in cs_ranges]
-        new_revisions = [r for r in revisions if r not in old_revisions]
-        lost = old_revisions.difference(revisions)
-
-        infos = ['This is an update of %s "%s".' %
-                 (h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name,
-                      pull_request_id=old_pull_request.pull_request_id),
-                  old_pull_request.title)]
+        h.flash(_('Successfully opened new pull request'),
+                category='success')
+        raise HTTPFound(location=pull_request.url())
 
-        if lost:
-            infos.append(_('Missing changesets since the previous pull request:'))
-            for r in old_pull_request.revisions:
-                if r in lost:
-                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
-                    infos.append('  %s "%s"' % (h.short_id(r), rev_desc))
-
-        if new_revisions:
-            infos.append(_('New changesets on %s %s since the previous pull request:') % (org_ref_type, org_ref_name))
-            for r in reversed(revisions):
-                if r in new_revisions:
-                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
-                    infos.append('  %s %s' % (h.short_id(r), h.shorter(rev_desc, 80)))
-
-            if ancestor_rev == other_rev:
-                infos.append(_("Ancestor didn't change - show diff since previous version:"))
-                infos.append(h.canonical_url('compare_url',
-                                 repo_name=org_repo.repo_name, # other_repo is always same as repo_name
-                                 org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base
-                                 other_ref_type='rev', other_ref_name=h.short_id(new_org_rev),
-                                 )) # note: linear diff, merge or not doesn't matter
-            else:
-                infos.append(_('This pull request is based on another %s revision and there is no simple diff.') % other_ref_name)
-        else:
-           infos.append(_('No changes found on %s %s since previous version.') % (org_ref_type, org_ref_name))
-           # TODO: fail?
-
-        # hack: ancestor_rev is not an other_ref but we want to show the
-        # requested destination and have the exact ancestor
-        new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
-        new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)
+    def create_new_iteration(self, old_pull_request, new_rev, title, description, reviewers):
+        owner = User.get(request.authuser.user_id)
+        new_org_rev = self._get_ref_rev(old_pull_request.org_repo, 'rev', new_rev)
+        new_other_rev = self._get_ref_rev(old_pull_request.other_repo, old_pull_request.other_ref_parts[0], old_pull_request.other_ref_parts[1])
+        try:
+            cmd = CreatePullRequestIterationAction(old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers)
+        except CreatePullRequestAction.ValidationError as e:
+            h.flash(str(e), category='error', logf=log.error)
+            raise HTTPNotFound
 
         try:
-            title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
-            v = int(old_v) + 1
-        except (AttributeError, ValueError):
-            v = 2
-        title = '%s (v%s)' % (title.strip(), v)
-
-        # using a mail-like separator, insert new update info at the top of the list
-        descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1)
-        description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos)
-        if len(descriptions) > 1:
-            description += '\n\n' + descriptions[1].strip()
-
-        try:
-            pull_request = PullRequestModel().create(
-                self.authuser.user_id,
-                old_pull_request.org_repo.repo_name, new_org_ref,
-                old_pull_request.other_repo.repo_name, new_other_ref,
-                revisions, reviewers_ids, title, description
-            )
-        except UserInvalidException as u:
-            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
-            raise HTTPBadRequest()
+            pull_request = cmd.execute()
+            Session().commit()
         except Exception:
             h.flash(_('Error occurred while creating pull request'),
                     category='error')
             log.error(traceback.format_exc())
-            return redirect(old_pull_request.url())
+            raise HTTPFound(location=old_pull_request.url())
 
-        ChangesetCommentsModel().create(
-            text=_('Closed, replaced by %s .') % pull_request.url(canonical=True),
-            repo=old_pull_request.other_repo.repo_id,
-            user=c.authuser.user_id,
-            pull_request=old_pull_request.pull_request_id,
-            closing_pr=True)
-        PullRequestModel().close_pull_request(old_pull_request.pull_request_id)
-
-        Session().commit()
-        h.flash(_('Pull request update created'),
+        h.flash(_('New pull request iteration created'),
                 category='success')
-
-        return redirect(pull_request.url())
+        raise HTTPFound(location=pull_request.url())
 
     # pullrequest_post for PR editing
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     def post(self, repo_name, pull_request_id):
         pull_request = PullRequest.get_or_404(pull_request_id)
         if pull_request.is_closed():
             raise HTTPForbidden()
         assert pull_request.other_repo.repo_name == repo_name
-        #only owner or admin can update it
-        owner = pull_request.owner.user_id == c.authuser.user_id
-        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
+        # only owner or admin can update it
+        owner = pull_request.owner_id == request.authuser.user_id
+        repo_admin = h.HasRepoPermissionLevel('admin')(c.repo_name)
         if not (h.HasPermissionAny('hg.admin')() or repo_admin or owner):
             raise HTTPForbidden()
 
         _form = PullRequestPostForm()().to_python(request.POST)
-        reviewers_ids = [int(s) for s in _form['review_members']]
+
+        cur_reviewers = set(pull_request.get_reviewer_users())
+        new_reviewers = set(_get_reviewer(s) for s in _form['review_members'])
+        old_reviewers = set(_get_reviewer(s) for s in _form['org_review_members'])
+
+        other_added = cur_reviewers - old_reviewers
+        other_removed = old_reviewers - cur_reviewers
+
+        if other_added:
+            h.flash(_('Meanwhile, the following reviewers have been added: %s') %
+                    (', '.join(u.username for u in other_added)),
+                    category='warning')
+        if other_removed:
+            h.flash(_('Meanwhile, the following reviewers have been removed: %s') %
+                    (', '.join(u.username for u in other_removed)),
+                    category='warning')
 
         if _form['updaterev']:
-            return self.create_update(pull_request,
+            return self.create_new_iteration(pull_request,
                                       _form['updaterev'],
                                       _form['pullrequest_title'],
                                       _form['pullrequest_desc'],
-                                      reviewers_ids)
+                                      new_reviewers)
+
+        added_reviewers = new_reviewers - old_reviewers - cur_reviewers
+        removed_reviewers = (old_reviewers - new_reviewers) & cur_reviewers
 
         old_description = pull_request.description
         pull_request.title = _form['pullrequest_title']
         pull_request.description = _form['pullrequest_desc'].strip() or _('No description')
         pull_request.owner = User.get_by_username(_form['owner'])
-        user = User.get(c.authuser.user_id)
-        try:
-            PullRequestModel().mention_from_description(user, pull_request, old_description)
-            PullRequestModel().update_reviewers(user, pull_request_id, reviewers_ids)
-        except UserInvalidException as u:
-            h.flash(_('Invalid reviewer "%s" specified') % u, category='error')
-            raise HTTPBadRequest()
+        user = User.get(request.authuser.user_id)
+
+        PullRequestModel().mention_from_description(user, pull_request, old_description)
+        PullRequestModel().add_reviewers(user, pull_request, added_reviewers)
+        PullRequestModel().remove_reviewers(user, pull_request, removed_reviewers)
 
         Session().commit()
         h.flash(_('Pull request updated'), category='success')
 
-        return redirect(pull_request.url())
+        raise HTTPFound(location=pull_request.url())
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def delete(self, repo_name, pull_request_id):
         pull_request = PullRequest.get_or_404(pull_request_id)
-        #only owner can delete it !
-        if pull_request.owner.user_id == c.authuser.user_id:
+        # only owner can delete it !
+        if pull_request.owner_id == request.authuser.user_id:
             PullRequestModel().delete(pull_request)
             Session().commit()
             h.flash(_('Successfully deleted pull request'),
                     category='success')
-            return redirect(url('my_pullrequests'))
+            raise HTTPFound(location=url('my_pullrequests'))
         raise HTTPForbidden()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def show(self, repo_name, pull_request_id, extra=None):
-        repo_model = RepoModel()
-        c.users_array = repo_model.get_users_js()
-        c.user_groups_array = repo_model.get_user_groups_js()
         c.pull_request = PullRequest.get_or_404(pull_request_id)
-        c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
+        c.allowed_to_change_status = self._is_allowed_to_change_status(c.pull_request)
         cc_model = ChangesetCommentsModel()
         cs_model = ChangesetStatusModel()
 
@@ -556,125 +469,139 @@
         c.a_repo = c.pull_request.other_repo
         (c.a_ref_type,
          c.a_ref_name,
-         c.a_rev) = c.pull_request.other_ref.split(':') # other_rev is ancestor
+         c.a_rev) = c.pull_request.other_ref.split(':') # a_rev is ancestor
 
         org_scm_instance = c.cs_repo.scm_instance # property with expensive cache invalidation check!!!
-        c.cs_repo = c.cs_repo
-        c.cs_ranges = [org_scm_instance.get_changeset(x) for x in c.pull_request.revisions]
+        try:
+            c.cs_ranges = []
+            for x in c.pull_request.revisions:
+                c.cs_ranges.append(org_scm_instance.get_changeset(x))
+        except ChangesetDoesNotExistError:
+            c.cs_ranges = []
+            h.flash(_('Revision %s not found in %s') % (x, c.cs_repo.repo_name),
+                'error')
         c.cs_ranges_org = None # not stored and not important and moving target - could be calculated ...
         revs = [ctx.revision for ctx in reversed(c.cs_ranges)]
-        c.jsdata = json.dumps(graph_data(org_scm_instance, revs))
+        c.jsdata = graph_data(org_scm_instance, revs)
 
         c.is_range = False
-        if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
-            cs_a = org_scm_instance.get_changeset(c.a_rev)
-            root_parents = c.cs_ranges[0].parents
-            c.is_range = cs_a in root_parents
-            #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
+        try:
+            if c.a_ref_type == 'rev': # this looks like a free range where target is ancestor
+                cs_a = org_scm_instance.get_changeset(c.a_rev)
+                root_parents = c.cs_ranges[0].parents
+                c.is_range = cs_a in root_parents
+                #c.merge_root = len(root_parents) > 1 # a range starting with a merge might deserve a warning
+        except ChangesetDoesNotExistError: # probably because c.a_rev not found
+            pass
+        except IndexError: # probably because c.cs_ranges is empty, probably because revisions are missing
+            pass
 
         avail_revs = set()
         avail_show = []
         c.cs_branch_name = c.cs_ref_name
+        c.a_branch_name = None
         other_scm_instance = c.a_repo.scm_instance
         c.update_msg = ""
         c.update_msg_other = ""
-        if org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
-            if c.cs_ref_type != 'branch':
-                c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
-            c.a_branch_name = c.a_ref_name
-            if c.a_ref_type != 'branch':
-                try:
-                    c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
-                except EmptyRepositoryError:
-                    c.a_branch_name = 'null' # not a branch name ... but close enough
-            # candidates: descendants of old head that are on the right branch
-            #             and not are the old head itself ...
-            #             and nothing at all if old head is a descendant of target ref name
-            if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
-                c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
-            elif c.pull_request.is_closed():
-                c.update_msg = _('This pull request has been closed and can not be updated.')
-            else: # look for descendants of PR head on source branch in org repo
-                avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
-                                                         revs[0], c.cs_branch_name)
-                if len(avail_revs) > 1: # more than just revs[0]
-                    # also show changesets that not are descendants but would be merged in
-                    targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
-                    if org_scm_instance.path != other_scm_instance.path:
-                        # Note: org_scm_instance.path must come first so all
-                        # valid revision numbers are 100% org_scm compatible
-                        # - both for avail_revs and for revset results
-                        hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
-                                                           org_scm_instance.path,
-                                                           other_scm_instance.path)
+        try:
+            if not c.cs_ranges:
+                c.update_msg = _('Error: changesets not found when displaying pull request from %s.') % c.cs_rev
+            elif org_scm_instance.alias == 'hg' and c.a_ref_name != 'ancestor':
+                if c.cs_ref_type != 'branch':
+                    c.cs_branch_name = org_scm_instance.get_changeset(c.cs_ref_name).branch # use ref_type ?
+                c.a_branch_name = c.a_ref_name
+                if c.a_ref_type != 'branch':
+                    try:
+                        c.a_branch_name = other_scm_instance.get_changeset(c.a_ref_name).branch # use ref_type ?
+                    except EmptyRepositoryError:
+                        c.a_branch_name = 'null' # not a branch name ... but close enough
+                # candidates: descendants of old head that are on the right branch
+                #             and not are the old head itself ...
+                #             and nothing at all if old head is a descendant of target ref name
+                if not c.is_range and other_scm_instance._repo.revs('present(%s)::&%s', c.cs_ranges[-1].raw_id, c.a_branch_name):
+                    c.update_msg = _('This pull request has already been merged to %s.') % c.a_branch_name
+                elif c.pull_request.is_closed():
+                    c.update_msg = _('This pull request has been closed and can not be updated.')
+                else: # look for descendants of PR head on source branch in org repo
+                    avail_revs = org_scm_instance._repo.revs('%s:: & branch(%s)',
+                                                             revs[0], c.cs_branch_name)
+                    if len(avail_revs) > 1: # more than just revs[0]
+                        # also show changesets that not are descendants but would be merged in
+                        targethead = other_scm_instance.get_changeset(c.a_branch_name).raw_id
+                        if org_scm_instance.path != other_scm_instance.path:
+                            # Note: org_scm_instance.path must come first so all
+                            # valid revision numbers are 100% org_scm compatible
+                            # - both for avail_revs and for revset results
+                            hgrepo = unionrepo.unionrepository(org_scm_instance.baseui,
+                                                               org_scm_instance.path,
+                                                               other_scm_instance.path)
+                        else:
+                            hgrepo = org_scm_instance._repo
+                        show = set(hgrepo.revs('::%ld & !::parents(%s) & !::%s',
+                                               avail_revs, revs[0], targethead))
+                        if show:
+                            c.update_msg = _('The following additional changes are available on %s:') % c.cs_branch_name
+                        else:
+                            c.update_msg = _('No additional changesets found for iterating on this pull request.')
                     else:
-                        hgrepo = org_scm_instance._repo
-                    show = set(hgrepo.revs('::%ld & !::%s & !::%s',
-                                           avail_revs, revs[0], targethead))
-                    c.update_msg = _('This pull request can be updated with changes on %s:') % c.cs_branch_name
-                else:
-                    show = set()
-                    avail_revs = set() # drop revs[0]
-                    c.update_msg = _('No changesets found for updating this pull request.')
+                        show = set()
+                        avail_revs = set() # drop revs[0]
+                        c.update_msg = _('No additional changesets found for iterating on this pull request.')
 
-                # TODO: handle branch heads that not are tip-most
-                brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
-                if brevs:
-                    # also show changesets that are on branch but neither ancestors nor descendants
-                    show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
-                    show.add(revs[0]) # make sure graph shows this so we can see how they relate
-                    c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
-                        h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
+                    # TODO: handle branch heads that not are tip-most
+                    brevs = org_scm_instance._repo.revs('%s - %ld - %s', c.cs_branch_name, avail_revs, revs[0])
+                    if brevs:
+                        # also show changesets that are on branch but neither ancestors nor descendants
+                        show.update(org_scm_instance._repo.revs('::%ld - ::%ld - ::%s', brevs, avail_revs, c.a_branch_name))
+                        show.add(revs[0]) # make sure graph shows this so we can see how they relate
+                        c.update_msg_other = _('Note: Branch %s has another head: %s.') % (c.cs_branch_name,
+                            h.short_id(org_scm_instance.get_changeset((max(brevs))).raw_id))
 
-                avail_show = sorted(show, reverse=True)
+                    avail_show = sorted(show, reverse=True)
 
-        elif org_scm_instance.alias == 'git':
-            c.update_msg = _("Git pull requests don't support updates yet.")
+            elif org_scm_instance.alias == 'git':
+                c.cs_repo.scm_instance.get_changeset(c.cs_rev) # check it exists - raise ChangesetDoesNotExistError if not
+                c.update_msg = _("Git pull requests don't support iterating yet.")
+        except ChangesetDoesNotExistError:
+            c.update_msg = _('Error: some changesets not found when displaying pull request from %s.') % c.cs_rev
 
         c.avail_revs = avail_revs
         c.avail_cs = [org_scm_instance.get_changeset(r) for r in avail_show]
-        c.avail_jsdata = json.dumps(graph_data(org_scm_instance, avail_show))
+        c.avail_jsdata = graph_data(org_scm_instance, avail_show)
 
         raw_ids = [x.raw_id for x in c.cs_ranges]
         c.cs_comments = c.cs_repo.get_comments(raw_ids)
-        c.statuses = c.cs_repo.statuses(raw_ids)
+        c.cs_statuses = c.cs_repo.statuses(raw_ids)
 
         ignore_whitespace = request.GET.get('ignorews') == '1'
-        line_context = request.GET.get('context', 3)
+        line_context = safe_int(request.GET.get('context'), 3)
         c.ignorews_url = _ignorews_url
         c.context_url = _context_url
-        c.fulldiff = request.GET.get('fulldiff')
-        diff_limit = self.cut_off_limit if not c.fulldiff else None
+        fulldiff = request.GET.get('fulldiff')
+        diff_limit = None if fulldiff else self.cut_off_limit
 
         # we swap org/other ref since we run a simple diff on one repo
         log.debug('running diff between %s and %s in %s',
                   c.a_rev, c.cs_rev, org_scm_instance.path)
-        txtdiff = org_scm_instance.get_diff(rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
-                                      ignore_whitespace=ignore_whitespace,
-                                      context=line_context)
-
-        diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
-                                             diff_limit=diff_limit)
-        _parsed = diff_processor.prepare()
-
-        c.limited_diff = False
-        if isinstance(_parsed, LimitedDiffContainer):
-            c.limited_diff = True
-
-        c.files = []
-        c.changes = {}
+        try:
+            raw_diff = diffs.get_diff(org_scm_instance, rev1=safe_str(c.a_rev), rev2=safe_str(c.cs_rev),
+                                      ignore_whitespace=ignore_whitespace, context=line_context)
+        except ChangesetDoesNotExistError:
+            raw_diff = _("The diff can't be shown - the PR revisions could not be found.")
+        diff_processor = diffs.DiffProcessor(raw_diff or '', diff_limit=diff_limit)
+        c.limited_diff = diff_processor.limited_diff
+        c.file_diff_data = []
         c.lines_added = 0
         c.lines_deleted = 0
 
-        for f in _parsed:
+        for f in diff_processor.parsed:
             st = f['stats']
             c.lines_added += st['added']
             c.lines_deleted += st['deleted']
-            fid = h.FID('', f['filename'])
-            c.files.append([fid, f['operation'], f['filename'], f['stats']])
-            htmldiff = diff_processor.as_html(enable_comments=True,
-                                              parsed_lines=[f])
-            c.changes[fid] = [f['operation'], f['filename'], htmldiff]
+            filename = f['filename']
+            fid = h.FID('', filename)
+            html_diff = diffs.as_html(enable_comments=True, parsed_lines=[f])
+            c.file_diff_data.append((fid, None, f['operation'], f['old_filename'], filename, html_diff, st))
 
         # inline comments
         c.inline_cnt = 0
@@ -686,8 +613,7 @@
             for comments in lines.values():
                 c.inline_cnt += len(comments)
         # comments
-        c.comments = cc_model.get_comments(c.db_repo.repo_id,
-                                           pull_request=pull_request_id)
+        c.comments = cc_model.get_comments(c.db_repo.repo_id, pull_request=pull_request_id)
 
         # (badly named) pull-request status calculation based on reviewer votes
         (c.pull_request_reviewers,
@@ -696,100 +622,21 @@
          ) = cs_model.calculate_pull_request_result(c.pull_request)
         c.changeset_statuses = ChangesetStatus.STATUSES
 
-        c.as_form = False
-        c.ancestor = None # there is one - but right here we don't know which
+        c.is_ajax_preview = False
+        c.ancestors = None # [c.a_rev] ... but that is shown in an other way
         return render('/pullrequests/pullrequest_show.html')
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def comment(self, repo_name, pull_request_id):
         pull_request = PullRequest.get_or_404(pull_request_id)
-
-        status = request.POST.get('changeset_status')
-        close_pr = request.POST.get('save_close')
-        f_path = request.POST.get('f_path')
-        line_no = request.POST.get('line')
-
-        if (status or close_pr) and (f_path or line_no):
-            # status votes and closing is only possible in general comments
-            raise HTTPBadRequest()
-
-        allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
-        if not allowed_to_change_status:
-            if status or close_pr:
-                h.flash(_('No permission to change pull request status'), 'error')
-                raise HTTPForbidden()
-
-        text = request.POST.get('text', '').strip()
-        if close_pr:
-            text = _('Closing.') + '\n' + text
-
-        comment = ChangesetCommentsModel().create(
-            text=text,
-            repo=c.db_repo.repo_id,
-            user=c.authuser.user_id,
-            pull_request=pull_request_id,
-            f_path=f_path,
-            line_no=line_no,
-            status_change=(ChangesetStatus.get_status_lbl(status)
-                           if status and allowed_to_change_status else None),
-            closing_pr=close_pr
-        )
-
-        action_logger(self.authuser,
-                      'user_commented_pull_request:%s' % pull_request_id,
-                      c.db_repo, self.ip_addr, self.sa)
-
-        if status:
-            ChangesetStatusModel().set_status(
-                c.db_repo.repo_id,
-                status,
-                c.authuser.user_id,
-                comment,
-                pull_request=pull_request_id
-            )
-
-        if close_pr:
-            PullRequestModel().close_pull_request(pull_request_id)
-            action_logger(self.authuser,
-                          'user_closed_pull_request:%s' % pull_request_id,
-                          c.db_repo, self.ip_addr, self.sa)
-
-        Session().commit()
-
-        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
-            return redirect(pull_request.url())
-
-        data = {
-           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
-        }
-        if comment is not None:
-            c.comment = comment
-            data.update(comment.get_dict())
-            data.update({'rendered_text':
-                         render('changeset/changeset_comment_block.html')})
-
-        return data
+        allowed_to_change_status = self._is_allowed_to_change_status(pull_request)
+        return create_cs_pr_comment(repo_name, pull_request=pull_request,
+                allowed_to_change_status=allowed_to_change_status)
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def delete_comment(self, repo_name, comment_id):
-        co = ChangesetComment.get(comment_id)
-        if co.pull_request.is_closed():
-            #don't allow deleting comments on closed pull request
-            raise HTTPForbidden()
-
-        owner = co.author.user_id == c.authuser.user_id
-        repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
-        if h.HasPermissionAny('hg.admin')() or repo_admin or owner:
-            ChangesetCommentsModel().delete(comment=co)
-            Session().commit()
-            return True
-        else:
-            raise HTTPForbidden()
+        return delete_cs_pr_comment(repo_name, comment_id)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/controllers/root.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,33 @@
+# -*- 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/>.
+from tgext.routes import RoutedController
+from kallithea.config.routing import make_map
+from kallithea.lib.base import BaseController
+from kallithea.controllers.error import ErrorController
+from tg import config
+
+
+# This is the main Kallithea entry point; TurboGears will forward all requests
+# to an instance of 'controller.root.RootController' in the configured
+# 'application' module (set by app_cfg.py).  Requests are forwarded to
+# controllers based on the routing mapper that lives in this root instance.
+# The mapper is configured using routes defined in routing.py.  This use of the
+# 'mapper' attribute is a feature of tgext.routes, which is activated by
+# inheriting from its RoutedController class.
+class RootController(RoutedController, BaseController):
+
+    mapper = make_map(config)
+
+    # the following assignment hooks in error handling
+    error = ErrorController()
--- a/kallithea/controllers/search.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/search.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,10 +28,10 @@
 import logging
 import traceback
 import urllib
-from pylons.i18n.translation import _
-from pylons import request, config, tmpl_context as c
+from tg.i18n import ugettext as _
+from tg import request, config, tmpl_context as c
 
-from whoosh.index import open_dir, EmptyIndexError
+from whoosh.index import open_dir, exists_in, EmptyIndexError
 from whoosh.qparser import QueryParser, QueryParserError
 from whoosh.query import Phrase, Prefix
 from webhelpers.util import update_params
@@ -40,19 +40,16 @@
 from kallithea.lib.base import BaseRepoController, render
 from kallithea.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
     IDX_NAME, WhooshResultWrapper
-from kallithea.model.repo import RepoModel
+from kallithea.lib.page import Page
 from kallithea.lib.utils2 import safe_str, safe_int
-from kallithea.lib.helpers import Page
+from kallithea.model.repo import RepoModel
 
 log = logging.getLogger(__name__)
 
 
 class SearchController(BaseRepoController):
 
-    def __before__(self):
-        super(SearchController, self).__before__()
-
-    @LoginRequired()
+    @LoginRequired(allow_default_user=True)
     def index(self, repo_name=None):
         c.repo_name = repo_name
         c.formated_results = []
@@ -85,16 +82,20 @@
             log.debug(cur_query)
 
         if c.cur_query:
-            p = safe_int(request.GET.get('page', 1), 1)
+            p = safe_int(request.GET.get('page'), 1)
             highlight_items = set()
+            index_dir = config['app_conf']['index_dir']
             try:
-                idx = open_dir(config['app_conf']['index_dir'],
-                               indexname=index_name)
+                if not exists_in(index_dir, index_name):
+                    raise EmptyIndexError
+                idx = open_dir(index_dir, indexname=index_name)
                 searcher = idx.searcher()
 
                 qp = QueryParser(search_type, schema=schema_defn)
                 if c.repo_name:
-                    cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
+                    # use "repository_rawname:" instead of "repository:"
+                    # for case-sensitive matching
+                    cur_query = u'repository_rawname:%s %s' % (c.repo_name, cur_query)
                 try:
                     query = qp.parse(unicode(cur_query))
                     # extract words for highlight
@@ -134,12 +135,10 @@
                 except QueryParserError:
                     c.runtime = _('Invalid search query. Try quoting it.')
                 searcher.close()
-            except (EmptyIndexError, IOError):
-                log.error(traceback.format_exc())
-                log.error('Empty Index data')
-                c.runtime = _('There is no index to search in. '
-                              'Please run whoosh indexer')
-            except (Exception):
+            except EmptyIndexError:
+                log.error("Empty search index - run 'kallithea-cli index-create' regularly")
+                c.runtime = _('The server has no search index.')
+            except Exception:
                 log.error(traceback.format_exc())
                 c.runtime = _('An error occurred during search operation.')
 
--- a/kallithea/controllers/summary.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/controllers/summary.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,50 +28,44 @@
 import traceback
 import calendar
 import logging
+import itertools
 from time import mktime
 from datetime import timedelta, date
 
-from pylons import tmpl_context as c, request
-from pylons.i18n.translation import _
+from tg import tmpl_context as c, request
+from tg.i18n import ugettext as _
 from webob.exc import HTTPBadRequest
 
 from beaker.cache import cache_region, region_invalidate
 
-from kallithea.lib.compat import product
 from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
     NodeDoesNotExistError
 from kallithea.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
 from kallithea.model.db import Statistics, CacheInvalidation, User
-from kallithea.lib.utils import jsonify
-from kallithea.lib.utils2 import safe_str
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
-    NotAnonymous
-from kallithea.lib.base import BaseRepoController, render
+from kallithea.lib.utils2 import safe_int, safe_str
+from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
+from kallithea.lib.base import BaseRepoController, render, jsonify
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.markup_renderer import MarkupRenderer
-from kallithea.lib.celerylib import run_task
 from kallithea.lib.celerylib.tasks import get_commits_stats
 from kallithea.lib.compat import json
 from kallithea.lib.vcs.nodes import FileNode
-from kallithea.controllers.changelog import _load_changelog_summary
+from kallithea.lib.page import RepoPage
 
 log = logging.getLogger(__name__)
 
 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
-                    sorted(list(product(ALL_READMES, ALL_EXTS)),
+                    sorted(list(itertools.product(ALL_READMES, ALL_EXTS)),
                            key=lambda y:y[0][1] + y[1][1])]
 
 
 class SummaryController(BaseRepoController):
 
-    def __before__(self):
-        super(SummaryController, self).__before__()
-
     def __get_readme_data(self, db_repo):
         repo_name = db_repo.repo_name
         log.debug('Looking for README file')
 
-        @cache_region('long_term')
+        @cache_region('long_term', '_get_readme_from_cache')
         def _get_readme_from_cache(key, kind):
             readme_data = None
             readme_file = None
@@ -105,24 +99,32 @@
         kind = 'README'
         valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
         if not valid:
-            region_invalidate(_get_readme_from_cache, None, repo_name, kind)
+            region_invalidate(_get_readme_from_cache, None, '_get_readme_from_cache', repo_name, kind)
         return _get_readme_from_cache(repo_name, kind)
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def index(self, repo_name):
-        _load_changelog_summary()
+        p = safe_int(request.GET.get('page'), 1)
+        size = safe_int(request.GET.get('size'), 10)
+        collection = c.db_repo_scm_instance
+        c.cs_pagination = RepoPage(collection, page=p, items_per_page=size)
+        page_revisions = [x.raw_id for x in list(c.cs_pagination)]
+        c.cs_comments = c.db_repo.get_comments(page_revisions)
+        c.cs_statuses = c.db_repo.statuses(page_revisions)
 
-        username = ''
-        if self.authuser.username != User.DEFAULT_USER:
-            username = safe_str(self.authuser.username)
+        if request.authuser.is_default_user:
+            username = ''
+        else:
+            username = safe_str(request.authuser.username)
 
         _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
-        if '{repo}' in _def_clone_uri:
-            _def_clone_uri_by_id = _def_clone_uri.replace('{repo}', '_{repoid}')
-        elif '{repoid}' in _def_clone_uri:
-            _def_clone_uri_by_id = _def_clone_uri.replace('_{repoid}', '{repo}')
+        if '{repo}' in _def_clone_uri_by_id:
+            _def_clone_uri_by_id = _def_clone_uri_by_id.replace('{repo}', '_{repoid}')
+        elif '_{repoid}' in _def_clone_uri:
+            _def_clone_uri = _def_clone_uri.replace('_{repoid}', '{repo}')
+        else:
+            log.error("Configured clone_uri_tmpl %r has no '{repo}' or '_{repoid}' and cannot toggle to use repo id URLs", c.clone_uri_tmpl)
 
         c.clone_repo_url = c.db_repo.clone_url(user=username,
                                                 uri_tmpl=_def_clone_uri)
@@ -134,8 +136,8 @@
         else:
             c.show_stats = False
 
-        stats = self.sa.query(Statistics)\
-            .filter(Statistics.repository == c.db_repo)\
+        stats = Statistics.query() \
+            .filter(Statistics.repository == c.db_repo) \
             .scalar()
 
         c.stats_percentage = 0
@@ -148,12 +150,12 @@
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
                           for x, y in lang_stats_d.items())
 
-            c.trending_languages = json.dumps(
+            c.trending_languages = (
                 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
             )
         else:
             c.no_data = True
-            c.trending_languages = json.dumps([])
+            c.trending_languages = []
 
         c.enable_downloads = c.db_repo.enable_downloads
         c.readme_data, c.readme_file = \
@@ -161,9 +163,7 @@
         return render('summary/summary.html')
 
     @LoginRequired()
-    @NotAnonymous()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @HasRepoPermissionLevelDecorator('read')
     @jsonify
     def repo_size(self, repo_name):
         if request.is_xhr:
@@ -171,9 +171,8 @@
         else:
             raise HTTPBadRequest()
 
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
+    @LoginRequired(allow_default_user=True)
+    @HasRepoPermissionLevelDecorator('read')
     def statistics(self, repo_name):
         if c.db_repo.enable_statistics:
             c.show_stats = True
@@ -192,25 +191,25 @@
         c.ts_min = ts_min_m
         c.ts_max = ts_max_y
 
-        stats = self.sa.query(Statistics)\
-            .filter(Statistics.repository == c.db_repo)\
+        stats = Statistics.query() \
+            .filter(Statistics.repository == c.db_repo) \
             .scalar()
         c.stats_percentage = 0
         if stats and stats.languages:
             c.no_data = False is c.db_repo.enable_statistics
             lang_stats_d = json.loads(stats.languages)
-            c.commit_data = stats.commit_activity
-            c.overview_data = stats.commit_activity_combined
+            c.commit_data = json.loads(stats.commit_activity)
+            c.overview_data = json.loads(stats.commit_activity_combined)
 
             lang_stats = ((x, {"count": y,
                                "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
                           for x, y in lang_stats_d.items())
 
-            c.trending_languages = json.dumps(
+            c.trending_languages = (
                 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
             )
             last_rev = stats.stat_on_revision + 1
-            c.repo_last_rev = c.db_repo_scm_instance.count()\
+            c.repo_last_rev = c.db_repo_scm_instance.count() \
                 if c.db_repo_scm_instance.revisions else 0
             if last_rev == 0 or c.repo_last_rev == 0:
                 pass
@@ -218,12 +217,11 @@
                 c.stats_percentage = '%.2f' % ((float((last_rev)) /
                                                 c.repo_last_rev) * 100)
         else:
-            c.commit_data = json.dumps({})
-            c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
-            c.trending_languages = json.dumps({})
+            c.commit_data = {}
+            c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
+            c.trending_languages = {}
             c.no_data = True
 
         recurse_limit = 500  # don't recurse more than 500 times when parsing
-        run_task(get_commits_stats, c.db_repo.repo_name, ts_min_y,
-                 ts_max_y, recurse_limit)
+        get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit)
         return render('summary/statistics.html')
--- a/kallithea/controllers/tags.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +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.controllers.tags
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Tags controller for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 21, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-import logging
-
-from pylons import tmpl_context as c
-
-from kallithea.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
-from kallithea.lib.base import BaseRepoController, render
-from kallithea.lib.compat import OrderedDict
-
-log = logging.getLogger(__name__)
-
-
-class TagsController(BaseRepoController):
-
-    def __before__(self):
-        super(TagsController, self).__before__()
-
-    @LoginRequired()
-    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
-                                   'repository.admin')
-    def index(self):
-        c.repo_tags = OrderedDict()
-
-        tags = [(name, c.db_repo_scm_instance.get_changeset(hash_)) for \
-                 name, hash_ in c.db_repo_scm_instance.tags.items()]
-        ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
-        for name, cs_tag in ordered_tags:
-            c.repo_tags[name] = cs_tag
-
-        return render('tags/tags.html')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/kallithea-diff.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,216 @@
+/* bootstrap progress bar has margin-bottom we don't want that in files list */
+.cs_files .progress {
+  margin-bottom: 0;
+}
+/* progress bars should be aligned right */
+.cs_files .changes {
+  float: right;
+  color: @kallithea-theme-main-color;
+}
+
+/* colors for deleted/changed/added, both for 3 labels in top right corner with total count, and for per file "diffstat" (rendered as Bootstrap progress-bar) */
+.changes .added {
+  color: inherit;
+  background-color: @add-bg-color;
+}
+.changes .changed {
+  background: @change-bg-color;
+}
+.changes .deleted {
+  background-color: @delete-bg-color;
+}
+/* binary
+NEW_FILENODE = 1
+DEL_FILENODE = 2
+MOD_FILENODE = 3
+RENAMED_FILENODE = 4
+CHMOD_FILENODE = 5
+BIN_FILENODE = 6
+*/
+.changes .bin {
+  background-color: @add-bg-color;
+}
+/* added binary */
+.changes .bin.bin1 {
+  background-color: @add-bg-color;
+}
+/* deleted binary*/
+.changes .bin.bin2 {
+  background-color: @delete-bg-color;
+}
+/* mod binary*/
+.changes .bin.bin3 {
+  background-color: @change-bg-color;
+}
+/* rename file*/
+.changes .bin.bin4 {
+  background-color: #6D99FF;
+}
+/* chmod file*/
+.changes .bin.bin5 {
+  background-color: #6D99FF;
+}
+
+/* center collapse button */
+.diff-collapse {
+  text-align: center;
+  margin-bottom: 15px;
+}
+
+.code-difftable {
+  /* the whole line should be colored */
+  border-collapse: collapse;
+  border-radius: 0px !important;
+  width: 100%;
+
+  /* line coloring */
+  .context {
+    background: none repeat scroll 0 0 @highlight-line-color;
+    color: #999;
+  }
+  .add {
+    background: none repeat scroll 0 0 #DDFFDD;
+  }
+  .add ins {
+    background: none repeat scroll 0 0 #AAFFAA;
+    text-decoration: none;
+  }
+  .del {
+    background: none repeat scroll 0 0 #FFDDDD;
+  }
+  .del del {
+    background: none repeat scroll 0 0 #FFAAAA;
+    text-decoration: none;
+  }
+
+  /* tabs */
+  td.code pre u:before {
+    content: "\21a6";
+    display: inline-block;
+    width: 0;
+  }
+  /* CR */
+  td.code pre u.cr:before {
+    content: "\21a4";
+    display: inline-block;
+  }
+  /* whitespace characters */
+  td.code pre u {
+    color: rgba(0, 0, 0, 0.3);
+  }
+  /* trailing spaces */
+  td.code pre i {
+    border-style: solid;
+    border-width: 0 0 0 1px;
+    border-color: rgba(0, 0, 0, 0.3);
+  }
+
+  /* line numbers */
+  .lineno {
+    padding-left: 2px;
+    padding-right: 2px !important;
+    width: 30px;
+    border-right: 1px solid @panel-default-border !important;
+    vertical-align: middle !important;
+    text-align: center;
+  }
+  .lineno.new {
+    text-align: right;
+  }
+  .lineno.old {
+    text-align: right;
+  }
+  .lineno a {
+    color: #aaa !important;
+    font-size: 11px;
+    font-family: @font-family-monospace;
+    line-height: normal;
+    padding-left: 6px;
+    padding-right: 6px;
+    display: block;
+  }
+  .line:hover .lineno a {
+    color: #333 !important;
+  }
+  /** CODE **/
+  .code {
+    display: block;
+  }
+  .code pre {
+    border: 0;
+    padding: 0;
+    margin: 0;
+    background: none;
+    min-height: 17px;
+    line-height: 17px;
+    white-space: pre-wrap;
+    word-break: break-all;
+  }
+
+  /* leading +/- on changed lines */
+  .del .code pre:before {
+    content: "-";
+    color: #800;
+  }
+  .add .code pre:before {
+    content: "+";
+    color: #080;
+  }
+  .code pre:before {
+    content: " ";
+    margin: 0 2px;
+  }
+}
+
+/* comment bubble */
+.add-bubble {
+  position: relative;
+  display: none;
+  float: left;
+  width: 0px;
+  height: 0px;
+  left: -8px;
+  box-sizing: border-box;
+}
+/* comment bubble, only visible when in a commentable diff */
+.commentable-diff tr.line.add:hover td .add-bubble,
+.commentable-diff tr.line.del:hover td .add-bubble,
+.commentable-diff tr.line.unmod:hover td .add-bubble {
+  display: block;
+  z-index: 1;
+}
+.add-bubble div {
+  background: @kallithea-theme-main-color;
+  width: 16px;
+  height: 16px;
+  line-height: 14px;
+  cursor: pointer;
+  padding: 0 2px 2px 0.5px;
+  border: 1px solid @kallithea-theme-main-color;
+  border-radius: 3px;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+.add-bubble div:before {
+  font-size: 14px;
+  color: #ffffff;
+  font-family: "kallithea";
+  content: '\1f5ea';
+}
+.add-bubble div:hover {
+  transform: scale(1.2, 1.2);
+}
+
+/* file diff icons */
+.icon-diff-modified:before {
+  color: #d0b44c;
+}
+.icon-diff-removed:before {
+  color: #bd2c00;
+}
+.icon-diff-added:before {
+  color: #6cc644;
+}
+.icon-diff-renamed:before {
+  color: #677a85;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/kallithea-labels.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,94 @@
+.label-meta {
+  color: #000;
+}
+.label-meta[data-tag="dead"] {
+  background-color: #E44;
+}
+.label-meta[data-tag="stale"] {
+  background-color: #EA4;
+}
+.label-meta[data-tag="featured"] {
+  background-color: #AEA;
+}
+.label-meta[data-tag="requires"] {
+  background-color: #9CF;
+}
+.label-meta[data-tag="recommends"] {
+  background-color: #BDF;
+}
+.label-meta[data-tag="lang"] {
+  background-color: #FAF474;
+}
+.label-meta[data-tag="license"] {
+  border: solid 1px #9CF;
+  background-color: #DEF;
+}
+.label-meta[data-tag="see"] {
+  border: solid 1px #CBD;
+  background-color: #EDF;
+}
+a.label-metameta[data-tag="license"]:hover {
+  background-color: @kallithea-theme-main-color;
+  color: #FFF;
+  text-decoration: none;
+}
+
+/* repository vcs "alias" */
+.label-repo {
+  border: 1px solid;
+  color: inherit;
+  text-transform: uppercase;
+  padding: .2em .3em 0;
+}
+
+/* permission labels */
+.label-admin {
+  background-color: #B94A48;
+  color: #ffffff;
+}
+.label-write {
+  background-color: #DB7525;
+  color: #ffffff;
+}
+.label-read {
+  background-color: #468847;
+  color: #ffffff;
+}
+.label-none {
+  background-color: #bfbfbf;
+  color: #ffffff;
+}
+
+/* changeset labels */
+.label-merge {
+  background-color: #fca062;
+  color: #ffffff;
+  text-transform: uppercase;
+}
+.label-bookmark {
+  border: 1px solid #46A546;
+  color: #46A546;
+}
+.label-tag {
+  border: 1px solid #62cffc;
+  color: #62cffc;
+}
+.label-bumped,
+.label-divergent,
+.label-extinct,
+.label-unstable {
+  background-color: #f00;
+  border-color: #600;
+  color: #fff;
+}
+.label-phase {
+  border: 1px solid #1F14CE;
+  color: #1F14CE;
+}
+.label-branch {
+  border: 1px solid #d9e8f8;
+  color: #577632;
+}
+.extra-container > .label {
+  padding-bottom: 0.2em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/kallithea-select2.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,68 @@
+// Style these like Bootstrap ".navbar-inverse .navbar-nav > li"
+.branch-switcher.select2-container,
+.repo-switcher.select2-container {
+  > a.select2-choice.select2-default {
+    line-height: @line-height-computed;
+    height: @navbar-height;
+    padding: @nav-link-padding;
+    padding-top: @navbar-padding-vertical;
+    padding-bottom: @navbar-padding-vertical;
+    color: @navbar-inverse-link-color !important;
+    background: inherit;
+    &:hover {
+      color: @navbar-inverse-link-hover-color !important;
+      background-color: @navbar-inverse-link-hover-bg;
+    }
+    border: inherit;
+    border-radius: inherit;
+    box-shadow: inherit;
+    > .select2-chosen {
+      margin-right: inherit; // don't leave room for .select2-arrow
+      line-height: inherit;
+    }
+    > .select2-arrow {
+      display: none;
+    }
+  }
+}
+
+// Style these like Bootstrap .dropdown-menu
+.branch-switcher-dropdown.select2-drop.select2-drop-active,
+.repo-switcher-dropdown.select2-drop.select2-drop-active {
+  color: inherit;
+  background-color: @dropdown-bg;
+  border: 1px solid @dropdown-border;
+  .box-shadow(0 6px 12px rgba(0,0,0,.175));
+  > .select2-results {
+    color: @dropdown-link-color;
+    .select2-highlighted {
+      color: @dropdown-link-hover-color;
+      background-color: @dropdown-link-hover-bg;
+    }
+  }
+}
+
+.select2-container {
+  /* select2 already has border and padding, remove bootstrap form-control border and padding */
+  &.form-control {
+    border: 0;
+    padding: 0;
+  }
+  /* make select2 as big as form-controls */
+  .select2-choice {
+    height: 100%;
+  }
+  .select2-chosen {
+    line-height: @input-height-base - 2; /* subtract border width */
+  }
+}
+
+/* highlight query in user autocomplete search results */
+.select2-results .match {
+  font-weight: 700;
+}
+
+/* fix select2 input style if input-sm is used */
+.select2-container.input-sm input {
+  .input-sm;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/kallithea-variables.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,52 @@
+/* basic variables */
+@kallithea-theme-main-color:        #577632;
+@kallithea-theme-inverse-color:     #FFF;
+@kallithea-theme-inverse-bg:        @kallithea-theme-main-color;
+@kallithea-panel-margin:            10px;
+
+/* logo */
+@kallithea-logo-url:                "../images/kallithea-logo.svg";
+@kallithea-logo-width:              140px;
+@kallithea-logo-height:             30px;
+@kallithea-logo-bottom:             4px;
+
+/* bootstrap override */
+@brand-primary:                     @kallithea-theme-main-color;
+@link-hover-color:                  darken(@kallithea-theme-main-color, 20.0%);
+@btn-default-bg:                    #E7E7E7;
+@font-family-monospace:             Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
+@navbar-height:                     36px;
+@navbar-margin-bottom:              @kallithea-panel-margin;
+@navbar-inverse-color:              @kallithea-theme-inverse-color;
+@navbar-inverse-bg:                 @kallithea-theme-inverse-bg;
+@navbar-inverse-border:             @kallithea-theme-inverse-bg;
+@navbar-inverse-link-color:         @kallithea-theme-inverse-color;
+@navbar-inverse-link-hover-color:   tint(@kallithea-theme-inverse-color, 15.0%);
+@navbar-inverse-link-hover-bg:      tint(@kallithea-theme-inverse-bg, 15.0%);
+@navbar-inverse-link-active-color:  @navbar-inverse-link-hover-color;
+@navbar-inverse-link-active-bg:     @navbar-inverse-link-hover-bg;
+@navbar-inverse-toggle-hover-bg:    @navbar-inverse-link-hover-bg;
+@navbar-inverse-toggle-border-color:@kallithea-theme-inverse-bg;
+@nav-pills-active-link-hover-color: @navbar-inverse-color;
+@nav-pills-active-link-hover-bg:    @navbar-inverse-bg;
+@nav-link-padding:                  8px 10px; /* default: 10px 15px */
+@dropdown-link-color:               @navbar-inverse-color;
+@dropdown-bg:                       @navbar-inverse-bg;
+@dropdown-link-hover-color:         @navbar-inverse-link-hover-color;
+@dropdown-link-hover-bg:            @navbar-inverse-link-hover-bg;
+@grid-gutter-width:                 2 * @kallithea-panel-margin; /* default: 30px */
+@panel-heading-padding:             5px 15px; /* default: 10px 15px */
+@panel-primary-text:                @kallithea-theme-inverse-color;
+@panel-primary-heading-bg:          @kallithea-theme-inverse-bg;
+@panel-primary-border:              @panel-primary-heading-bg;
+@headings-font-weight:              700;
+@table-cell-padding:                2px 4px;
+@icon-font-path:                    ; /* no glyphicons */
+@icon-font-name:                    none;
+
+/* custom variables */
+@highlight-color:                   #FAFFA6;
+@highlight-line-color:              #DDE7EF;
+@add-bg-color:                      #BBFFBB;
+@change-bg-color:                   #DDDDDD;
+@delete-bg-color:                   #FF8888;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/main.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,28 @@
+/*!
+ * Don't edit the css file directly.
+ *
+ * Instead, edit the less file(s) and regenerate the css:
+ *
+ * npm install
+ * npm run less
+ *
+ */
+
+/* 3rd party styles */
+@import "node_modules/bootstrap/less/bootstrap.less";
+@import (inline) "node_modules/datatables.net-bs/css/dataTables.bootstrap.css";
+@import (inline) "node_modules/at.js/dist/css/jquery.atwho.css";
+@import (less) "node_modules/select2/select2.css";
+@import (less) "node_modules/select2-bootstrap-css/select2-bootstrap.css";
+@import (less) "tmp/pygments.css";
+@import (less) "../public/fontello/css/kallithea.css";
+
+/* kallithea styles */
+@import "kallithea-variables.less";
+@import "kallithea-labels.less";
+@import "kallithea-select2.less";
+@import "kallithea-diff.less";
+@import "style.less";
+
+/* finally, import the optional theme file with local customizations */
+@import (optional) "theme.less";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/package.json	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,21 @@
+{
+  "name": "kallithea",
+  "private": true,
+  "dependencies": {
+    "at.js": "1.5.4",
+    "bootstrap": "3.3.7",
+    "codemirror": "4.7",
+    "datatables.net": "1.10.13",
+    "datatables.net-bs": "1.10.13",
+    "jquery": "1.12.3",
+    "jquery.caret": "0.3.1",
+    "jquery.flot": "0.8.3",
+    "select2": "3.5.1",
+    "select2-bootstrap-css": "1.2.4"
+  },
+  "devDependencies": {
+    "less": "~2.7",
+    "less-plugin-clean-css": "~1.5",
+    "license-checker": "24.1.0"
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/front-end/style.less	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,958 @@
+body {
+  background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
+}
+
+/* pseude content that should not be selected or copied by the user */
+[data-pseudo-content]:before {
+  content: attr(data-pseudo-content);
+}
+
+/* class for texts where newlines should be preserved, for very light-weight ascii art markup (like pull request descriptions) */
+.formatted-fixed {
+  font-family: @font-family-monospace;
+  white-space: pre-wrap;
+}
+
+/* use monospace for changeset hashes */
+.changeset_hash {
+  font-family: @font-family-monospace;
+}
+
+/* Note: class 'icon-empty' or 'icon-gravatar' can be used to get icon-ish styling without an actual glyph */
+i[class^='icon-empty'],
+i[class^='icon-gravatar'] {
+  background-repeat: no-repeat;
+  background-position: center;
+  display: inline-block;
+  min-width: 16px;
+  min-height: 16px;
+  margin: -2px 0 -4px 0;
+}
+
+.inline-comments-general.show-general-status .hidden.general-only {
+  display: block !important;
+}
+.truncate {
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -o-text-overflow: ellipsis;
+  -ms-text-overflow: ellipsis;
+}
+.truncate.autoexpand:hover {
+  overflow: visible;
+}
+
+/* show comment anchors when hovering over panel-heading */
+a.permalink {
+  visibility: hidden;
+}
+.panel-heading:hover .permalink {
+  visibility: visible;
+}
+
+.navbar-inverse {
+  border: none;
+}
+
+/* logo */
+nav.navbar.mainmenu > .navbar-header > .navbar-brand {
+  font-size: 20px;
+  padding-top: 12px;
+  > .branding:before {
+    content: "";
+    display: inline-block;
+    margin-right: .2em;
+    background-image: url(@kallithea-logo-url);
+    width: @kallithea-logo-width;
+    height: @kallithea-logo-height;
+    margin-bottom: -@kallithea-logo-bottom;
+    margin-top: -12px;
+  }
+}
+
+/* code highlighting */
+/* don't use bootstrap style for code blocks */
+.code-highlighttable pre {
+  background: inherit;
+  border: 0;
+}
+
+/* every direct child of a panel, that is not .panel-heading, should auto
+ * overflow to prevent overflowing of elements like text boxes and tables */
+.panel > :not(.panel-heading){
+  overflow-x: auto;
+  min-height: 0.01%;
+}
+
+/* allow other exceptions to automatic overflow-x */
+.panel > .overflow-x-visible {
+  overflow-x: visible;
+}
+
+/* margin below PR comments */
+.comment > .panel,
+/* margin below top level panels */
+#main > .panel {
+  margin-bottom: @kallithea-panel-margin;
+}
+
+/* search highlighting */
+div.search-code-body pre .match {
+  background-color: @highlight-color;
+}
+div.search-code-body pre .break {
+  background-color: @highlight-line-color;
+  width: 100%;
+  display: block;
+}
+
+/* use @alert-danger-text for form error messages and .alert-danger for the input element */
+.form-group .error-message {
+  color: @alert-danger-text;
+  display: inline-block;
+  padding-top: 5px;
+  &:empty{
+    display: none;
+  }
+}
+input.error {
+  .alert-danger;
+}
+
+/* datatable */
+.dataTables_left {
+  .pull-left;
+}
+.dataTables_right {
+  .pull-right;
+}
+
+/* make all datatable paginations small */
+.dataTables_paginate .pagination {
+  .pagination-sm;
+}
+
+/* show column sort icons in our font ... and before column header */
+table.dataTable {
+  .sorting_asc:before {
+    font-family: "kallithea";
+    content: "\23f6";
+    padding-right: 8px;
+  }
+  .sorting_desc:before {
+    font-family: "kallithea";
+    content: "\23f7";
+    padding-right: 8px;
+  }
+  .sorting:before {
+    font-family: "kallithea";
+    content: "\2195";
+    padding-right: 8px;
+    opacity: 0.5;
+  }
+  .sorting_asc:after,
+  .sorting_desc:after,
+  .sorting:after {
+    content: "" !important;
+  }
+}
+
+/* _dt_elements.html styling - some submit buttons have their own form but should still be shown inline */
+table.dataTable td > .btn + form {
+  display: inline;
+}
+
+table.dataTable .dt_repo_pending {
+  opacity: 0.5;
+}
+
+/* language bars (summary page) */
+#lang_stats {
+  .progress-bar {
+    min-width: 15px;
+    border-top-right-radius: 8px;
+    border-bottom-right-radius: 8px;
+  }
+  td {
+    padding: 1px 0 !important;
+  }
+}
+
+/* use pointer cursor for expand_commit */
+.expand_commit .icon-align-left {
+  cursor: pointer;
+  color: #999;
+}
+
+/* don't break author, date and comment cells into multiple lines in changeset table */
+table.changesets {
+  .author,
+  .date,
+  .comments {
+    white-space: nowrap;
+  }
+}
+
+/* textareas should be at least 100px high and 400px wide */
+textarea.form-control {
+  font-family: @font-family-monospace;
+  min-height: 100px;
+  min-width: 400px;
+}
+
+/* add some space between the code-browser icons and the file names */
+.browser-dir > i[class^='icon-'],
+.submodule-dir > i[class^='icon-'],
+.browser-file > i[class^='icon-'] {
+  padding-right: 0.3em;
+}
+
+div.panel-primary {
+  border: none;
+}
+
+/* no extra vertical margin */
+#content div.panel ul.pagination {
+  margin: 0;
+}
+
+/* remove margin below footer */
+.navbar.footer {
+  margin-bottom: 0;
+}
+
+.user-menu {
+  padding: 0 !important;
+}
+#quick_login {
+  width: 360px;
+  margin-top: 15px;
+  min-height: 110px;
+}
+#quick_login input#username,
+#quick_login input#password {
+  display: block;
+  margin: 5px 0 10px;
+}
+#quick_login .password_forgotten a,
+#quick_login .register a {
+  padding: 0 !important;
+  line-height: 25px !important;
+  float: left;
+  clear: both;
+}
+#quick_login .submit {
+  float: right;
+}
+#quick_login .submit input#sign_in {
+  margin-top: 5px;
+}
+#quick_login > .pull-left {
+  width: 170px;
+}
+#quick_login > .pull-right {
+  width: 140px;
+}
+#quick_login .full_name {
+  font-weight: bold;
+  padding: 3px;
+}
+#quick_login .email {
+  padding: 3px 3px 3px 0;
+}
+#quick_login :not(input) {
+  color: @kallithea-theme-inverse-color;
+  padding-bottom: 3px;
+}
+
+#journal .journal_user {
+  color: #747474;
+  font-size: 14px;
+  font-weight: bold;
+  height: 30px;
+}
+#journal .journal_user.deleted {
+  color: #747474;
+  font-size: 14px;
+  font-weight: normal;
+  height: 30px;
+  font-style: italic;
+}
+#journal .journal_icon {
+  clear: both;
+  float: left;
+  padding-right: 4px;
+  padding-top: 3px;
+}
+#journal .journal_action {
+  padding-top: 4px;
+  min-height: 2px;
+  float: left;
+}
+#journal .journal_action_params {
+  clear: left;
+  padding-left: 22px;
+}
+#journal .date {
+  clear: both;
+  color: #777777;
+  font-size: 11px;
+  padding-left: 22px;
+}
+#journal .journal_repo_name {
+  font-weight: bold;
+  font-size: 1.1em;
+}
+#journal .compare_view {
+  padding: 5px 0px 5px 0px;
+  width: 95px;
+}
+.trending_language_tbl,
+.trending_language_tbl td {
+  border: 0 !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+.trending_language_tbl,
+.trending_language_tbl tr {
+  border-spacing: 1px;
+}
+h3.files_location {
+  font-size: 1.8em;
+  font-weight: 700;
+  border-bottom: none !important;
+  margin: 10px 0 !important;
+}
+.file_history {
+  padding-top: 10px;
+  font-size: 16px;
+}
+.file_author {
+  float: left;
+}
+.file_author .item {
+  float: left;
+  padding: 5px;
+  color: #888;
+}
+table#updaterevs-table tr.mergerow,
+table#updaterevs-table tr.out-of-range,
+table#changesets tr.mergerow,
+table#changesets tr.out-of-range {
+  opacity: 0.6;
+}
+.issue-tracker-link {
+  color: #3F6F9F;
+  font-weight: bold !important;
+}
+/* changeset statuses (must be the same name as the status) */
+.changeset-status-not_reviewed {
+  color: #bababa;
+}
+.changeset-status-approved {
+  color: #81ba51;
+}
+.changeset-status-rejected {
+  color: #d06060;
+}
+.changeset-status-under_review {
+  color: #ffc71e;
+}
+
+#repo_size {
+  display: block;
+  margin-top: 4px;
+  color: #666;
+  float: right;
+}
+.currently_following {
+  padding-left: 10px;
+  padding-bottom: 5px;
+}
+#switch_repos {
+  position: absolute;
+  height: 25px;
+  z-index: 1;
+}
+#switch_repos select {
+  min-width: 150px;
+  max-height: 250px;
+  z-index: 1;
+}
+table#permissions_manage span.private_repo_msg {
+  font-size: 0.8em;
+  opacity: 0.6;
+}
+table#permissions_manage td.private_repo_msg {
+  font-size: 0.8em;
+}
+table#permissions_manage tr#add_perm_input td {
+  vertical-align: middle;
+}
+div.gravatar {
+  float: left;
+  background-color: #FFF;
+  margin-right: 0.7em;
+  padding: 1px 1px 1px 1px;
+  line-height: 0;
+  border-radius: 3px;
+}
+div.gravatar img {
+  border-radius: 2px;
+}
+.panel-body.settings .nav-pills > :not(.active) > a {
+  color: inherit;
+}
+.panel-body.no-padding {
+  padding: 0;
+}
+.panel-body ~ .panel-body {
+  padding-top: 0;
+}
+.panel-body.no-padding ~ .panel-body {
+  padding-top: 15px;
+}
+.panel-body > :last-child {
+  margin-bottom: 0;
+}
+.panel-body.settings .text-muted {
+  margin: 5px 0;
+}
+ins,
+div.options a:hover {
+  text-decoration: none;
+}
+img,
+nav.navbar #quick li a:hover span.normal,
+#clone_url,
+#clone_url_id {
+  border: none;
+}
+img.icon,
+.right .merge img {
+  vertical-align: bottom;
+}
+#content div.panel div.panel-heading ul.links,
+#content div.panel div.message div.dismiss {
+  float: right;
+  margin: 0;
+  padding: 0;
+}
+nav.navbar #home,
+#content div.panel ul.left,
+#content div.panel ol.left,
+div#commit_history,
+div#legend_data,
+div#legend_container,
+div#legend_choices {
+  float: left;
+}
+
+/* set size for statistics charts */
+#commit_history {
+  width: 450px;
+  height: 300px;
+}
+#overview {
+  clear: both;
+  width: 450px;
+  height: 100px;
+}
+
+#content #left #menu ul.closed,
+#content #left #menu li ul.collapsed {
+  display: none;
+}
+#content #left #menu ul.opened,
+#content #left #menu li ul.expanded {
+  display: block !important;
+}
+
+#content div.panel ol.lower-roman,
+#content div.panel ol.upper-roman,
+#content div.panel ol.lower-alpha,
+#content div.panel ol.upper-alpha,
+#content div.panel ol.decimal {
+  margin: 10px 24px 10px 44px;
+}
+
+div.form div.form-group div.button input,
+#content div.panel div.form div.buttons input,
+div.form div.buttons input,
+#content div.panel div.action div.button input {
+  font-size: 11px;
+  font-weight: 700;
+  margin: 0;
+}
+div.form div.form-group div.highlight,
+#content div.panel div.form div.buttons div.highlight {
+  display: inline;
+}
+#content div.panel table td.user,
+#content div.panel table td.address {
+  width: 10%;
+  text-align: center;
+}
+#content div.panel div.action div.button {
+  text-align: right;
+  margin: 6px 0 0;
+  padding: 0;
+}
+.ac .match {
+  font-weight: 700;
+  padding-top: 5px;
+  padding-bottom: 5px;
+}
+.q_filter_box {
+  border-radius: 4px;
+  border: 0 none;
+  margin-bottom: -4px;
+  margin-top: -4px;
+  padding-left: 3px;
+}
+#node_filter {
+  border: 0px solid #545454;
+  color: #AAAAAA;
+  padding-left: 3px;
+}
+/** comment main **/
+.comment .panel,
+.comment-inline-form {
+  max-width: 978px;
+}
+.comment .panel-body {
+  background-color: #FAFAFA;
+}
+.comments-number {
+  padding: 10px 0;
+  color: #666;
+}
+.automatic-comment {
+  font-style: italic;
+}
+/** comment form **/
+.status-block {
+  margin: 5px;
+  clear: both;
+}
+.panel-heading .pull-left input[type=checkbox],
+.panel-heading .pull-right input[type=checkbox] {
+  position: relative;
+  top: 4px;
+  margin: -10px 2px 0;
+}
+/** indent actual inline comments - not general comments **/
+td.inline-comments {
+  padding: @kallithea-panel-margin;
+}
+.inline-comments .comments-number {
+  padding: 0px 0px 10px 0px;
+}
+.comment-inline-well {
+  margin-bottom: @kallithea-panel-margin;
+}
+input.status_change_checkbox,
+input.status_change_radio {
+  margin: 0 0 5px 15px;
+}
+@keyframes animated-comment-background {
+  0% {
+    background-position: 0 0;
+  }
+  100% {
+    background-position: 20px 0;
+  }
+}
+.comment-preview.failed .user,
+.comment-preview.failed .panel-body {
+  color: #666;
+}
+.comment-preview .comment-submission-status {
+  float: right;
+}
+.comment-preview .comment-submission-status .btn-group {
+  margin-left: 10px;
+}
+.comment-preview.submitting .panel-body {
+  background-image: linear-gradient(-45deg, #FAFAFA, #FAFAFA 25%, #FFF 25%, #FFF 50%, #FAFAFA 50%, #FAFAFA 75%, #FFF 75%, #FFF 100%);
+  background-size: 20px 20px;
+  animation: animated-comment-background 0.4s linear infinite;
+}
+/****
+PULL REQUESTS
+*****/
+div.pr-details-title.closed {
+  color: #555;
+  background: #eee;
+}
+div.pr {
+  margin: 0px 15px;
+  padding: 4px 4px;
+}
+tr.pr-closed td {
+  background-color: #eee !important;
+  color: #555 !important;
+}
+span.pr-closed-tag {
+  margin-bottom: 1px;
+  margin-right: 1px;
+  padding: 1px 3px;
+  font-size: 10px;
+  color: @kallithea-theme-main-color;
+  white-space: nowrap;
+  border-radius: 4px;
+  border: 1px solid #d9e8f8;
+  line-height: 1.5em;
+}
+#s2id_org_ref,
+#s2id_other_ref,
+#s2id_org_repo,
+#s2id_other_repo {
+  min-width: 150px;
+  margin: 5px;
+}
+#pr-summary > .pr-not-edit {
+  min-height: 50px !important;
+}
+/* make 'next iteration' changeset table smaller and scrollable */
+#pr-summary #updaterevs {
+  max-height: 200px;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+/****
+  PERMS
+*****/
+.perm-gravatar-ac {
+  vertical-align: middle;
+  padding: 2px;
+  width: 14px;
+  height: 14px;
+}
+
+/* avoid gaps between the navbar and browser */
+.navbar.mainmenu {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.navbar.footer {
+  border-bottom-left-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+/* show some context of link targets - but only works when the link target
+   can be extended with any visual difference */
+div.comment:target:before {
+  display: block;
+  height: 100px;
+  margin: -100px 0 0;
+  content: "";
+}
+div.comment:target > .panel {
+  border: solid 2px #ee0 !important;
+}
+.lineno:target a {
+  border: solid 2px #ee0 !important;
+  margin: -2px;
+}
+.btn-image-diff-show,
+.btn-image-diff-swap {
+  margin: 5px;
+}
+.img-diff {
+  max-width: 45%;
+  height: auto;
+  margin: 5px;
+  /* http://lea.verou.me/demos/css3-patterns.html */
+  background-image: linear-gradient(45deg, #888 25%, transparent 25%, transparent), linear-gradient(-45deg, #888 25%, transparent 25%, transparent), linear-gradient(45deg, transparent 75%, #888 75%), linear-gradient(-45deg, transparent 75%, #888 75%);
+  background-size: 10px 10px;
+  background-color: #999;
+}
+.img-preview {
+  max-width: 100%;
+  height: auto;
+  margin: 5px;
+}
+div.comment-prev-next-links div.prev-comment,
+div.comment-prev-next-links div.next-comment {
+  display: inline-block;
+  min-width: 150px;
+  margin: 3px 15px;
+}
+#comments-general-comments div.comment-prev-next-links div.prev-comment,
+#comments-general-comments div.comment-prev-next-links div.next-comment {
+  margin-left: 0;
+}
+
+/* changelog graph */
+#graph_nodes,
+#updaterevs-graph {
+  .make-xs-column(1);
+  height: 0;
+}
+#graph_content,
+#graph_content_pr,
+#updaterevs-table {
+  .make-xs-column-offset(1);
+  .make-xs-column(11);
+  padding: 0;
+}
+
+/* use bootstrap grid columns for centered columns */
+.centered-column {
+  .make-sm-column-offset(3);
+  .make-sm-column(6);
+  .form {
+    .form-horizontal;
+    .form-group > label {
+      .make-sm-column(4);
+    }
+    .form-group > div {
+      .make-sm-column(8);
+    }
+    .form-group > div:first-child { /* in case there is no label */
+      .make-sm-column-offset(4);
+      .make-sm-column(8);
+    }
+  }
+}
+
+/* use columns and form-horizontal for settings pages ... on wide screens */
+@media (min-width: @screen-sm-min) {
+  .settings {
+    max-width: @container-md;
+    > ul.nav-stacked {
+      .make-sm-column(2);
+      max-width: (@container-md/12)*2;
+    }
+    > div {
+      .make-sm-column(10);
+      max-width: (@container-md/12)*10;
+    }
+    .form {
+      .form-horizontal;
+    }
+    .form-group {
+      .clearfix;
+      > label {
+        .make-xs-column(3);
+        overflow: hidden;
+        text-overflow: ellipsis;
+        input {
+          width: 100%;
+        }
+      }
+      > div {
+        .make-xs-column(9);
+      }
+      .buttons {
+        .make-xs-column-offset(3);
+      }
+    }
+  }
+}
+
+/* use columns and form-horizontal for summary page */
+#summary {
+  max-width: @container-md;
+  .form-horizontal;
+  .make-sm-column(10);
+  padding-left: 0;
+  .form-group > label {
+    .make-sm-column(2);
+  }
+  .form-group > div {
+    .make-sm-column(10);
+  }
+}
+#summary-menu-stats {
+  .make-sm-column(2);
+  padding-right: 0;
+}
+
+/* use columns and form-horizontal for pull request page */
+.pr-box {
+  .make-sm-column(9);
+  padding-left: 0;
+  max-width: @container-md;
+  #pr-summary {
+    .form-horizontal;
+    .form-group > label {
+      .make-sm-column(3);
+    }
+    .form-group > div {
+      .make-sm-column(9);
+    }
+    .form-group > .buttons {
+      .make-sm-column-offset(3);
+      .make-sm-column(9);
+    }
+  }
+}
+.pr-reviewers-box {
+  .make-sm-column(3);
+  padding-right: 0;
+}
+
+/* repo table icons */
+#repos_list_wrap_wrapper {
+  /* make icon-folder and repotag the same width */
+  .icon-folder:before {
+    margin: 0; // default margin would otherwise add to the total width
+    width: 24px;
+    text-align: left;
+  }
+  .label-repo {
+    display: inline-block;
+    width: 24px;
+  }
+}
+
+/* changelog table columns */
+.table#changesets {
+  table-layout: fixed;
+  td {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    vertical-align: baseline;
+    &:last-child {
+      padding-right: 0;
+    }
+  }
+  .checkbox-column {
+    width: 20px;
+    padding: 0;
+    /* the optional second checkbox will be inline-block but should wrap to a new line */
+    white-space: normal;
+    > input[type=checkbox] {
+      margin-top: inherit;
+      vertical-align: text-bottom;
+    }
+  }
+  .changeset-logical-index {
+    color: @gray-light;
+    font-style: italic;
+    font-size: 85%;
+    text-align: right;
+    overflow: visible;
+  }
+  .changeset-logical-index,
+  .expand_commit,
+  .status {
+    width: 28px;
+  }
+  .author {
+    width: 200px;
+    @media (max-width: @screen-sm-max) {
+      width: 120px;
+    }
+    @media (max-width: @screen-xs-max) {
+      width: 20px;
+      /* keep gravatar but hide name on tiny screens to give important columns more room */
+      span {
+        .hidden;
+      }
+    }
+    > .icon-user::before {
+      margin: 0;
+    }
+  }
+  .hash {
+    .small;
+    width: 110px;
+    @media (max-width: @screen-xs-max) {
+      width: 48px;
+    }
+  }
+  .date {
+    .small;
+    width: 100px;
+  }
+  /* hide on small screens to give important columns more room */
+  .status,
+  .expand_commit,
+  .comments,
+  .extra-container {
+    .hidden-xs;
+  }
+  .mid > .log-container {
+    position: relative;
+    overflow: hidden;
+    > .extra-container {
+      position: absolute;
+      top: 0;
+      right: 0;
+      background: white;
+      box-shadow: -10px 0px 10px 0px white;
+    }
+  }
+}
+
+/* undo Bootstrap chrome/webkit blue outline on focus in navbar */
+.navbar-inverse .navbar-nav > li > a:focus {
+  outline: 0;
+}
+
+/* use same badge coloring in navbar inverse as in panel-heading */
+.navbar-inverse {
+  .badge {
+    color: @navbar-inverse-bg;
+    background-color: @navbar-inverse-color;
+  }
+}
+
+/* pygments style */
+div.search-code-body pre .match {
+  background-color: @highlight-color;
+}
+div.search-code-body pre .break {
+  background-color: @highlight-line-color;
+  width: 100%;
+  color: #747474;
+  display: block;
+}
+div.annotatediv {
+  margin-left: 2px;
+  margin-right: 4px;
+}
+.code-highlight {
+  border-left: 1px solid #ccc;
+}
+.code-highlight pre,
+.linenodiv pre {
+  padding: 5px 2px 0px 5px;
+  margin: 0;
+}
+.code-highlight pre div:target {
+  background-color: #FFFFBE !important;
+}
+.linenos a { text-decoration: none; }
+
+/* Stylesheets for the context bar */
+#quick_login > .pull-right .list-group-item {
+  background-color: @kallithea-theme-main-color;
+  border: 0;
+}
+#content #context-pages .follow .show-following,
+#content #context-pages .following .show-follow {
+  display: none;
+}
+
+nav.navbar #quick > li > a,
+#context-pages > ul > li > a {
+  height: @navbar-height;
+}
+
+/* align with panel-heading */
+nav.navbar#context-bar > .container-fluid {
+  padding-left: 15px;
+}
+
+/* at.js */
+.atwho-view strong {
+  /* the blue color doesn't look good, use normal color */
+  color: inherit;
+}
--- a/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/be/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -1,104 +1,128 @@
 # Belarusian translations for Kallithea.
-# Copyright (C) 2015 Various authors, licensing as GPLv3
+# Copyright (C) 2016 Various authors, licensing as GPLv3
 # This file is distributed under the same license as the Kallithea project.
-# Automatically generated, 2015.
+# Automatically generated, 2016.
 # #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2017-04-22 21:08+0000\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2017-08-20 10:44+0000\n"
 "Last-Translator: Viktar Vauchkevich <victorenator@gmail.com>\n"
-"Language-Team: Belarusian "
-"<https://hosted.weblate.org/projects/kallithea/stable/be/>\n"
+"Language-Team: Belarusian <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/be/>\n"
 "Language: be\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
-"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
-"X-Generator: Weblate 2.14-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Generator: Weblate 2.17-dev\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Яшчэ не было змен"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Нічога"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(зачынена)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
-msgstr "Адлюстроўваць прабелы"
-
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+msgstr "Паказваць прабелы"
+
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ігнараваць прабелы"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Павялічыць кантэкст да %(num)s радкоў"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "Няма правоў змяняць статус pull-запыту"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Pull-запыт %s паспяхова выдалены"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Няма такой рэвізіі ў гэтым рэпазітары"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr "Нельга рэдагаваць статус змен, злучаных з зачыненымі pull-request'ами"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Выбраць набор змен"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "Не атрымалася знайсці іншы рэпазітар %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr "Немагчыма параўноўваць рэпазітары розных тыпаў"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Немагчыма параўноўваць рэпазітары без агульнага продка"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Няма адказу"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Невядомая памылка"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr "Запыт не распазнаны серверам з-за няправільнага сінтаксісу."
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "У вас няма правоў для прагляду гэтай старонкі"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Рэсурс не знойдзены"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -106,360 +130,361 @@
 "Сервер не можа выканаць запыт з-за нечаканых умоваў, якія ўзніклі падчас "
 "яго спрацавання."
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "Змены ў рэпазітары %s"
-
-#: kallithea/controllers/feed.py:56
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s выканаў каміт у %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Змены апынуліся занадта вялікімі і былі скарочаныя..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "Стужка навін %s %s"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Змены апынуліся занадта вялікімі і былі выразаныя..."
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s выканаў каміт у %s"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Змены ў рэпазітары %s"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Націсніце каб дадаць новы файл"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Няма файлаў. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr "%s (%s)"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Рэпазітар заблакаваў %s у %s"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
-msgstr "Вы можаце выдаляць файлы толькі ў рэвізіі, злучанай з існай галінкай"
-
-#: kallithea/controllers/files.py:328
+msgstr "Вы можаце выдаляць файлы толькі ў рэвізіі, злучанай з існай галінай"
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Файл %s выдалены з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Файл %s выдалены"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Падчас каміта адбылася памылка"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
-msgstr "Вы можаце рэдагаваць файлы толькі ў рэвізіі, злучанай з існай галінкай"
-
-#: kallithea/controllers/files.py:391
+msgstr ""
+"Вы можаце рэдагаваць файлы толькі ў рэвізіі, злучанай з існай галінай"
+
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Файл %s адрэдагаваны з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Без змен"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
-msgstr "Змены ўжыты ў %s"
-
-#: kallithea/controllers/files.py:443
+msgstr "Змены захаваныя ў %s"
+
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Файл дададзены з дапамогай Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Пуста"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Безназоўны"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Размяшчэнне павінна быць адносным шляхам, і не можа ўтрымліваць \"..\" у "
 "шляхі"
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
-msgstr "Магчымасць спампоўваць адключана"
-
-#: kallithea/controllers/files.py:537
+msgstr "Магчымасць спампоўваць адключаная"
+
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Невядомая рэвізія %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Пусты рэпазітар"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Невядомы тып архіва"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Набор змен"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
-msgstr "Галінкі"
-
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+msgstr "Галіны"
+
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Тэгі"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
-msgstr "Адбылася памылка падчас стварэння форка рэпазітара %s"
-
-#: kallithea/controllers/home.py:84
+msgstr "Памылка падчас стварэння форка рэпазітара %s"
+
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Групы"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Рэпазітары"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
-msgstr "Галінка"
-
-#: kallithea/controllers/home.py:136
+msgstr "Галіна"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Зачыненыя галіны"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Тэгі"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Закладкі"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
-msgstr "Публічны часопіс"
-
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+msgstr "Публічны журнал"
+
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
-msgstr "Часопіс"
-
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+msgstr "Журнал"
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Няслушная капча"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Рэгістрацыя ў Kallithea прайшла паспяхова"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Рэгістрацыя ў %s прайшла паспяхова"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr "Спасылка для скідання пароля адасланая"
-
-#: kallithea/controllers/login.py:246
+msgstr "Код для скідання пароля адпраўлены"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
-msgstr "Няспраўны код для скідання пароля"
-
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+msgstr "Няслушны код скідання пароля"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Пароль абноўлены"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "Няслушны рэцэнзент \"%s\""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (зачынена)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Змены"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "Адмысловы"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
-msgstr "Галінкі ўдзельніка"
-
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+msgstr "Галіны ўдзельніка"
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Закладкі"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Памылка пры стварэнні pull-запыту: %s"
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Няма апісання"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "Pull-запыт створаны паспяхова"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "Адбылася памылка пры стварэнні pull-запыту"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr "Адсутныя рэвізіі адносна папярэдняга pull-запыту:"
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr "Новыя рэвізіі на %s %s адносна папярэдняга pull-запыту:"
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr "Гэты pull-запыт заснаваны на іншай рэвізіі %s, просты diff немагчымы."
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr "Няма змен на %s %s адносна папярэдняй версіі."
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "Зачынены, замешчаны %s."
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Pull-запыт створаны паспяхова"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
 msgstr "Абнаўленне для pull-запыту створана"
 
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Няма апісання"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "Pull-запыт абноўлены"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Pull-запыт паспяхова выдалены"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, fuzzy, python-format
+#| msgid "Changeset for %s %s not found in %s"
+msgid "Revision %s not found in %s"
+msgstr "Набор змен для %s %s не знойдзены ў %s"
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "Няма змен для абнаўлення гэтага pull-запыту."
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
-msgstr "Гэты pull-запыт ужо прыняты на галінку %s."
-
-#: kallithea/controllers/pullrequests.py:597
+msgstr "Гэты pull-запыт ужо прыняты на галіну %s."
+
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr "Гэты pull-запыт быў зачынены і не можа быць абноўлены."
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr "Гэты pull-запыт можа быць абноўлены з %s:"
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
+#: kallithea/controllers/pullrequests.py:543
+#, fuzzy, python-format
+#| msgid "The following changes are available on %s:"
+msgid "The following additional changes are available on %s:"
+msgstr "Гэтыя змены даступныя на %s:"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
 msgstr "Няма змен для абнаўлення гэтага pull-запыту."
 
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
-msgstr "Увага: Галінка %s мае яшчэ адну верхавіну: %s."
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr "Абнаўленне pull-запытаў git не падтрымліваецца."
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr "Няма правоў змяняць статус pull-запыту"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Зачынены."
-
-#: kallithea/controllers/search.py:135
+msgstr "Увага: Галіна %s мае яшчэ адну верхавіну: %s."
+
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
+msgstr "Абнаўленне pull-запытаў git яшчэ не падтрымліваецца."
+
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "Няма змен для абнаўлення гэтага pull-запыту."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "Недапушчальны пошукавы запыт. Паспрабуйце скласці яго ў двукоссі."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Індэксы адсутнічаюць. Калі ласка, запусціце індэксатар Whoosh"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
-msgstr "Адбылася памылка пры выкананні гэтага пошуку."
-
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+msgstr "Памылка пры выкананні гэтага пошуку."
+
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "Няма дадзеных"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "Статыстычныя дадзеныя адключаны для гэтага рэпазітара"
 
@@ -469,153 +494,155 @@
 
 #: kallithea/controllers/admin/auth_settings.py:146
 msgid "error occurred during update of auth settings"
-msgstr "адбылася памылка пры абнаўленні налад аўтарызацыі"
-
-#: kallithea/controllers/admin/defaults.py:97
+msgstr "памылка пры абнаўленні налад аўтарызацыі"
+
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Стандартныя налады паспяхова абноўлены"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
-msgstr "Адбылася памылка пры абнаўленні стандартных налад"
+msgstr "Памылка пры абнаўленні стандартных налад"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "Назаўжды"
 
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr "Назаўжды"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 хвілін"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 хвілін"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 гадзіна"
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "1 гадзіна"
-
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 дзень"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 месяц"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Тэрмін"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Адбылася памылка падчас стварэння gist-запіса"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist-запіс %s выдалены"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
 msgstr "Без змен"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
+msgstr "Gist-запіс абноўлены"
+
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
-msgstr "Дадзеныя gist-запісы абноўлены"
-
-#: kallithea/controllers/admin/gists.py:270
+msgstr "Gist-запіс абноўлены"
+
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
-msgstr "Адбылася памылка пры абнаўленні gist-запісы %s"
-
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+msgstr "Памылка пры абнаўленні gist-запісу %s"
+
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
-"Вы не можаце змяніць дадзеныя гэтага карыстача, паколькі ён важны для "
-"працы ўсяго прыкладання"
-
-#: kallithea/controllers/admin/my_account.py:129
+"Вы не можаце змяніць дадзеныя гэтага карыстальніка, паколькі ён важны для "
+"працы ўсёй праграмы"
+
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Ваш уліковы запіс паспяхова абноўлены"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
-msgstr "Адбылася памылка пры абнаўленні карыстача %s"
-
-#: kallithea/controllers/admin/my_account.py:178
+msgstr "Памылка пры абнаўленні карыстальніка %s"
+
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Памылка пры абнаўленні пароля"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
-msgstr "Карыстачу дададзены e-mail %s"
-
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+msgstr "Карыстальніку дададзены e-mail %s"
+
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
-msgstr "Адбылася памылка пры захаванні e-mail"
-
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+msgstr "Памылка пры захаванні e-mail"
+
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
-msgstr "E-mail карыстача выдалены"
+msgstr "E-mail карыстальніка выдалены"
+
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr "API-ключ паспяхова створаны"
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr "API-ключ паспяхова скінуты"
 
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr "API-ключ паспяхова створаны"
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr "API-ключ паспяхова скінуты"
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "API-ключ паспяхова выдалены"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Чытанне"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Запіс"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -623,623 +650,624 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Адміністратар"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Адключана"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Дазволена, з ручной актывацыяй уліковага запісу"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Дазволена, з аўтаматычнай актывацыяй уліковага запісу"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Ручная актывацыя вонкавага ўліковага запісу"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Аўтаматычная актывацыя вонкавага ўліковага запісу"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Адключана"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Дазволена, з ручной актывацыяй уліковага запісу"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Дазволена, з аўтаматычнай актывацыяй уліковага запісу"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Ручная актывацыя вонкавага ўліковага запісу"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Аўтаматычная актывацыя вонкавага ўліковага запісу"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Уключана"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
-msgstr "Глабальныя прывілеі паспяхова абноўлены"
-
-#: kallithea/controllers/admin/permissions.py:139
+msgstr "Глабальныя прывілеі паспяхова абноўленыя"
+
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Адбылася памылка падчас абнаўлення прывілеяў"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Адбылася памылка пры стварэнні групы рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
-msgstr "Створана новая група рэпазітароў %s"
-
-#: kallithea/controllers/admin/repo_groups.py:250
+msgstr "Створаная новая група рэпазітароў %s"
+
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
-msgstr "Група рэпазітароў %s абноўлена"
-
-#: kallithea/controllers/admin/repo_groups.py:266
+msgstr "Група рэпазітароў %s абноўленая"
+
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Адбылася памылка пры абнаўленні групы рэпазітароў %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
-msgstr "Дадзеная група ўтрымоўвае %s рэпазітароў і не можа быць выдалена"
-
-#: kallithea/controllers/admin/repo_groups.py:291
+msgstr "Група ўтрымлівае %s рэпазітароў і не можа быць выдаленая"
+
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
-msgstr "Група ўтрымоўвае ў сабе %s падгруп і не можа быць выдалены"
-
-#: kallithea/controllers/admin/repo_groups.py:297
+msgstr "Група ўтрымлівае ў сабе %s падгруп і не можа быць выдаленая"
+
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
-msgstr "Група рэпазітароў %s выдалена"
-
-#: kallithea/controllers/admin/repo_groups.py:302
+msgstr "Група рэпазітароў %s выдаленая"
+
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
-msgstr "Адбылася памылка пры выдаленні групы рэпазітароў %s"
-
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+msgstr "Памылка пры выдаленні групы рэпазітароў %s"
+
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Адміністратар не можа адклікаць свае прывелеі"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
-msgstr "Прывілеі групы рэпазітароў абноўлены"
-
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+msgstr "Прывілеі групы рэпазітароў абноўленыя"
+
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
-msgstr "Адбылася памылка пры водгуку прывелеі"
-
-#: kallithea/controllers/admin/repos.py:152
+msgstr "Памылка пры водгуку прывелея"
+
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
-msgstr "Адбылася памылка пры стварэнні рэпазітара %s"
-
-#: kallithea/controllers/admin/repos.py:213
+msgstr "Памылка пры стварэнні рэпазітара %s"
+
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Рэпазітар %s створаны з %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
-msgstr "Зроблены форк(копія) рэпазітара %s на %s"
-
-#: kallithea/controllers/admin/repos.py:225
+msgstr "Зроблены форк рэпазітара %s на %s"
+
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Рэпазітар %s створаны"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Рэпазітар %s паспяхова абноўлены"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
-msgstr "Адбылася памылка падчас абнаўлення рэпазітара %s"
-
-#: kallithea/controllers/admin/repos.py:310
+msgstr "Памылка падчас абнаўлення рэпазітара %s"
+
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
-msgstr "Форки %s адлучаны"
-
-#: kallithea/controllers/admin/repos.py:313
+msgstr "Форкі %s адлучаныя"
+
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
-msgstr "Выдалены форки рэпазітара %s"
-
-#: kallithea/controllers/admin/repos.py:318
+msgstr "Выдаленыя форки рэпазітара %s"
+
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Рэпазітар %s выдалены"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "Немагчыма выдаліць %s, ён усё яшчэ мае форкі"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
-msgstr "Адбылася памылка падчас выдалення %s"
-
-#: kallithea/controllers/admin/repos.py:374
+msgstr "Памылка падчас выдалення %s"
+
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
-msgstr "Прывілеі рэпазітара абноўлены"
-
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr "Адбылася памылка пры стварэнні поля"
-
-#: kallithea/controllers/admin/repos.py:444
+msgstr "Прывілеі рэпазітара абноўленыя"
+
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
+msgstr "Памылка пры стварэнні поля"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
-msgstr "Адбылася памылка пры выдаленні поля"
-
-#: kallithea/controllers/admin/repos.py:460
+msgstr "Памылка пры выдаленні поля"
+
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Не форк --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Бачнасць рэпазітара ў публічным часопісе абноўлена"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
-msgstr "Адбылася памылка пры ўсталёўцы рэпазітара ў агульнадаступны часопіс"
-
-#: kallithea/controllers/admin/repos.py:512
+msgstr "Памылка пры даданні рэпазітара ў агульнадаступны часопіс"
+
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Нічога"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Рэпазітар %s адзначаны як форк %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
-msgstr "Адбылася памылка пры выкананні аперацыі"
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+msgstr "Памылка пры выкананні аперацыі"
+
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "Рэпазітар заблакаваны"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
-msgstr "Рэпазітар адблакаваны"
-
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+msgstr "Рэпазітар разблакаваны"
+
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
-msgstr "Адбылася памылка падчас разблакавання"
-
-#: kallithea/controllers/admin/repos.py:582
+msgstr "Памылка падчас разблакавання"
+
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "Кэш скінуты"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
-msgstr "Адбылася памылка пры ачыстцы кэша"
-
-#: kallithea/controllers/admin/repos.py:601
+msgstr "Памылка пры скіданні кэша"
+
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
-msgstr "Занесены змены з выдаленага рэпазітара"
-
-#: kallithea/controllers/admin/repos.py:604
+msgstr "Занесеныя змены з аддаленага рэпазітара"
+
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
-msgstr "Адбылася памылка пры занясенні змен з выдаленага рэпазітара"
-
-#: kallithea/controllers/admin/repos.py:637
+msgstr "Памылка пры занясенні змен з аддаленага рэпазітара"
+
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "Адбылася памылка пры выдаленні статыстыкі рэпазітара"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "Абноўлены налады VCS"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
 msgstr ""
-"Немагчыма ўключыць падтрымку hgsubversion. Бібліятэка «hgsubversion» "
+"Немагчыма ўключыць падтрымку hgsubversion. Бібліятэка hgsubversion "
 "адсутнічае"
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
-msgstr "Адбылася памылка пры абнаўленні налад прыкладання"
-
-#: kallithea/controllers/admin/settings.py:211
+msgstr "Памылка пры абнаўленні наладаў праграмы"
+
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
-msgstr "Рэпазітары паспяхова перасканіраваны, дададзена: %s, выдалена: %s."
-
-#: kallithea/controllers/admin/settings.py:226
+msgstr "Рэпазітары паспяхова перасканаваныя, дададзена: %s, выдалена: %s."
+
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr "Скінуць кэш для %s рэпазітароў"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
-msgstr "Абноўленыя параметры налады прыкладання"
-
-#: kallithea/controllers/admin/settings.py:337
+msgstr "Абноўленыя налады праграмы"
+
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Налады візуалізацыі абноўленыя"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
-msgstr "Адбылася памылка пры абнаўленні налад візуалізацыі"
-
-#: kallithea/controllers/admin/settings.py:368
+msgstr "Адбылася памылка пры абнаўленні наладаў візуалізацыі"
+
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
-msgstr "Калі ласка, увядзіце email-адрас"
-
-#: kallithea/controllers/admin/settings.py:383
+msgstr "Калі ласка, увядзіце e-mail-адрас"
+
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr "Задача адпраўкі e-mail створаная"
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Няма дадзеных"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Дададзены новы хук"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Абноўленыя хукі"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
-msgstr "адбылася памылка пры стварэнні хука"
-
-#: kallithea/controllers/admin/settings.py:458
+msgstr "Памылка пры стварэнні хука"
+
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
-msgstr "Запланавана пераіндэксаванне базы Whoosh"
-
-#: kallithea/controllers/admin/user_groups.py:150
+msgstr "Запланаванае пераіндэксаванне базы Whoosh"
+
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
-msgstr "Створана група карыстачоў %s"
-
-#: kallithea/controllers/admin/user_groups.py:163
+msgstr "Створана група карыстальнікаў %s"
+
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
-msgstr "Адбылася памылка пры стварэнні групы карыстачоў %s"
-
-#: kallithea/controllers/admin/user_groups.py:201
+msgstr "Памылка пры стварэнні групы карыстальнікаў %s"
+
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
-msgstr "Група карыстачоў %s абноўлена"
-
-#: kallithea/controllers/admin/user_groups.py:224
+msgstr "Група карыстальнікаў %s абноўленая"
+
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
-msgstr "Адбылася памылка пры абнаўленні групы карыстачоў %s"
-
-#: kallithea/controllers/admin/user_groups.py:242
+msgstr "Памылка пры абнаўленні групы карыстальнікаў %s"
+
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
-msgstr "Група карыстачоў паспяхова выдалена"
-
-#: kallithea/controllers/admin/user_groups.py:247
+msgstr "Група карыстальнікаў паспяхова выдаленая"
+
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
-msgstr "Адбылася памылка пры выдаленні групы карыстачоў"
-
-#: kallithea/controllers/admin/user_groups.py:314
+msgstr "Памылка пры выдаленні групы карыстальнікаў"
+
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
-msgstr "Мэтавая група не можа быць такі ж"
-
-#: kallithea/controllers/admin/user_groups.py:320
+msgstr "Мэтавая група не можа быць той жа самай"
+
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
-msgstr "Прывілеі групы карыстачоў абноўлены"
-
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+msgstr "Прывілеі групы карыстальнікаў абноўленыя"
+
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
-msgstr "Абноўлены прывілеі"
-
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+msgstr "Абноўленыя прывілеі"
+
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
-msgstr "Адбылася памылка пры захаванні прывілеяў"
-
-#: kallithea/controllers/admin/users.py:134
+msgstr "Памылка пры захаванні прывілеяў"
+
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
-msgstr "Карыстач %s створаны"
-
-#: kallithea/controllers/admin/users.py:149
+msgstr "Карыстальнік %s створаны"
+
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
-msgstr "Адбылася памылка пры стварэнні карыстача %s"
-
-#: kallithea/controllers/admin/users.py:182
+msgstr "Памылка пры стварэнні карыстальніка %s"
+
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
-msgstr "Карыстач паспяхова абноўлены"
-
-#: kallithea/controllers/admin/users.py:218
+msgstr "Карыстальнік паспяхова абноўлены"
+
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
-msgstr "Карыстач паспяхова выдалены"
-
-#: kallithea/controllers/admin/users.py:223
+msgstr "Карыстальнік паспяхова выдалены"
+
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
-msgstr "Адбылася памылка пры выдаленні карыстача"
-
-#: kallithea/controllers/admin/users.py:236
+msgstr "Памылка пры выдаленні карыстальніка"
+
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
-msgstr "Дададзены IP %s у белы спіс карыстача"
-
-#: kallithea/controllers/admin/users.py:469
+msgstr "Дададзены IP %s у белы спіс карыстальніка"
+
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Адбылася памылка пры захаванні IP"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
-msgstr "Выдалены IP %s з белага спісу карыстача"
-
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IP %s заблакаваны"
-
-#: kallithea/lib/auth.py:757
+msgstr "Выдалены IP %s з белага спісу карыстальніка"
+
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+"Вы павінны быць зарэгістраваным карыстальнікам, каб выканаць гэта дзеянне"
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr "Старонка даступная толькі аўтарызаваным карыстальнікам"
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr "Няслушны API-ключ"
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr "Вы павінны быць зарэгістраваным карыстачом, каб выканаць гэта дзеянне"
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr "Старонка даступная толькі аўтарызаваным карыстачам"
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr "Рэпазітар не знойдзены на файлавай сістэме"
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr "Набор змен для %s %s не знойдзены ў %s"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Двайковы файл"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Набор змены апынуўся занадта вялікімі і быў падрэзаны, выкарыстоўвайце "
 "меню параўнання для паказу выніку параўнання"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Змен не выяўлена"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
-msgstr "Выдалена галінка: %s"
-
-#: kallithea/lib/helpers.py:612
+msgstr "Выдаленая галіна: %s"
+
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Створаны тэг: %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Набор змен %s не знойдзены"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Паказаць адрозненні разам %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr "Параўнанне"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "і"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "на %s больш"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "версіі"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr "Імя форка %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull-запыт %s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[выдалены] рэпазітар"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[створаны] рэпазітар"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[створаны] рэпазітар як форк"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[форкнуты] рэпазітар"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[абноўлены] рэпазітар"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "[загружаны] архіў з рэпазітара"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[выдалены] рэпазітар"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
-msgstr "[створаны] карыстач"
-
-#: kallithea/lib/helpers.py:775
+msgstr "[створаны] карыстальнік"
+
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
-msgstr "[абноўлены] карыстач"
-
-#: kallithea/lib/helpers.py:777
+msgstr "[абноўлены] карыстальнік"
+
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
-msgstr "[створана] група карыстачоў"
-
-#: kallithea/lib/helpers.py:779
+msgstr "[створана] група карыстальнікаў"
+
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
-msgstr "[абноўлена] група карыстачоў"
-
-#: kallithea/lib/helpers.py:781
+msgstr "[абноўлена] група карыстальнікаў"
+
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[каментар] да рэвізіі ў рэпазітары"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
-msgstr "[пракаменціравана] у запыце на занясенне змен для"
-
-#: kallithea/lib/helpers.py:785
+msgstr "[каментар] у pull-запыце для"
+
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
-msgstr "[зачынены] Pull-запыт для"
-
-#: kallithea/lib/helpers.py:787
+msgstr "[зачынены] pull-запыт для"
+
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[адпраўлена] у"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
-msgstr "[занесены змены з дапамогай Kallithea] у рэпазітары"
-
-#: kallithea/lib/helpers.py:791
+msgstr "[каміт праз Kallithea] у рэпазітары"
+
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
-msgstr "[занесены змены з выдаленага рэпазітара] у рэпазітар"
-
-#: kallithea/lib/helpers.py:793
+msgstr "[занесены з аддаленага рэпазітара] у рэпазітар"
+
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
-msgstr "[занесены змены] з"
-
-#: kallithea/lib/helpers.py:795
+msgstr "[занесены] з"
+
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[дададзены ў назіранні] рэпазітар"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[выдалены з назірання] рэпазітар"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " і на %s больш"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Няма файлаў"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "новы файл"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "зменены"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "выдалены"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "пераназваны"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1247,42 +1275,42 @@
 "repositories"
 msgstr ""
 "Рэпазітар %s адсутнічае ў базе дадзеных; магчыма, ён быў створаны ці "
-"пераназваны з файлавай сістэмы. Калі ласка, перазапусціце прыкладанне для"
-" сканавання рэпазітароў"
-
-#: kallithea/lib/utils2.py:415
+"пераназваны з файлавай сістэмы. Калі ласка, перазапусціце прыкладанне для "
+"сканавання рэпазітароў"
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d год"
-msgstr[1] "%d гадоў"
-msgstr[2] "%d гады"
-
-#: kallithea/lib/utils2.py:416
+msgstr[1] "%d гады"
+msgstr[2] "%d гадоў"
+
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d месяц"
-msgstr[1] "%d месяца"
+msgstr[1] "%d месяцы"
 msgstr[2] "%d месяцаў"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d дзень"
-msgstr[1] "%d дня"
+msgstr[1] "%d дні"
 msgstr[2] "%d дзён"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d гадзіна"
-msgstr[1] "%d гадзін"
-msgstr[2] "%d гадзіны"
-
-#: kallithea/lib/utils2.py:419
+msgstr[1] "%d гадзіны"
+msgstr[2] "%d гадзін"
+
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1290,7 +1318,7 @@
 msgstr[1] "%d хвіліны"
 msgstr[2] "%d хвілін"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1298,846 +1326,452 @@
 msgstr[1] "%d секунды"
 msgstr[2] "%d секунд"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "у %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s назад"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "у %s і %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s і %s назад"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
-msgstr "прама цяпер"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Рэпазітар - няма доступу"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Рэпазітар - доступ на чытанне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Рэпазітар - доступ на запіс"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Рэпазітар - адміністраванне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Група Рэпазітароў - няма доступу"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Група Рэпазітароў - доступ на чытанне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Група Рэпазітароў - доступ на запіс"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Група Рэпазітароў - адміністраванне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Адміністратар Kallithea"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Стварэнне рэпазітароў адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Стварэнне рэпазітароў уключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Магчымасць ствараць форк рэпазітара адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Магчымасць ствараць форк рэпазітара ўключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Рэгістрацыя адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Рэгістрацыя новага карыстача ў Kallithea з ручной актывацыяй"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Рэгістрацыя новага карыстача ў Kallithea з аўтаматычнай актывацыяй"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Не прагледжана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Ухвалена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Адхілена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "На разглядзе"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "верхні ўзровень"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Група Рэпазітароў - няма доступу"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Група рэпазітароў - доступ на чытанне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Група рэпазітароў - доступ на запіс"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Група рэпазітароў - адміністраванне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Група карыстачоў - няма доступу"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Група карыстачоў - доступ на чытанне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Група карыстачоў - доступ на запіс"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Група карыстачоў - адміністраванне"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Стварэнне груп рэпазітароў адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Стварэнне груп рэпазітароў уключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Стварэнне груп карыстачоў адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Стварэнне груп карыстачоў уключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Рэгістрацыя адключана"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Рэгістрацыя карыстача з ручной актывацыяй уліковага запісу"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Рэгістрацыя карыстача з аўтаматычнай актывацыяй"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+msgstr "цяпер"
+
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "на радку %s"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Згадванне]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "верхні ўзровень"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Адміністратар Kallithea"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "Несанкцыянаваны доступ да рэсурсу"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1684
+msgstr "Толькі адміністратары могуць ствараць групы репазітароў"
+
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
-msgstr ""
-
-#: kallithea/model/db.py:1686
+msgstr "Неадміністратары могуць ствараць групы репазітароў"
+
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1687
+msgstr "Толькі адміністратары могуць ствараць групы карыстальнікаў"
+
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
-msgstr ""
-
-#: kallithea/model/db.py:1689
+msgstr "Неадміністратары могуць ствараць групы карыстальнікаў"
+
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1690
+msgstr "Толькі адміністратары могуць ствараць рэпазітары верхняга ўзроўню"
+
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1695
+msgstr "Неадміністратары могуць ствараць рэпазітары верхняга ўзроўню"
+
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
-msgstr ""
-
-#: kallithea/model/db.py:1696
+msgstr "Месцазнаходжанне рэпазітароў"
+
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Рэгістрацыя адключаная"
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr "Рэгістрацыя карыстальніка з ручной актывацыяй уліковага запісу"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr "Рэгістрацыя карыстальніка з аўтаматычнай актывацыяй"
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr "Не прагледжана"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr "На разглядзе"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "Ухвалена"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Ухвалена"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Калі ласка, увядзіце лагін"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Увядзіце значэнне даўжынёй не меней %(min)i знакаў"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Калі ласка, увядзіце пароль"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Увядзіце не меней %(min)i знакаў"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr "Імя не можа ўтрымліваць толькі лічбы"
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s пакінуў каментар да набору змен %(age)s"
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s адправіў паведамленне %(age)s"
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s згадаў вас %(age)s"
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s зарэгістраваўся ў Kallithea %(age)s"
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s адкрыў новы pull-запыт %(age)s"
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s пакінуў каментар да pull-запыту %(age)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s пакінуў каментар да набору змен %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s адправіў паведамленне %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s згадаў вас %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s зарэгістраваўся ў Kallithea %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s адкрыў новы pull-запыт %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s пакінуў каментар да pull-запыту %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, fuzzy, python-format
+#| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "[пракаментавана] у запыце на занясенне змен для"
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
-msgstr "Новы карыстач \"%(new_username)s\" зарэгістраваны"
-
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "[пракаменціравана] у запыце на занясенне змен для"
-
-#: kallithea/model/notification.py:322
+msgstr "Новы карыстальнік \"%(new_username)s\" зарэгістраваны"
+
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "Зачынены"
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s"
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: %(pr_title)s"
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "Памылка пры стварэнні pull-запыту: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "Пацвердзіце выдаленне гэтага pull-request'а"
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "Адсутныя рэвізіі адносна папярэдняга pull-запыту:"
+
+#: kallithea/model/pull_request.py:348
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Новыя рэвізіі на %s %s адносна папярэдняга pull-запыту:"
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, fuzzy, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"Гэты pull-запыт заснаваны на іншай рэвізіі %s, просты diff немагчымы."
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "No changes found on %s %s since previous version."
+msgid "No changes found on %s %s since previous iteration."
+msgstr "Няма змен на %s %s адносна папярэдняй версіі."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "апошняя версія"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
-msgstr "Рэгістрацыя новага карыстача"
-
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-"Вы не можаце выдаліць карыстача, паколькі гэта крытычна для працы ўсяго "
-"прыкладання"
-
-#: kallithea/model/user.py:261
+msgstr "Рэгістрацыя новага карыстальніка"
+
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+"Вы не можаце выдаліць карыстальніка, паколькі гэта крытычна для працы "
+"ўсёй праграмы"
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
-"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s рэпазітароў і таму не"
-" можа быць выдалены. Зменіце ўладальніка ці выдаліце гэтыя рэпазітары: %s"
-
-#: kallithea/model/user.py:266
+"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s рэпазітароў і "
+"таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтыя "
+"рэпазітары: %s"
+
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп рэпазітароў і "
-"таму не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя "
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп рэпазітароў "
+"і таму не можа быць выдалены. Змяніце ўладальніка ці выдаліце гэтая "
 "групы: %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
-"Карыстач \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп карыстачоў і "
-"таму не можа быць выдалены. Зменіце ўладальніка ці выдаліце дадзеныя "
-"групы: %s"
-
-#: kallithea/model/user.py:360
+"Карыстальнік \"%s\" усё яшчэ з'яўляецца ўладальнікам %s груп "
+"карыстальнікаў і таму не можа быць выдалены. Змяніце ўладальніка ці "
+"выдаліце гэтыя групы: %s"
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "Спасылка скіду пароля"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
-msgstr "Спасылка скіду пароля"
-
-#: kallithea/model/user.py:409
+msgstr "Паведамленне пра скіданне пароля"
+
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "Значэнне не можа быць пустым спісам"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
-msgstr "Карыстач з імем \"%(username)s\" ужо існуе"
-
-#: kallithea/model/validators.py:97
+msgstr "Карыстальнік з імем \"%(username)s\" ужо існуе"
+
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "Імя \"%(username)s\" недапушчальнае"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-"Імя карыстача можа ўтрымоўваць толькі літары, лічбы, знакі падкрэслення, "
-"кропкі і працяжнік; а гэтак жа павінна пачынацца з літары, лічбы або са "
-"знака падкрэслення"
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+"Імя карыстальніка можа ўтрымоўваць толькі літары, лічбы, знакі "
+"падкрэслення, кропкі і працяжнік; а гэтак жа павінна пачынацца з літары, "
+"лічбы або са знака падкрэслення"
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr "Імя \"%(username)s\" недапушчальнае"
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr "Імя \"%(username)s\" недапушчальна"
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
-msgstr "Няслушнае імя групы карыстачоў"
-
-#: kallithea/model/validators.py:153
+msgstr "Няслушнае імя групы карыстальнікаў"
+
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
-msgstr "Група карыстачоў \"%(usergroup)s\" ужо існуе"
-
-#: kallithea/model/validators.py:155
+msgstr "Група карыстальнікаў \"%(usergroup)s\" ужо існуе"
+
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
-"імя групы карыстачоў можа ўтрымоўваць толькі літары, лічбы, знакі "
+"імя групы карыстальнікаў можа ўтрымоўваць толькі літары, лічбы, знакі "
 "падкрэслення, кропкі і працяжнік; а гэтак жа павінна пачынацца з літары "
 "ці лічбы"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Немагчыма выкарыстоўваць гэту групу як бацькоўскую"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "Група \"%(group_name)s\" ужо існуе"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Рэпазітар з  імем \"%(group_name)s\" ужо існуе"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Недапушчальныя знакі (не ascii) у паролі"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr "Няслушна зададзены стары пароль"
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Паролі не супадаюць"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr "Няслушнае імя ці пароль"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "Несупадзенне токенаў"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "Імя рэпазітара %(repo)s забароненае"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Рэпазітар %(repo)s ужо існуе"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "Рэпазітар \"%(repo)s\" ужо існуе ў групе \"%(group)s\""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Група рэпазітароў \"%(repo)s\" ужо існуе"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr "Няслушны URL рэпазітара"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
@@ -2145,440 +1779,331 @@
 "Няслушны URL рэпазітара. Ён мусіць быць карэктным URL http, https, ssh, "
 "svn+http ці svn+https"
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "Тып форка будзе супадаць з бацькоўскім"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
-msgstr "У вас недастаткова мае рацыю для стварэння рэпазітароў у гэтай групе"
-
-#: kallithea/model/validators.py:506
+msgstr "У вас недастаткова правоў для стварэння рэпазітароў у гэтай групе"
+
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
-msgstr "недастаткова мае рацыю для стварэння рэпазітара ў каранёвым каталогу"
-
-#: kallithea/model/validators.py:556
+msgstr "недастаткова правоў для стварэння рэпазітара ў каранёвым каталогу"
+
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr "У Вас недастаткова прывілеяў для стварэння групы ў гэтым месцы"
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
-msgstr "Дадзенае імя карыстача ці групы карыстачоў недапушчальна"
-
-#: kallithea/model/validators.py:690
+msgstr "Дадзенае імя карыстальніка ці групы карыстальнікаў недапушчальна"
+
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "Гэты шлях хібны"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
-msgstr "Гэты E-mail ужо заняты"
-
-#: kallithea/model/validators.py:725
+msgstr "Гэты e-mail ужо ўжываецца"
+
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
-msgstr "\"%(email)s\" не знойдзены."
-
-#: kallithea/model/validators.py:762
+msgstr "Email-адрас \"%(email)s\" не знойдзены"
+
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 "Для ўваходу па LDAP павінна быць паказана значэнне атрыбута CN - гэта "
-"эквівалент імя карыстача"
-
-#: kallithea/model/validators.py:774
+"эквівалент імя карыстальніка"
+
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Калі ласка, увядзіце існы IPv4 ці IPv6 адрас"
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 msgstr ""
 "Значэнне маскі падсеткі павінна быць у межах ад 0 да 32 (%(bits)r - "
 "няслушна)"
 
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
-"Ключавое імя можа толькі складацца з літар, знака падкрэслення, працяжнік"
-" ці лікаў"
-
-#: kallithea/model/validators.py:822
+"Ключавое імя можа толькі складацца з літар, знака падкрэслення, працяжнік "
+"ці лікаў"
+
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "Файла няма ў каталогу"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr "Пра праграму"
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Панэль кіравання"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "фільтр..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "рэпазітары"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "Дадаць рэпазітар"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "Дадаць групу рэпазітароў"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
-msgstr "Вы маеце адміністратарскія правы на гэту групу і можаце рэдагаваць яе"
-
-#: kallithea/templates/index_base.html:32
+msgstr ""
+"Вы маеце адміністратарскія правы на гэту групу і можаце рэдагаваць яе"
+
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "Змяніць групу рэпазітароў"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "Імя групы"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Рэпазітар"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Апісанне"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Імя"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Апошняя змена"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Стан"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Уладальнік"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "Па ўзрастанні"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "Па змяншэнні"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Рэпазітары не знойдзены."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Памылка дадзеных."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Загрузка..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Увайсці"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Увайсці ў %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
-msgstr "Імя карыстача"
-
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+msgstr "Імя карыстальніка"
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Пароль"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Запомніць"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
-msgstr "Забыліся пароль?"
-
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+msgstr "Забыліся на пароль?"
+
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Няма акаўнта?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Увайсці"
 
 #: kallithea/templates/password_reset.html:5
 msgid "Password Reset"
-msgstr "Скід пароля"
-
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+msgstr "Скінуць пароль"
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
-msgstr "Забыліся пароль для %s?"
-
-#: kallithea/templates/password_reset.html:14
+msgstr "Забыліся на пароль для %s?"
+
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr "Скінуць Ваш пароль"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "Паштовы адрас"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr "Капча"
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 msgid "Send Password Reset Email"
-msgstr "Паслаць спасылку скіду пароля"
-
-#: kallithea/templates/password_reset.html:47
+msgstr "Паслаць спасылку для скідання пароля"
+
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
-msgstr "Спасылка для скіду пароля была адасланая на адпаведны e-mail."
-
-#: kallithea/templates/password_reset_confirmation.html:19
+msgstr ""
+"Спасылка для скіду пароля будзе адпраўленая на адпаведны email-адрас, "
+"калі ён зарэгістраваны ў сістэме."
+
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr "Новы пароль"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr "Пацвердзіце новы пароль"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Рэгістрацыя"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
-msgstr "Рэгістра на %s"
-
-#: kallithea/templates/register.html:42
+msgstr "Рэгістрацыя на %s"
+
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
-msgstr "Паўторыце пароль"
-
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+msgstr "Паўтарыце пароль"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Імя"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Прозвішча"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
-msgstr "Калі ласка, пачакайце, пакуль адміністратар пацвердзіць Вашу рэгістрацыю."
-
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Галінкі яшчэ не створаны"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Зачыненыя галінкі"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Пазнакі адсутнічаюць"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Закладак яшчэ няма"
+msgstr ""
+"Калі ласка, пачакайце, пакуль адміністратар пацвердзіць Вашу рэгістрацыю."
 
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
-msgstr "Часопіс адміністратара"
+msgstr "Журнал адміністратара"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
-msgstr "Фільтр часопіса..."
+msgstr "Фільтр журнала..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr "Адфільтраваць"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2587,30 +2112,16 @@
 msgstr[2] "%s запісы"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Дзеянне"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Рэпазітар"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Дата"
 
@@ -2618,166 +2129,166 @@
 msgid "From IP"
 msgstr "З IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
-msgstr "Дзеянні яшчэ не вырабляліся"
+msgstr "Няма інфармацыі"
 
 #: kallithea/templates/admin/auth/auth_settings.html:5
 msgid "Authentication Settings"
 msgstr "Налады аўтэнтыфікацыі"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "Аўтэнтыфікацыя"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
-msgstr "Убудовы аўтэнтыфікацыі"
-
-#: kallithea/templates/admin/auth/auth_settings.html:31
+msgstr "Плагіны аўтэнтыфікацыі"
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
-msgstr "Уключаныя ўбудовы"
-
-#: kallithea/templates/admin/auth/auth_settings.html:33
+msgstr "Уключаныя плагіны"
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
-"Спіс убудоў, падзеленых коскі. Kallithea будзе спрабаваць аўтэнтыфікаваць"
-" карыстача ў парадку ўказання ўбудоў"
-
-#: kallithea/templates/admin/auth/auth_settings.html:34
+"Спіс плагінаў, падзеленых коскамі. Kallithea будзе спрабаваць "
+"аўтэнтыфікаваць карыстальніка ў парадку ўказання плагінаў"
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
-msgstr "Даступныя ўбудаваныя ўбудовы"
-
-#: kallithea/templates/admin/auth/auth_settings.html:51
+msgstr "Даступныя ўбудаваныя плагіны"
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
-msgstr "Убудова"
+msgstr "Плагін"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Захаваць"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
-msgstr "Значэнні па змаўчанні"
-
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+msgstr "Значэнні па змоўчанні"
+
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Тып"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Прыватны рэпазітар"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
 msgstr "Прыватныя рэпазітары бачныя толькі іх удзельнікам."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Уключыць статыстыку"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Уключыць акно статыстыкі на старонцы «Агульныя звесткі»."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Уключыць спампоўку"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Уключыць меню спампоўкі на старонцы «Агульныя звесткі»."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Уключыць блакаванне"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
-msgstr "Уключыць аўтаблакоўку для рэпазітара."
+msgstr "Уключыць аўтаблакаванне для рэпазітара."
 
 #: kallithea/templates/admin/gists/edit.html:5
 #: kallithea/templates/admin/gists/edit.html:18
 msgid "Edit Gist"
-msgstr "Праўка gist-запісы"
-
-#: kallithea/templates/admin/gists/edit.html:36
+msgstr "Правіць gist-запіс"
+
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Апісанне..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Мінае"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr "Ніколі"
 
@@ -2786,7 +2297,8 @@
 msgstr "Абнавіць"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Адмена"
 
@@ -2794,13 +2306,13 @@
 #: 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
@@ -2809,65 +2321,65 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
-msgstr "Стварыць новую gist-запіс"
-
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+msgstr "Стварыць новы gist-запіс"
+
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
-msgstr "Створана"
-
-#: kallithea/templates/admin/gists/index.html:74
+msgstr "Створаны"
+
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Gist-запісы адсутнічаюць"
 
 #: kallithea/templates/admin/gists/new.html:5
 #: kallithea/templates/admin/gists/new.html:18
 msgid "New Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+msgstr "Новы gist-запіс"
+
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
-msgstr "Скід"
+msgstr "Скінуць"
 
 #: kallithea/templates/admin/gists/show.html:5
 #: kallithea/templates/admin/gists/show.html:9
@@ -2875,290 +2387,268 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Выдаліць"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
-msgstr "Пацвердзіце выдаленне гэтай gist-запісы"
-
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+msgstr "Пацвердзіце выдаленне гэтага gist-запісу"
+
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Рэдагаваць"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr "Паказаць толькі тэкст"
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "створана"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "Паказаць толькі тэкст"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Мой Акаўнт"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "Профіль"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:38
+msgstr "E-mail адрэсы"
+
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr "API-ключы"
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+msgstr "Мае рэпазітары"
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
+#, fuzzy
 msgid "Watched Repositories"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:41
+msgstr "Стварыць рэпазітары"
+
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
-#, fuzzy
 msgid "Show Permissions"
-msgstr "Скапіяваць прывілеі"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+msgstr "Паказаць прывілеі"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr "Убудаваны"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
-msgstr "Пацвердзіце скіданне гэтага API-ключа: %s"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
-#, fuzzy
+msgstr "Пацвердзіць скіданне гэтага API-ключа: %s"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
-msgstr "мінае"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+msgstr "Ануляваны"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "Пацвердзіце выдаленне гэтага API-ключа: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr "Выдаліць"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
-msgstr "Няма дадатковых API-ключоў."
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+msgstr "Дадатковыя API-ключы не пазначаныя"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr "Новы API-ключ"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Дадаць"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "Асноўны"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Пацвердзіце выдаленне e-mail: %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr "Дадатковыя адрасы e-mail не пазначаны."
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
-msgstr "Новы E-mail"
+msgstr "Новы e-mail"
 
 #: kallithea/templates/admin/my_account/my_account_password.html:1
 msgid "Change Your Account Password"
 msgstr "Змена пароля"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
-msgstr "Дзейны пароль"
-
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+msgstr "Цяперашні пароль"
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Новы пароль"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr "Пацвердзіце новы пароль"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
-msgstr "Зменіце аватар праз сайт"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr "Цяперашні IP-адрас"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
+msgstr "Змяніць аватар можна праз"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "Выкарыстоўваецца"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
-msgstr "Аватары адключаны"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr "Не паказаны email. Калі ласка, абновіце ваш email."
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr "Цяперашні IP"
+msgstr "Аватары адключаныя"
 
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "Рэпазітары, дзе Вы — уладальнік"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Запісы не знойдзены."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Імя"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr "Рэпазітары, за якімі Вы назіраеце"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Мае апавяшчэнні"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Усё"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Каментары"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Pull-запыты"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr "Адзначыць усё як прачытаныя"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Апавяшчэнняў няма"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr "Паказаць апавяшчэнне"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Апавяшчэнні"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr "Стандартныя прывілеі"
 
@@ -3172,179 +2662,186 @@
 msgid "IP Whitelist"
 msgstr "Белы спіс IP"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Ананімны доступ"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "Ананімны доступ"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
-"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнага рэпазітара. "
-"Улічыце, што раней усталяваныя прывілеі па змаўчанні будуць скінуты"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнага рэпазітара. "
+"Улічыце, што раней усталяваныя прывілеі па змоўчанні будуць скінутыя"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Імпартаваць існы рэпазітар?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "Група рэпазітароў"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "Група рэпазітароў"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
-"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнай групы "
-"рэпазітароў. Улічыце, што раней усталяваныя прывілеі па змаўчанні для "
-"груп рэпазітароў будуць скінуты"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнай групы "
+"рэпазітароў. Улічыце, што раней усталяваныя прывілеі па змоўчанні для "
+"груп рэпазітароў будуць скінутыя"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "Імпартаваць існы рэпазітар?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
-msgstr "Група карыстачоў"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
-#, fuzzy
+msgstr "Група карыстальнікаў"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
-"Абраныя прывілеі будуць усталяваны па змаўчанні для кожнай групы "
-"карыстачоў. Улічыце, што раней усталяваныя прывілеі па змаўчанні для груп"
-" карыстачоў будуць скінуты"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+"Выбраныя прывілеі будуць усталяваныя па змоўчанні для кожнай групы "
+"карыстальнікаў. Улічыце, што раней усталяваныя прывілеі па змоўчанні для "
+"груп карыстальнікаў будуць скінутыя"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "Стварэнне рэпазітара"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
-msgstr "Стварэнне груп карыстачоў"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+msgstr "Стварэнне груп карыстальнікаў"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Стварэнне форка рэпазітара"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Рэгістрацыя"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "Актывацыя іншага ўліковага запісу"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
-#, fuzzy, python-format
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Пацвердзіце выдаленне IP %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr "Дазволены любыя IP-адрасы."
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Новы IP-адрас"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "Групы рэпазітароў"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Імя групы"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Бацькоўская група"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3353,30 +2850,29 @@
 msgid "%s Repository Group Settings"
 msgstr "Налады групы рэпазітароў %s"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr "Дадаць падгрупу"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Налады"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Дадаткова"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Прывілеі"
@@ -3401,12 +2897,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Створана"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3418,127 +2914,140 @@
 msgid "Delete this repository group"
 msgstr "Выдаліць гэту групу рэпазітароў"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "disabled"
+msgid "Visible"
+msgstr "адключана"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "Add Repository"
+msgid "Add repos"
+msgstr "Дадаць рэпазітар"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#, fuzzy
+#| msgid "Add user group"
+msgid "Add/Edit groups"
+msgstr "Дадаць групу карыстальнікаў"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
-#, fuzzy
 msgid "User/User Group"
-msgstr "групы карыстальнікаў"
+msgstr "Карыстальнік/група карыстальнікаў"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
-#, fuzzy
 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
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
-#, fuzzy
 msgid "Revoke"
-msgstr "адклікаць"
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+msgstr "Адклікаць"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "Дадаць новы"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 #, fuzzy
 msgid "Apply to children"
 msgstr "дастасаваць да даччыным"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr "Абедзьве"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
 msgstr ""
 "Уключыць аўтаблакоўку для групы. Гэта опцыя будзе ўжыта да ўсіх даччыных "
 "груп і рэпазітарам"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr "Выдаліць гэту групу"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
-msgstr "Пацвердзіце выдаленне гэтай групы карыстачоў"
+msgstr "Пацвердзіце выдаленне гэтай групы карыстальнікаў"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "Дахаты"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "з"
+#, fuzzy, python-format
+#| msgid "Repository Group: %s"
+msgid "Repository group %s"
+msgstr "Група рэпазітароў: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr "Адміністраванне груп рэпазітароў"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr "Лік рэпазітароў верхняга ўзроўня"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "[створаны] рэпазітар"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 "Кароткае і асэнсаванае. Для разгорнутага апісання выкарыстоўвайце файл "
 "README."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "Апцыянальна абраць групу, у якую змясціць дадзены рэпазітар."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Тып стваранага рэпазітара."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Рэвізія для выгрузкі"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3571,22 +3080,22 @@
 msgid "%s Repository Settings"
 msgstr "Налады рэпазітара %s"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr "Дадатковыя палі"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr "Кэшы"
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Выдалены"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Статыстыка"
 
@@ -3595,65 +3104,63 @@
 msgstr "Бацькоўская група"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr "Набор"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
-msgstr "Уручную зрабіць гэты рэпазітар форкам абранага са спісу."
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+msgstr "Уручную зрабіць гэты рэпазітар форкам выбранага са спісу."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
-msgstr "Доступ да публічнага часопіса"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+msgstr "Доступ да публічнага журналу"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
-msgstr "Выдаліць з агульнадаступнага часопіса"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+msgstr "Выдаліць з агульнадаступнага журналу"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
-msgstr "Дадаць у публічны часопіс"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+msgstr "Дадаць у публічны журнал"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 "Усе выконваемыя з гэтым рэпазітаром дзеянні будуць адлюстроўвацца ў "
-"публічным часопісе."
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+"публічным журнал."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr "Уключыць блакаванне"
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr "Пацвердзіце разблакаванне рэпазітара."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr "Разблакаваць рэпазітар"
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr "Пацвердзіце здыманне блакавання з рэпазітара."
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
-msgstr "Разблакаваць рэпазітар"
+#, python-format
+msgid "Locked by %s on %s"
+msgstr "Заблакавана %s %s"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Confirm to lock repository."
 msgstr "Пацвердзіце блакаванне рэпазітара."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "Заблакаваць рэпазітар"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Рэпазітар не заблакаваны"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3661,33 +3168,33 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Пацвердзіце выдаленне гэтага рэпазітара: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Выдаліць гэты рэпазітар"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] "Дадзены рэпазітар мае %s копію"
 msgstr[1] "Дадзены рэпазітар мае %s копіі"
-msgstr[2] "Дадзены рэпазітар мае %s дзід"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+msgstr[2] "Дадзены рэпазітар мае %s копій"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
-msgstr "Адлучыць fork'і"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+msgstr "Адлучыць форкі"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
-msgstr "Выдаліць fork'і"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+msgstr "Выдаліць форкі"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3698,110 +3205,107 @@
 msgid "Invalidate Repository Cache"
 msgstr "Скінуць кэш рэпазітара"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr "Пацвердзіце скід кэша."
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
-msgstr "Ручны скід кэша рэпазітара. Пры першым доступе кэш адновіцца."
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgstr "Ручное скіданне кэша рэпазітара. Пры першым доступе кэш адновіцца."
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 msgid "List of Cached Values"
 msgstr "Спіс кэшаваных значэнняў"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "Прэфікс"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Ключ"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Актыўны"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Пацвердзіце выдаленне гэтага поля: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "Ключ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "Імя поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "Увядзіце кароткае імя поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "Апісанне поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "Увядзіце апісанне поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
-msgstr "Дадатковыя палі адключаны."
-
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+msgstr "Дадатковыя палі адключаныя."
+
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
+msgid "Private Repository"
+msgstr "Прыватны рэпазітар"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
 #, fuzzy
-msgid "Private Repository"
-msgstr "прыватны рэпазітар"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
-#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "[форкнуты] рэпазітар"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
-msgstr "Рэпазітар %s створаны"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
-#, fuzzy
+msgstr "URL аддаленага рэпазітара"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
-msgstr "[занесены змены з выдаленага рэпазітара] у рэпазітар"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-#, fuzzy
-msgid "Confirm to pull changes from remote repository."
-msgstr "Пацвердзіце спампоўку змен."
+msgstr "Занесці змены з аддаленага рэпазітара"
 
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr "Пацвердзіце спампоўку змен з аддаленага рэпазітара."
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
-#, fuzzy
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
-msgstr "прыватны рэпазітар"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+msgstr "Пастаяннае ШВ рэпазітара"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr "Што гэта?"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3811,37 +3315,39 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
-#, fuzzy
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
-msgstr "[створаны] рэпазітар"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
-#, fuzzy
+msgstr "Аддалены рэпазітар"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
-msgstr "Рэпазітар"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+msgstr "URL рэпазітара"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
-"Рэвізія па змаўчанні, з якой будзе вырабляцца выгрузка файлаў пры "
-"спампоўцы"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+"Рэвізія па змоўчанні, з якой будзе рабіцца выгрузка файлаў пры спампоўцы"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Змяніць уладальніка рэпазітара."
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
 msgid "Processed commits"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 msgid "Processed progress"
 msgstr ""
 
@@ -3851,13 +3357,13 @@
 
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:10
 msgid "Confirm to remove current statistics."
-msgstr "Пацвердзіце скід статыстыкі."
+msgstr "Пацвердзіце скіданне статыстыкі."
 
 #: kallithea/templates/admin/repos/repos.html:5
 msgid "Repositories Administration"
 msgstr "Адміністраванне рэпазітароў"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr "Стан"
 
@@ -3878,7 +3384,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Хукі"
 
@@ -3890,280 +3396,270 @@
 msgid "System Info"
 msgstr "Інфармацыя пра сістэму"
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr "Адаслаць тэставае паведамленне на"
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Адправіць"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "Брэндынг сайта"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "Прывітанне для HTTP-аўтэнтыфікацыі"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr "Захаваць налады"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr "Карыстальніцкія хукі"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr "Карыстацкія хуки"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "Не атрымалася выдаліць хук"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Rescan option"
+msgid "Rescan options"
 msgstr "Опцыі перасканіравання"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:11
-#, fuzzy
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
-msgstr "Пошук па рэпазітарах"
-
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+msgstr "Сцерці запісы пра выдаленыя рэпазітары"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "Скінуць кэш для ўсіх рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr "Скінуць кэш для ўсіх рэпазітароў."
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
+#: kallithea/templates/admin/settings/settings_mapping.html:28
 msgid ""
 "Verify if Kallithea's Git hooks are installed for each repository. "
 "Current hooks will be updated to the latest version."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:28
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "Опцыі стварэння індэксу"
 
-#: kallithea/templates/admin/settings/settings_search.html:12
+#: kallithea/templates/admin/settings/settings_search.html:9
 msgid "Build from scratch"
 msgstr "Зборка з нуля"
 
-#: kallithea/templates/admin/settings/settings_search.html:15
+#: kallithea/templates/admin/settings/settings_search.html:12
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Перабудаваць індэкс"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr "Праверка абнаўленняў..."
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:4
-#, fuzzy
-msgid "Check for updates"
-msgstr "праверыць наяўнасць абнаўленняў"
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
+msgstr "Версія Kallithea"
 
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
-msgstr "Платформа"
+msgid "Check for updates"
+msgstr "Праверыць наяўнасць абнаўленняў"
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
-msgstr "Версія Git"
+msgid "Kallithea configuration file"
+msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:10
-msgid "Upgrade info endpoint"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr "Платформа"
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr "Версія Git"
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Upgrade info endpoint"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr "Праверка абнаўленняў..."
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Вэб"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "Запытваць SSL для аперацый з VCS"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "Паказваць памер рэпазітара пасля адпраўкі"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
-msgstr "Лагіраваць карыстацкія каманды адпраўкі"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+msgstr "Лагаваць карыстацкія каманды адпраўкі"
+
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
-msgstr "Лагіраваць карыстацкія каманды атрымання"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+msgstr "Лагаваць карыстацкія каманды атрымання"
+
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "Абнаўляць рэпазітар пасля адпраўкі (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr "Пашырэнні Mercurial"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Уключыць падтрымку вялікіх файлаў"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Уключыць падтрымку hgsubversion"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 msgid "Location of repositories"
 msgstr "Месцазнаходжанне рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
 msgstr ""
 "Націсніце для разблакавання. Змены набудуць моц пасля перазагрузкі "
 "Kallithea."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Галоўнае"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "Выкарыстоўваць дадатковыя палі ў рэпазітарах"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr "Дазваляе захоўваць дадатковыя палі ў рэпазітарах."
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Адлюстроўваць версію Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
 msgstr "Паказвае або хавае нумар версіі Kallithea ў ніжняй частцы старонкі."
 
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr "Выкарыстоўваць Gravatars у Kallithea"
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4180,95 +3676,103 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr "URL для кланавання"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "Элементы панэлі"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "Памер рэпазітара"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+#, fuzzy
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
 msgstr ""
 "Колькасць элементаў, што паказваюцца на галоўнай старонцы панэлі "
 "кіравання перад паказам нумарацыі старонак."
 
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Абразкі"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "Паказваць абразкі публічных рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "Паказваць абразкі прыватных рэпазітароў"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 msgid "Show public/private icons next to repository names."
 msgstr "Паказваць абразкі публічных рэпазітароў."
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
-#, fuzzy
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
-msgstr "Метатэгіраванне"
-
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+msgstr "Метатэгаванне"
+
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
-msgstr "Дадаць групу карыстачоў"
+msgstr "Дадаць групу карыстальнікаў"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr "Групы карыстальнікаў"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr "Дадаць групу карыстальнікаў"
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr "Кароткае дадатковае апісанне для гэтай групы карыстальнікаў."
 
@@ -4278,9 +3782,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit.html:33
-#, fuzzy
 msgid "Show Members"
-msgstr "удзельнікі"
+msgstr "Паказаць удзельнікаў"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
 #, python-format
@@ -4288,54 +3791,54 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Удзельнікі"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
-msgstr "Пацвердзіце выдаленне наступнай групы карыстачоў: %s"
+msgstr "Пацвердзіце выдаленне наступнай групы карыстальнікаў: %s"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
 msgid "Delete this user group"
 msgstr "Выдаліць гэтую групу карыстальнікаў"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Няма ўдзельнікаў"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Абраныя ўдзельнікі групы"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Даступныя ўдзельнікі"
 
 #: kallithea/templates/admin/user_groups/user_groups.html:5
 msgid "User Groups Administration"
-msgstr "Адміністраванне груп карыстачоў"
+msgstr "Адміністраванне груп карыстальнікаў"
 
 #: kallithea/templates/admin/users/user_add.html:5
 msgid "Add user"
-msgstr "Дадаць карыстача"
+msgstr "Дадаць карыстальніка"
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
-msgstr "Карыстачы"
+msgstr "Карыстальнікі"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
-msgstr "Дадаць карыстача"
-
-#: kallithea/templates/admin/users/user_add.html:50
+msgstr "Дадаць карыстальніка"
+
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "Пацверджанне пароля"
 
@@ -4354,12 +3857,12 @@
 msgstr "Карыстальнік: %s"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr "Апошні ўваход"
 
@@ -4368,411 +3871,395 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
-msgstr "Пацвердзіце выдаленне карыстача %s"
+msgstr "Пацвердзіце выдаленне карыстальніка %s"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:23
 msgid "Delete this user"
 msgstr "Выдаліць гэтага карыстальніка"
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr "Не паказаны email. Калі ласка, абнавіце email карыстальніка."
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "Пацвердзіце новы пароль"
 
 #: kallithea/templates/admin/users/users.html:5
 msgid "Users Administration"
-msgstr "Адміністраванне карыстачоў"
-
-#: kallithea/templates/admin/users/users.html:56
+msgstr "Адміністраванне карыстальнікаў"
+
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "Асобнік сервера: %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr "Падтрымка"
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Рэпазітар Mercurial"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Git рэпазітар"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr "Стварыць форк"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Агульныя звесткі"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "Гісторыя змен"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Файлы"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "Пераключыцца на"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
-msgid "Options"
-msgstr "Опцыі"
-
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
-msgid "Compare Fork"
-msgstr "Параўнаць форк"
-
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
-msgid "Compare"
-msgstr "Параўнаць"
-
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
-#: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
-msgid "Search"
-msgstr "Пошук"
-
-#: kallithea/templates/base/base.html:163
-msgid "Unlock"
-msgstr "Разблакаваць"
-
-#: kallithea/templates/base/base.html:165
-msgid "Lock"
-msgstr "Заблакаваць"
-
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr "Назіраць"
-
-#: kallithea/templates/base/base.html:174
-msgid "Unfollow"
-msgstr "Не назіраць"
-
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr "Форк"
-
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
-msgid "Create Pull Request"
-msgstr "Стварыць Pull запыт"
-
-#: kallithea/templates/base/base.html:183
+#: kallithea/templates/base/base.html:135
 #, python-format
 msgid "Show Pull Requests for %s"
 msgstr "Паказаць pull-запыты для %s"
 
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Pull-запыты"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
+msgid "Options"
+msgstr "Опцыі"
+
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
+msgid "Compare Fork"
+msgstr "Параўнаць форк"
+
+#: kallithea/templates/base/base.html:158
+msgid "Compare"
+msgstr "Параўнаць"
+
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
+#: kallithea/templates/search/search.html:14
+#: kallithea/templates/search/search.html:67
+msgid "Search"
+msgstr "Пошук"
+
+#: kallithea/templates/base/base.html:164
+msgid "Unlock"
+msgstr "Разблакаваць"
+
+#: kallithea/templates/base/base.html:166
+msgid "Lock"
+msgstr "Заблакаваць"
+
+#: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr "Назіраць"
+
+#: kallithea/templates/base/base.html:175
+msgid "Unfollow"
+msgstr "Не назіраць"
+
+#: kallithea/templates/base/base.html:178
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr "Форк"
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
+msgid "Create Pull Request"
+msgstr "Стварыць pull-запыт"
+
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "Пераключыцца на"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr "Супадзенняў не знойдзена"
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "Паказаць апошнюю актыўнасць"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
-msgstr "Агульнадаступны часопіс"
-
-#: kallithea/templates/base/base.html:233
+msgstr "Агульнадаступны журнал"
+
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "Паказаць публічныя запісы"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gist"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr "Усе публічныя Gist-запісы"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr "Мае публічныя Gist-запісы"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr "Мае прыватныя Gist-запісы"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "Пошук па рэпазітарах"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
-msgstr "Мае Pull-запыты"
-
-#: kallithea/templates/base/base.html:289
+msgstr "Мае pull-запыты"
+
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr "Не аўтарызаваны"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr "Аўтарызавацца"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr "Забыліся пароль?"
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
+msgstr "Забыліся на пароль?"
+
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "Няма акаўнта?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
-msgstr "Выйсце"
-
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr "Супадзенняў не знойдзена"
+msgstr "Выйсці"
 
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
-msgstr "Гарачыя клавішы"
+msgid "Parent rev."
+msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
-#, fuzzy
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
-msgstr "Значэнні па змаўчанні"
-
-#: kallithea/templates/base/default_perms_box.html:19
+msgstr "Ужываць значэнні па змоўчанні"
+
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "Стварыць рэпазітары"
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr "Опцыя дазваляе карыстальніку ствараць рэпазітары"
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr "Опцыя дазваляе карыстачу ствараць рэпазітары"
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
-msgstr "Ствараць групы карыстачоў"
-
-#: kallithea/templates/base/default_perms_box.html:45
+msgstr "Ствараць групы карыстальнікаў"
+
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
-msgstr "Опцыя дазваляе карыстачу ствараць групы карыстачоў"
-
-#: kallithea/templates/base/default_perms_box.html:52
+msgstr "Опцыя дазваляе карыстальніку ствараць групы карыстальнікаў"
+
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "Ствараць fork ад рэпазітароў"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
-"Абярыце гэту опцыю каб дазволіць дадзенаму карыстачу ствараць fork'і "
+"Абярыце гэту опцыю каб дазволіць дадзенаму карыстальніку ствараць форкі "
 "рэпазітароў"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "Паказаць"
 
 #: kallithea/templates/base/perms_summary.html:22
 msgid "No permissions defined yet"
-msgstr "Прывілеі яшчэ не прызначаны"
+msgstr "Прывілеі яшчэ не прызначаныя"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Прывілей"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Змяніць прывілеі"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr "Адпраўка..."
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "Уключыць спампоўку"
+
+#: kallithea/templates/base/root.html:31
 msgid "Add Another Comment"
 msgstr "Дадаць яшчэ адзін каментар"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "Адмяніць назіранне за рэпазітаром"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "Назіраць за рэпазітаром"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "Група"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "удзельнікі"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "Загрузка..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "загрузка..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "Пошук усечаны"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "Няма супадзенняў"
 
-#: kallithea/templates/base/root.html:31
-#, fuzzy
+#: kallithea/templates/base/root.html:39
 msgid "Open New Pull Request from {0}"
-msgstr "Каментар у pull-запыце"
-
-#: kallithea/templates/base/root.html:32
+msgstr "Стварыць новы pull-запыт з {0}"
+
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
-msgstr ""
-
-#: kallithea/templates/base/root.html:33
-#, fuzzy
+msgstr "Стварыць новы pull-запыт для {0} &rarr; {1}"
+
+#: kallithea/templates/base/root.html:41
 msgid "Show Selected Changesets {0} &rarr; {1}"
-msgstr "Паказаць абраныя наборы змен: __S &rarr; __E"
-
-#: kallithea/templates/base/root.html:34
+msgstr "Паказаць выбраныя наборы змен: {0} &rarr; {1}"
+
+#: kallithea/templates/base/root.html:42
 msgid "Selection Link"
 msgstr "Спасылка выбару"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 msgid "Collapse Diff"
 msgstr "Згарнуць параўнанне"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 msgid "Expand Diff"
-msgstr "Расчыніць параўнанне"
-
-#: kallithea/templates/base/root.html:37
+msgstr "Разгарнуць параўнанне"
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr "Няма рэвізій"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "Не атрымалася адклікаць прывілеі"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "Пацвердзіце выдаленне прывілею для {0}: {1} ?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr "уключана"
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr "адключана"
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Выбраць набор змен"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
-msgstr "Абраць набор змен"
-
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "Закладкі %s"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Аўтар"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Рэвізія"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "Галінкі %s"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+msgstr "Выбраць набор змен"
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "Па ўзрастанні"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "Па змяншэнні"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Запісы не знойдзеныя."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Памылка дадзеных."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Загрузка..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "Логі змен %s"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4780,81 +4267,31 @@
 msgstr[1] "Паказаны %d з %d рэвізій"
 msgstr[2] "Паказаны %d з %d рэвізій"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "Ачысціць выбар"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr "Перайсці на верхавіну рэпазітара"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "Параўнаць fork з %s"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "Параўнаць форк з бацькоўскім рэпазітаром (%s)"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
-msgstr "Адфільтраваць галінку:"
-
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, fuzzy, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-"Статут набору змен: %s?\n"
-"Клікніце, каб перайсці да адпаведнага pull-request'у #%s"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "Статут набору змен: %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "Каментары адсутнічаюць"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "Закладка %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "Пазнака %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Галінка %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+msgstr "Адфільтраваць галіну:"
+
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Змен яшчэ няма"
 
@@ -4870,7 +4307,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "Дададзена"
 
@@ -4884,220 +4321,228 @@
 msgid "Affected %s files"
 msgstr "Закранае %s файлаў"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Узрост"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Спасылкі"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Дадаць ці загрузіць файлы праз Kallithea"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr "Дадаць новы файл"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Адправіць новы рэпазітар"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "Існы рэпазітар?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"Статус набору змен: %s ад %s\n"
+"Клікніце, каб адкрыць адпаведны pull-запыт %s"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Статус набору змен: %s ад %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr "Разгарнуць паведамленне"
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d каментар"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "Закладка %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "Тэг %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Галіна %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "%s Змены"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
-msgstr "Статут змен"
-
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+msgstr "Статус змен"
+
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "Адлюстраваць у фармаце diff"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
-msgstr "Ужыць рознаснае выпраўленне (Patch diff)"
-
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+msgstr "Patch diff"
+
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
-msgstr "Запампаваць diff"
-
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+msgstr "Спампаваць diff"
+
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "звесці"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr "Перанесена з:"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
-#, fuzzy
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
-msgstr "Створана"
-
-#: kallithea/templates/changeset/changeset.html:149
-#, fuzzy
+msgstr "Заменена:"
+
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
-msgstr "Створана"
-
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+msgstr "Замяняе:"
+
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] "%s файл зменены"
-msgstr[1] "%s файлаў зменена"
-msgstr[2] "%s файла зменена"
-
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+msgstr[1] "%s файлы зменена"
+msgstr[2] "%s файлаў зменена"
+
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "%s файл зменены: %s даданне, %s выдаленне"
-msgstr[1] "%s файла зменена: %s даданні, %s выдаленні"
+msgstr[1] "%s файлы зменена: %s даданні, %s выдаленні"
 msgstr[2] "%s файлаў зменена: %s даданняў, %s выдаленняў"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr "Паказаць поўны diff"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
+#: kallithea/templates/changeset/changeset_file_comment.html:20
 #, fuzzy
-msgid "No revisions"
-msgstr "няма рэвізій"
+#| msgid "Comment"
+msgid "comment"
+msgstr "Каментаваць"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
-#, fuzzy
 msgid "on pull request"
-msgstr "Каментар у pull-запыце"
+msgstr "у pull-запыце"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:22
 msgid "No title"
 msgstr "Няма загалоўка"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:24
-#, fuzzy
 msgid "on this changeset"
-msgstr "Няма змен"
+msgstr "на змене"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
 msgid "Delete comment?"
 msgstr "Выдаліць каментар?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#, fuzzy
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
-msgstr "Апошнія змены"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "Каментар да радка {1}."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr "Парсінг каментароў выкананы з выкарыстаннем сінтаксісу %s з падтрымкай %s."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-"Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку пэўнаму "
-"карыстачу"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Папярэдні прагляд каментара"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr "Ужыванне..."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+msgstr "Змена статусу"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+"Выкарыстоўвайце @імя_карыстальніка ў тэксце, каб адправіць паведамленне "
+"пэўнаму карыстальніку"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "Змяніць статус рэвізіі"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr "Без змен"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr "Завяршыць pull-запыт"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr "Закрыць"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Каментаваць"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Прадпрагляд"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "Вам неабходна аўтарызавацца, каб пакідаць каментары."
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
-msgstr "Аўтарызавацца цяпер"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+msgstr "Аўтарызавацца зараз"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "Схаваць"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d каментар"
-msgstr[1] "%d каментара"
+msgstr[1] "%d каментары"
 msgstr[2] "%d каментароў"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
-msgstr[0] "%d да радка"
-msgstr[1] "%d да радкоў"
-msgstr[2] "%d да радкоў"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
@@ -5105,104 +4550,110 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-#, fuzzy
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-"Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку пэўнаму "
-"карыстачу"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr "Змяніць статус рэвізіі"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr "Без змен"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr "Зачыніць"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "%s Змены"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "Статус набору змен: %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "Закранутыя файлы"
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "файл выдалены"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Без змен"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr "Выдалены"
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr "Пераназваны"
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Невядомая рэвізія %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Безназоўны"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "файл выдалены"
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr "Паказаць поўны diff для гэтага файла"
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr "Паказаць поўны diff для гэтага файла"
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr "Паказаць каментары ў радках"
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr "Выдалены"
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr "Пераназваны"
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "Няма змен"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "Продак"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+#, fuzzy
+#| msgid "Common ancestor"
+msgid "Merge Ancestor"
+msgstr "Агульны продак"
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr "Паказаць merge diff"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr "Агульны продак"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr "адстае на"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "%s змен"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr "ад"
 
@@ -5213,130 +4664,188 @@
 msgstr "%s Параўнаць"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr "Параўнаць рэвізіі"
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] "Паказаць %s commit"
-msgstr[1] "Паказаць %s commit'а"
+msgstr[1] "Паказаць %s commit'ы"
 msgstr[2] "Паказаць %s commit'аў"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "Паказаць поўны diff"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Публічны рэпазітар"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "Змен яшчэ не было"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Падпісацца на стужку RSS %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Падпісацца на стужку Atom %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr "Ствараецца"
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s пакінуў каментар да набору змен %(age)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Comment from %s on %s changeset %s"
+msgid "Comment on Changeset \"%s\""
 msgstr "Каментар ад %s да набору змен %s %s"
 
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "Стан набору змен зменены на"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr "Гэта аўтаматычнае апавяшчэнне. Не адказвайце на гэтае паведамленне."
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset flow"
+msgid "Changeset on"
+msgstr "Струмень змен"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Галіна"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Змена статусу"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr "Гэты pull-запыт быў зачынены і не можа быць абноўлены."
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Добры дзень, %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
-#, fuzzy
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
-msgstr "Мы атрымалі запыт на стварэнне новага пароля для вашага акаўнта."
-
-#: kallithea/templates/email_templates/password_reset.html:7
+msgstr "Мы атрымалі запыт на скіданне пароля для вашага акаўнта."
+
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention on Pull Request %s \"%s\" by %s"
 msgstr "%s згадаў Вас у каментары да pull-запыту %s \"%s\""
 
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s requested your review of %s pull request \"%s\""
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
 msgstr "%s запытаў рэцэнзаванне pull-запыту %s \"%s\""
 
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "Pull-запыт %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
 #: kallithea/templates/email_templates/pull_request_comment.html:4
-#, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr "Каментар зачыніў pull-запыт са статусам"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr "Каментар пакінуты са статусам"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "Падрабязней пра карыстача"
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%s згадаў Вас у каментары да pull-запыту %s \"%s\""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Pull-запыт %s ад %s#%s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "[каментар] у pull-запыце для"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "Імя групы"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5354,7 +4863,7 @@
 msgstr "Параўнанне файла %s"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "%s Файлы"
@@ -5364,72 +4873,73 @@
 msgid "%s Files Add"
 msgstr "%s Файлаў дададзена"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr "Дадаць новы файл"
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Размяшчэнне"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr "Увядзіце імя файла..."
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "ці"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr "Адаслаць файл"
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr "Стварыць новы файл"
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr "Рэжым новага файла"
+msgid "New file type"
+msgstr "Тып новага файла"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
-msgstr "Ужыць змены"
-
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "Папярэдняя рэвізія"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "Наступная рэвізія"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "Адсочваць дадзеную галінку"
-
-#: kallithea/templates/files/files_browser.html:44
+msgstr "Захаваць змены"
+
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "Загружаецца спіс файлаў..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Памер"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "Апошняя версія"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr "Апошняя змена"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr "Аўтар апошняй рэвізіі"
 
@@ -5439,7 +4949,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr "Выдаліць файл"
 
@@ -5452,24 +4962,20 @@
 msgid "Edit file"
 msgstr "Рэдагаваць файл"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "Зыходны код"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "Рэдагаванне файла"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5478,51 +4984,60 @@
 msgstr[1] "%s аўтараў"
 msgstr[2] "%s аўтара"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr "Паказаць ўсю гісторыю"
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr "Паказаць ўсю гісторыю"
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr "Паказаць аўтараў"
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
-msgstr ""
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "Выдаленая галіна: %s"
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr "Рэдагаванне бінарных файлаў забароненае"
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr "Рэдагаванне файлаў дазволенае толькі ў HEAD-рэвізіі дадзенай галіны"
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr "Рэдагаванне бінарных файлаў забаронена"
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr "Рэдагаванне файлаў дазволена толькі ў HEAD-рэвізіі дадзенай галінкі"
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "Бінарны файл (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr "Файл занадта вялікі для адлюстравання."
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr "Паказаць поўныя анатацыі."
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Файл занадта вялікай для адлюстравання"
+msgid "Show as raw."
+msgstr "Паказаць сырым."
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5542,12 +5057,12 @@
 msgstr "%s Назіральнікі"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "Назіральнікі"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "Назіраць за рэпазітаром"
 
@@ -5556,37 +5071,37 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "Імя форка"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 "Рэвізія па змаўчанні, з якой будзе вырабляцца выгрузка файлаў пры "
 "спампоўцы."
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Прыватны"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Скапіяваць прывілеі"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "Скапіяваць прывілеі з форкнутага рэпазітара"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "Абнаўляць пасля кланавання"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "Спампоўваць зыходнікі пасля стварэння клона"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5596,40 +5111,40 @@
 msgstr "Форкі %s"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "Адгалінаванні"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "Форкнута"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
-msgstr "Форкі яшчэ не створаны"
-
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr "Стужка часопіса ATOM"
+msgstr "Форкі яшчэ не створаныя"
 
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr "Стужка часопіса Atom"
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "Стужка часопіса RSS"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "Мае рэпазітары"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "Запісы адсутнічаюць"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
-msgstr "Агульная стужка часопіса ATOM"
-
-#: kallithea/templates/journal/public_journal.html:14
+msgstr "Агульная стужка часопіса Atom"
+
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "Агульная стужка часопіса RSS"
 
@@ -5638,31 +5153,36 @@
 msgid "New Pull Request"
 msgstr "Новы pull-запыт"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Загаловак"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "Напісаць кароткае пісанне па гэтым запыце"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "Струмень змен"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "Першапачатковы рэпазітар"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Рэвізія"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "Рэпазітар прызначэння"
 
@@ -5671,9 +5191,12 @@
 msgstr "Запісы отсуствуют"
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:14
-#, fuzzy
 msgid "Vote"
-msgstr "адклікаць"
+msgstr "Галасаваць"
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Узрост"
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
@@ -5698,7 +5221,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "Зачынена"
 
@@ -5711,164 +5234,163 @@
 msgstr "Пацвердзіце выдаленне гэтага pull-request'а"
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
-msgstr "Пацвердзіце выдаленне гэтага pull-request'а"
+msgstr "Пацвердзіце выдаленне гэтага pull-запыту з %s каментарамі"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:6
-#, fuzzy, python-format
+#, python-format
 msgid "%s Pull Request %s"
-msgstr "%s Pull-запыт #%s"
+msgstr "%s зull-запыт %s"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:10
-#, fuzzy, python-format
+#, python-format
 msgid "Pull request %s from %s#%s"
-msgstr "Pull-запыты №%s ад %s#%s"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+msgstr "Pull-запыт %s ад %s#%s"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "Яшчэ не разгледжаны"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d рэцэнзент"
-msgstr[1] "%d рэцэнзента"
-msgstr[2] "%d рэцэнзентаў"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "Запыт на занясенне змен быў разгледжаны ўсімі рэцэнзентамі"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr "Няма рэцэнзентаў"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr "на"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr "Цэль"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "Прыняць змены"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr "Абнавіць"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "Рэгістрацыя"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr "Рэцэнзенты pull-запытаў"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr "Захаваць змены"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr "Адмяніць змены"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "reviewer"
+msgid "Reviewers"
+msgstr "рэцэнзент"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "Выдаліць рэцэнзента"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "Патэнцыйныя рэцэнзенты"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr "Захаваць змены"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr "Адмяніць змены"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr "Агульны продак"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
-msgstr "%s Запыты на занясенне змен"
+msgstr "%s pull-запыты"
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
-#, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
+#, python-format
 msgid "Pull Requests from '%s'"
-msgstr "Pull-запыты ад %s"
+msgstr "Pull-запыты ад '%s'"
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:13
 #, python-format
 msgid "Pull Requests to '%s'"
-msgstr "Pull-запыты для %s"
-
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+msgstr "Pull-запыты да '%s'"
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr "Стварыць новы pull-запыт"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Паказаць pull-запыты да %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Паказаць pull-запыты ад %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "Рэцэнзенты pull-запытаў"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5882,31 +5404,31 @@
 msgid "Search in All Repositories"
 msgstr "Пошук па ўсіх рэпазітарах"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "Фраза для пошуку"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "Крытэр пошуку"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
-msgstr "Змесціва файлаў"
-
-#: kallithea/templates/search/search.html:66
+msgstr "Змест файлаў"
+
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "Паведамленні камітаў"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "Імя файла"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
-msgstr "Недастаткова мае рацыю"
+msgstr "Недастаткова правоў"
 
 #: kallithea/templates/summary/statistics.html:4
 #, python-format
@@ -5914,80 +5436,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
-msgstr "ATOM стужка рэпазітара %s"
+msgstr "Atom стужка рэпазітара %s"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "RSS стужка рэпазітара %s"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "Уключана"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "Атрыманая статыстыка: "
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "файлы"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Паказаць яшчэ"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "commit'ы"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "файлы дададзены"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "файлы зменены"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "файлы выдалены"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "файл выдалены"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "файл зменены"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "файл выдалены"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "%s агульныя звесткі"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "Рэпазітар заблакаваны %s"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "Рэпазітар разблакаваны"
 
@@ -5995,86 +5517,294 @@
 msgid "Fork of"
 msgstr "Форк ад"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Кланаваць з"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr "Спасылка для кланавання"
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr "Паводле ID"
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
-msgstr "Паказаць па імі"
-
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr "Паказаць па ID"
-
-#: kallithea/templates/summary/summary.html:92
+msgstr "Паводле імя"
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "Папулярныя файлы"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
-msgstr "Запампаваць"
-
-#: kallithea/templates/summary/summary.html:112
+msgstr "Спампаваць"
+
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "Спамповак яшчэ няма"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
-msgstr "Спампоўка адключана ў гэтым рэпазітары"
-
-#: kallithea/templates/summary/summary.html:120
+msgstr "Спампоўванне адключанае ў гэтым рэпазітары"
+
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
-msgstr "Запампаваць у zip"
-
-#: kallithea/templates/summary/summary.html:125
+msgstr "Спампаваць у zip"
+
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "Адзначце для спампоўкі архіва з даччынымі рэпазітарамі"
 
-#: kallithea/templates/summary/summary.html:125
-#, fuzzy
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
-msgstr "з даччынымі рэпазітарамі"
-
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "Памер рэпазітара"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+msgstr "З даччынымі рэпазітарамі"
+
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "Стужка навін"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Апошнія змены"
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr "Хуткі старт"
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr "Хуткі старт"
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr "Дадаць ці загрузіць файлы праз Kallithea"
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr "Адправіць новы рэпазітар"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "Існы рэпазітар?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
-msgstr "Запампаваць %s як %s"
-
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s Пазнак"
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr "Параўнаць тэгі"
+msgstr "Спампаваць %s як %s"
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "Індэксы адсутнічаюць. Калі ласка, запусціце індэксатар Whoosh"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP %s заблакаваны"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s пакінуў каментар да набору змен %(age)s"
+
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s адправіў паведамленне %(age)s"
+
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s згадаў вас %(age)s"
+
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s зарэгістраваўся ў Kallithea %(age)s"
+
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s адкрыў новы pull-запыт %(age)s"
+
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s пакінуў каментар да pull-запыту %(age)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s пакінуў каментар да набору змен %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s адправіў паведамленне %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s згадаў вас %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s зарэгістраваўся ў Kallithea %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s адкрыў новы pull-запыт %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s пакінуў каментар да pull-запыту %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "Група рэпазітароў"
+
+#~ msgid "My Notifications"
+#~ msgstr "Мае апавяшчэнні"
+
+#~ msgid "All"
+#~ msgstr "Усе"
+
+#~ msgid "Comments"
+#~ msgstr "Каментары"
+
+#~ msgid "Mark All Read"
+#~ msgstr "Адзначыць усе як прачытаныя"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "Адзначыць усе як прачытаныя"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Апавяшчэнняў няма"
+
+#~ msgid "Show Notification"
+#~ msgstr "Паказаць апавяшчэнне"
+
+#~ msgid "Notifications"
+#~ msgstr "Апавяшчэнні"
+
+#~ msgid "Home"
+#~ msgstr "Дамоў"
+
+#~ msgid "with"
+#~ msgstr "з"
+
+#~ msgid "members"
+#~ msgstr "удзельнікі"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "Каментары адсутнічаюць"
+
+#~ msgid "Author"
+#~ msgstr "Аўтар"
+
+#~ msgid "Refs"
+#~ msgstr "Спасылкі"
+
+#~ msgid "Commenting on line."
+#~ msgstr "Каментар да радка."
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "у pull-запыце"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "Дата"
+
+#~ msgid "Previous revision"
+#~ msgstr "Папярэдняя рэвізія"
+
+#~ msgid "Next revision"
+#~ msgstr "Наступная рэвізія"
+
+#~ msgid "Follow current branch"
+#~ msgstr "Адсочваць дадзеную галіну"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "Яшчэ не разгледжаны"
+
+#~ msgid "%d reviewer"
+#~ msgid_plural "%d reviewers"
+#~ msgstr[0] "%d рэцэнзент"
+#~ msgstr[1] "%d рэцэнзенты"
+#~ msgstr[2] "%d рэцэнзентаў"
+
+#~ msgid "Pull request was reviewed by all reviewers"
+#~ msgstr "Запыт на занясенне змен быў разгледжаны ўсімі рэцэнзентамі"
+
+#~ msgid "There are no reviewers"
+#~ msgstr "Няма рэцэнзентаў"
+
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Рэцэнзенты pull-запытаў"
+
+#~ msgid "Dashboard"
+#~ msgstr "Панэль кіравання"
+
+#~ msgid "Group Name"
+#~ msgstr "Імя групы"
+
+#~ msgid "Remember me"
+#~ msgstr "Запомніць"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Змяніць аватар можна праз"
+
+#~ msgid "Using"
+#~ msgstr "Выкарыстоўваецца"
+
+#~ msgid "Missing email, please update your user email address."
+#~ msgstr "Няма email адрэсы, калі ласка, абнавіце ваш email."
+
+#~ msgid "Rescan option"
+#~ msgstr "Опцыі перасканіравання"
+
+#~ msgid "Web"
+#~ msgstr "Вэб"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "Запытваць SSL для аперацый з VCS"
+
+#~ msgid "Use Gravatars in Kallithea"
+#~ msgstr "Выкарыстоўваць Gravatars у Kallithea"
+
+#~ msgid "Dashboard items"
+#~ msgstr "Элементы панэлі"
+
+#~ msgid ""
+#~ "Number of items displayed in the main page dashboard before pagination "
+#~ "is shown."
+#~ msgstr ""
+#~ "Колькасць элементаў, што паказваюцца на галоўнай старонцы панэлі "
+#~ "кіравання перад паказам нумарацыі старонак."
+
+#~ msgid "quick filter..."
+#~ msgstr "фільтр..."
+
+#~ msgid "Missing email, please update this user email address."
+#~ msgstr "Не паказаны email. Калі ласка, абнавіце email карыстальніка."
+
+#~ msgid "Keyboard shortcuts"
+#~ msgstr "Гарачыя клавішы"
+
+#~ msgid "Forgot password ?"
+#~ msgstr "Забыліся на пароль?"
+
+#~ msgid "Ancestor"
+#~ msgstr "Продак"
+
+#~ msgid "Comment from %s on %s changeset %s"
+#~ msgstr "Каментар ад %s да набору змен %s %s"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "Статус набору змен зменены на"
+
+#~ msgid "This is an automatic notification. Don't reply to this mail."
+#~ msgstr ""
+#~ "Гэта аўтаматычнае апавяшчэнне. Не адказвайце на гэтае паведамленне."
+
+#~ msgid "%s mentioned you on %s pull request \"%s\""
+#~ msgstr "%s згадаў Вас у каментары да pull-запыту %s \"%s\""
+
+#~ msgid "%s requested your review of %s pull request \"%s\""
+#~ msgstr "%s запытаў рэцэнзаванне pull-запыту %s \"%s\""
+
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "Каментар зачыніў pull-запыт са статусам"
+
+#~ msgid "The comment was made with status"
+#~ msgstr "Каментар пакінуты са статусам"
+
+#~ msgid "View this user here"
+#~ msgstr "Падрабязней пра карыстальніка"
+
+#~ msgid "Repository Size"
+#~ msgstr "Памер рэпазітара"
 
 #~ msgid "No comments."
 #~ msgstr "Няма каментароў."
@@ -6101,19 +5831,16 @@
 #~ msgstr "Рэпазітар %s"
 
 #~ msgid "You can't edit this user"
-#~ msgstr "Вы не можаце рэдагаваць дадзенага карыстача"
+#~ msgstr "Вы не можаце рэдагаваць дадзенага карыстальніка"
 
 #~ msgid "No Files"
 #~ msgstr "Файлаў няма"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "Імя \"%(username)s\" адхілена"
 
 #~ msgid "invalid user name"
-#~ msgstr "няслушнае імя карыстача"
+#~ msgstr "няслушнае імя карыстальніка"
 
 #~ msgid "Your account is disabled"
 #~ msgstr "Ваш акаўнт выключаны"
@@ -6121,12 +5848,6 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "няслушны URL для кланавання"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr "Рэвізіі %(revs)s ужо ўключаны ў pull-request ці маюць усталяваны статус"
-
 #~ msgid "Defaults"
 #~ msgstr "Значэнні па змаўчанні"
 
@@ -6142,15 +5863,6 @@
 #~ msgid "reset"
 #~ msgstr "cкінуць"
 
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
 #~ msgid "delete"
 #~ msgstr "выдаліць"
 
@@ -6164,10 +5876,10 @@
 #~ msgstr "Перазапісаць існыя налады"
 
 #~ msgid "Default IP Whitelist for All Users"
-#~ msgstr "Белы спіс IP для ўсіх карыстачоў"
+#~ msgstr "Белы спіс IP для ўсіх карыстальнікаў"
 
 #~ msgid "Default User Permissions Overview"
-#~ msgstr "Агляд мае рацыю карыстачоў па змаўчанні"
+#~ msgstr "Агляд мае рацыю карыстальнікаў па змаўчанні"
 
 #~ msgid "none"
 #~ msgstr "нічога"
@@ -6181,12 +5893,6 @@
 #~ msgid "admin"
 #~ msgstr "адміністратар"
 
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
 #~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr "Апцыянальны URL, з якога патрабуецца скланаваць рэпазітар."
 
@@ -6196,9 +5902,6 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Атрымаць змены з выдаленага боку"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
 #~ msgid "Non-changeable id"
 #~ msgstr "Нязменлівы id"
 
@@ -6208,69 +5911,21 @@
 #~ msgid "new value"
 #~ msgstr "новае значэнне"
 
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
 #~ msgid "SMTP server"
 #~ msgstr "SMTP-сервер"
 
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
 #~ msgid "Destroy old data"
 #~ msgstr "Знішчыць усе дадзеныя"
 
 #~ msgid "Default permissions"
 #~ msgstr "Стандартныя прывілеі"
 
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
 #~ msgid "show"
 #~ msgstr "паказа́ць"
 
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "Змена статусу"
 
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
 #~ msgid "revision"
 #~ msgstr "рэвізія"
 
@@ -6289,9 +5944,6 @@
 #~ msgid "owner"
 #~ msgstr "уладальнік"
 
-#~ msgid "reviewer"
-#~ msgstr "рэцэнзент"
-
 #~ msgid "Your new password"
 #~ msgstr "Ваш новы пароль"
 
@@ -6305,11 +5957,216 @@
 #~ msgstr "Паказаць абраны набор змен: __S"
 
 #~ msgid "You can generate it by clicking following URL"
-#~ msgstr "Вы можаце нанова згенераваць яго, пяройдучы па наступнай спасылцы"
-
-#~ msgid "Please ignore this email if you did not request a new password ."
 #~ msgstr ""
+#~ "Вы можаце нанова згенераваць яго, пяройдучы па наступнай спасылцы"
 
 #~ msgid "Created by"
 #~ msgstr "Створана"
 
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Зачынены, заменены %s."
+
+#~ msgid "Closing."
+#~ msgstr "Зачынены."
+
+#~ msgid "Changeset not found"
+#~ msgstr "Набор змен не знойдзены"
+
+#~ msgid "Repository no access"
+#~ msgstr "Рэпазітар - няма доступу"
+
+#~ msgid "Repository read access"
+#~ msgstr "Рэпазітар - доступ на чытанне"
+
+#~ msgid "Repository write access"
+#~ msgstr "Рэпазітар - доступ на запіс"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Рэпазітар - адміністраванне"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Група Рэпазітароў - няма доступу"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Група Рэпазітароў - доступ на чытанне"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Група Рэпазітароў - доступ на запіс"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Група Рэпазітароў - адміністраванне"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Стварэнне рэпазітароў адключанае"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Стварэнне рэпазітароў уключанае"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Магчымасць ствараць форк рэпазітара адключаная"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Магчымасць ствараць форк рэпазітара ўключаная"
+
+#~ msgid "Register disabled"
+#~ msgstr "Рэгістрацыя адключаная"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Рэгістрацыя новага карыстальніка ў Kallithea з ручной актывацыяй"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr ""
+#~ "Рэгістрацыя новага карыстальніка ў Kallithea з аўтаматычнай актывацыяй"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "Не прагледжана"
+
+#~ msgid "Rejected"
+#~ msgstr "Адхілена"
+
+#~ msgid "Under Review"
+#~ msgstr "На разглядзе"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Група Рэпазітароў - няма доступу"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Група рэпазітароў - доступ на чытанне"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Група рэпазітароў - доступ на запіс"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Група рэпазітароў - адміністраванне"
+
+#~ msgid "User group no access"
+#~ msgstr "Група карыстальнікаў - няма доступу"
+
+#~ msgid "User group read access"
+#~ msgstr "Група карыстальнікаў - доступ на чытанне"
+
+#~ msgid "User group write access"
+#~ msgstr "Група карыстальнікаў - доступ на запіс"
+
+#~ msgid "User group admin access"
+#~ msgstr "Група карыстальнікаў - адміністраванне"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Стварэнне груп рэпазітароў адключанае"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Стварэнне груп рэпазітароў уключанае"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Стварэнне груп карыстальнікаў адключанае"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Стварэнне груп карыстальнікаў уключанае"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "Рэгістрацыя карыстальніка з ручной актывацыяй уліковага запісу"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "Рэгістрацыя карыстальніка з аўтаматычнай актывацыяй"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "%(user)s просіць вас разгледзець pull request %(pr_nice_id)s: "
+#~ "%(pr_title)s"
+
+#~ msgid "repositories"
+#~ msgstr "рэпазітары"
+
+#~ msgid "No repositories found."
+#~ msgstr "Рэпазітары не знойдзеныя."
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Галіны яшчэ не створаныя"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Пазнакі адсутнічаюць"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Закладак яшчэ няма"
+
+#~ msgid "enabled"
+#~ msgstr "уключана"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "Закладкі %s"
+
+#~ msgid "Compare Bookmarks"
+#~ msgstr "Параўнаць закладкі"
+
+#~ msgid "%s Branches"
+#~ msgstr "%s Галіны"
+
+#~ msgid "Compare Branches"
+#~ msgstr "Параўнаць галіны"
+
+#~ msgid "Editing file"
+#~ msgstr "Рэдагаванне файла"
+
+#~ msgid "Update"
+#~ msgstr "Абнавіць"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Захаваць абнаўленні як новы pull-запыт"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s Тэгаў"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Параўнаць тэгі"
+
+#~ msgid ""
+#~ "Changing status on a changeset associated with a closed pull request "
+#~ "is not allowed"
+#~ msgstr ""
+#~ "Нельга рэдагаваць статус змен, злучаных з зачыненымі pull-request'ами"
+
+#~ msgid "You have successfully registered into Kallithea"
+#~ msgstr "Рэгістрацыя ў Kallithea прайшла паспяхова"
+
+#~ msgid "This pull request can be updated with changes on %s:"
+#~ msgstr "Гэты pull-запыт можа быць абноўлены з %s:"
+
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "Пацвердзіце скід кэша."
+
+#~ msgid ""
+#~ "Changeset status: %s\n"
+#~ "Click to open associated pull request %s"
+#~ msgstr ""
+#~ "Статут набору змен: %s?\n"
+#~ "Клікніце, каб перайсці да адпаведнага pull-request'у #%s"
+
+#~ msgid "Commenting on line {1}."
+#~ msgstr "Каментар да радка {1}."
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr ""
+#~ "Парсінг каментароў выкананы з выкарыстаннем сінтаксісу %s з падтрымкай "
+#~ "%s."
+
+#~ msgid "Use @username inside this text to notify another user"
+#~ msgstr ""
+#~ "Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку "
+#~ "пэўнаму карыстачу"
+
+#~ msgid "Comment preview"
+#~ msgstr "Папярэдні прагляд каментара"
+
+#~ msgid "Preview"
+#~ msgstr "Прадпрагляд"
+
+#~ msgid "Use @username inside this text to notify another user."
+#~ msgstr ""
+#~ "Выкарыстоўвайце @імя_карыстача ў тэксце, каб адправіць абвестку "
+#~ "пэўнаму карыстачу"
+
+#~ msgid "New file mode"
+#~ msgstr "Рэжым новага файла"
+
+#~ msgid "File is too big to display"
+#~ msgstr "Файл занадта вялікай для адлюстравання"
--- a/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/cs/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,11 +7,11 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2015-11-12 08:51+0000\n"
 "Last-Translator: Michal Čihař <michal@cihar.com>\n"
-"Language-Team: Czech "
-"<https://hosted.weblate.org/projects/kallithea/stable/cs/>\n"
+"Language-Team: Czech <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/cs/>\n"
 "Language: cs\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -19,443 +19,457 @@
 "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
 "X-Generator: Weblate 2.5-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(zavřeno)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "Změny"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Úspěšně aktualizované heslo"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Taková revize neexistuje"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Prázdný repozitář"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "Neznámá revize %s"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nemáte oprávnění k zobrazení této stránky"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:55
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Změny na repozitáři %s"
 
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Klikněte pro přidání nového souboru"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Zatím nejsou žádné soubory. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Žádné změny"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Přidaný soubor přes Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Žádný obsah"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Stahování vypnuto"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Neznámá revize %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Změny"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Větve"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Tagy"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Skupiny"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Repozitáře"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Větev"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Záložka"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Špatná captcha"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr ""
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Úspěšně aktualizované heslo"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zavřené)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Záložky"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:363
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:352
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-msgid "Error occurred while creating pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
 #, python-format
 msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
-msgid "Pull request updated"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:529
-msgid "Successfully deleted pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:595
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:597
-msgid "This pull request has been closed and can not be updated."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Došlo k chybě při vyhledávání."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -467,149 +481,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 minut"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 minut"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 hodina"
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "1 hodina"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 den"
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr "1 den"
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 měsíc"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Došlo k chybě při vytváření gist"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Došlo k chybě při aktualizaci hesla uživatele"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Došlo k chybě při ukládání e-mailové adresy"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -617,621 +633,619 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "Error occurred during gist creation"
+msgid "An error occurred during creation of field: %r"
+msgstr "Došlo k chybě při vytváření gist"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr "Nic"
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr "Nic"
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 msgid "Repository has been locked"
 msgstr "Repozitář byl uzamčen"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "Repozitář byl odemčen"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid ""
-"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
-"missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Watched Repositories"
 msgid "Invalidated %s repositories"
 msgstr "Repozitáře"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "Změny"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1239,7 +1253,7 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1247,7 +1261,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1255,7 +1269,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1263,7 +1277,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1271,7 +1285,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1279,7 +1293,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1287,1120 +1301,613 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 #, fuzzy
-#| msgid "Non-admins can can fork repositories"
 msgid "Non-admins can fork repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
-#, fuzzy
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
-#, fuzzy
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "Prázdný repozitář"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2408,147 +1915,130 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
-#, fuzzy, python-format
+#: kallithea/templates/journal/journal.html:13
+#, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
 msgstr[0] ""
@@ -2556,30 +2046,16 @@
 msgstr[2] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2587,7 +2063,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2596,109 +2072,109 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "Repozitáře"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid ""
-"Private repositories are only visible to people explicitly added as "
-"collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2707,45 +2183,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2754,7 +2230,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2777,16 +2254,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2795,45 +2272,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2843,184 +2320,199 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "Repozitáře"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "Repozitáře"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -3028,61 +2520,60 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 #, fuzzy
 msgid "Repositories You Own"
 msgstr "Umístění repozitářů"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
@@ -3090,45 +2581,9 @@
 msgid "Repositories You are Watching"
 msgstr "Umístění repozitářů"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3142,168 +2597,174 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid ""
-"Note: This will also give all users API access to create repositories "
-"everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
-#, fuzzy, python-format
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3312,30 +2773,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Nastavení"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Pokročilé"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3360,12 +2820,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3377,16 +2837,34 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "Neznámá revize %s"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3394,103 +2872,96 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
-#, fuzzy
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr ""
+#, fuzzy, python-format
+#| msgid "Repository Defaults"
+msgid "Repository group %s"
+msgstr "Repozitáře"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3519,26 +2990,26 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3547,66 +3018,64 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 msgid "Confirm to unlock repository."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 #, fuzzy
 msgid "Unlock Repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, fuzzy
 msgid "Confirm to lock repository."
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 #, fuzzy
 msgid "Lock Repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3614,34 +3083,34 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 #, fuzzy
 msgid "Delete this Repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
-#, fuzzy, python-format
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3652,110 +3121,112 @@
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Go to tip of repository"
+msgid "Fork of repository"
+msgstr "Prázdný repozitář"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "Změny na repozitáři %s"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3765,35 +3236,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "Repozitáře"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3810,7 +3286,7 @@
 msgid "Repositories Administration"
 msgstr "Umístění repozitářů"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3831,7 +3307,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3843,278 +3319,268 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 #, fuzzy
 msgid "Save Settings"
 msgstr "Nastavení"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "Chyba při vytváření repozitáře %s"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4131,93 +3597,101 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repositories"
+msgid "Repository page size"
+msgstr "Repozitáře"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Nastavení"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4236,13 +3710,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4251,15 +3725,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4273,17 +3747,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4302,12 +3776,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4316,7 +3790,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4325,24 +3799,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4350,223 +3816,220 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr ""
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Repozitáře"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4575,150 +4038,142 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-#, fuzzy
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-#, fuzzy
-msgid "Open New Pull Request from {0}"
-msgstr "Změna stavu-> %s"
+msgid "Add Another Comment"
+msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
+#, fuzzy
+msgid "Open New Pull Request from {0}"
+msgstr "Změna stavu-> %s"
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "Neznámá revize %s"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4726,80 +4181,32 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "Prázdný repozitář"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4815,7 +4222,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4829,39 +4236,59 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Prázdný repozitář"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Změny"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, python-format
+msgid "%s comments"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4869,60 +4296,50 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -4930,9 +4347,9 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -4940,18 +4357,17 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-#, fuzzy
-msgid "No revisions"
-msgstr "Neznámá revize %s"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+msgid "comment"
+msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -4968,61 +4384,61 @@
 msgstr "Žádné změny"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#, fuzzy
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Změna stavu-> %s"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "Změny"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "Žádné změny"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Změna stavu-> %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(zavřeno)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
@@ -5030,15 +4446,15 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
@@ -5046,104 +4462,100 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "Změny"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "Žádné změny"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(zavřeno)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Neznámá revize %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
-#, fuzzy, python-format
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -5154,20 +4566,20 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -5175,107 +4587,159 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Set changeset status"
+msgid "Comment on Changeset \"%s\""
+msgstr "Změny"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changesets"
+msgid "Changeset on"
+msgstr "Změny"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Větev"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Změna stavu-> %s"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "Repozitář byl uzamčen"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "on pull request"
+msgid "Pull request"
+msgstr "Změna stavu-> %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "on pull request"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Změna stavu-> %s"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5294,7 +4758,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5304,73 +4768,74 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "Žádné změny"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5380,7 +4845,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5393,24 +4858,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5419,50 +4880,58 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5483,12 +4952,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5497,35 +4966,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5535,40 +5004,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5577,31 +5046,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5613,6 +5087,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5636,7 +5114,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5649,7 +5127,7 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
 msgstr ""
 
@@ -5663,109 +5141,96 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "Zatím nejsou žádné soubory. %s"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
-#, fuzzy
-msgid "Remove reviewer"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
-msgid "Type name of reviewer to add"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
-#, fuzzy
-msgid "Potential Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
-msgid "Click to add the repository owner as reviewer:"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
 msgid "Save Changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
 #, fuzzy
 msgid "Cancel Changes"
 msgstr "Žádné změny"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5773,7 +5238,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Open New Pull Request from {0}"
 msgid "Pull Requests from '%s'"
 msgstr "Změna stavu-> %s"
 
@@ -5782,35 +5246,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5824,29 +5292,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5856,80 +5324,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5937,113 +5405,91 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "Změna stavu-> %s"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "Prázdný repozitář"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
-#~ msgid "increase diff context to %(num)s lines"
-#~ msgstr ""
-
-#~ msgid "No comments."
-#~ msgstr ""
-
-#~ msgid "public journal"
-#~ msgstr ""
-
-#~ msgid "journal"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Cannot delete %s it still contains attached forks"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
+#, fuzzy
+#~| msgid "Repository URL"
+#~ msgid "Repository Group"
+#~ msgstr "Repozitáře"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "Zatím nejsou žádné soubory. %s"
 
 #~ msgid "Unlocked"
 #~ msgstr "Odemčeno"
@@ -6051,336 +5497,5 @@
 #~ msgid "Locked"
 #~ msgstr "Zamčeno"
 
-#~ msgid "Repository has been %s"
-#~ msgstr ""
-
-#~ msgid "You can't edit this user"
-#~ msgstr ""
-
-#~ msgid "compare view"
-#~ msgstr ""
-
-#~ msgid "fork name %s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s"
-#~ msgstr ""
-
-#~ msgid "No Files"
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
-#~ msgid "You can't remove this user since it's crucial for entire application"
-#~ msgstr ""
-
-#~ msgid "Username \"%(username)s\" is forbidden"
-#~ msgstr ""
-
-#~ msgid "invalid password"
-#~ msgstr ""
-
-#~ msgid "invalid user name"
-#~ msgstr ""
-
-#~ msgid "Your account is disabled"
-#~ msgstr ""
-
-#~ msgid "Repository name %(repo)s is disallowed"
-#~ msgstr ""
-
-#~ msgid "invalid clone URL"
-#~ msgstr ""
-
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "This email address is already taken"
-#~ msgstr ""
-
-#~ msgid "email \"%(email)s\" does not exist."
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
-#~ msgid "Defaults"
-#~ msgstr ""
-
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
-#~ msgid "Watched"
-#~ msgstr ""
-
-#~ msgid "My Permissions"
-#~ msgstr ""
-
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
-#~ msgid "delete"
-#~ msgstr ""
-
-#~ msgid "current IP"
-#~ msgstr ""
-
-#~ msgid "Permissions Administration"
-#~ msgstr ""
-
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Repository creation"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
-#~ msgid "Default User Permissions Overview"
-#~ msgstr ""
-
-#~ msgid "none"
-#~ msgstr ""
-
-#~ msgid "read"
-#~ msgstr ""
-
-#~ msgid "write"
-#~ msgstr ""
-
-#~ msgid "admin"
-#~ msgstr ""
-
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "default"
-#~ msgstr ""
-
-#~ msgid "revoke"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr ""
-
-#~ msgid "private repository"
-#~ msgstr ""
-
-#~ msgid "Remote URL"
-#~ msgstr ""
-
-#~ msgid "Pull Changes from Remote Location"
-#~ msgstr ""
-
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
-#~ msgid "edit"
-#~ msgstr ""
-
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
-#~ msgid "Default permissions"
-#~ msgstr ""
-
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "merge"
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Comment from pull request"
-#~ msgstr ""
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
-#~ msgid "Mimetype"
-#~ msgstr ""
-
 #~ msgid "My Repos"
 #~ msgstr "Prázdný repozitář"
-
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
-#~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid "Your password reset link was sent"
-#~ msgstr ""
-
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changeset __S"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
-#~ msgid "Created by"
-#~ msgstr ""
-
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "Changeset not found"
-#~ msgstr ""
-
-#~ msgid "Pull Requests from %s'"
-#~ msgstr ""
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/i18n/da/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,5490 @@
+# Translations template for Kallithea.
+# Copyright (C) 2017 Various authors, licensing as GPLv3
+# This file is distributed under the same license as the Kallithea project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Kallithea 0.3.99\n"
+"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2019-03-14 01:03+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: Danish <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/da/>\n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.5.1\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
+msgid "There are no changesets yet"
+msgstr "Der er ingen changesets endnu"
+
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
+#: kallithea/templates/base/perms_summary.html:14
+msgid "None"
+msgstr "Ingen"
+
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
+msgid "(closed)"
+msgstr "(lukket)"
+
+#: kallithea/controllers/changeset.py:83
+msgid "Show whitespace"
+msgstr "Vis mellemrum"
+
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
+#: kallithea/templates/files/diff_2way.html:55
+msgid "Ignore whitespace"
+msgstr "Ignorer mellemrum"
+
+#: kallithea/controllers/changeset.py:163
+#, python-format
+msgid "Increase diff context to %(num)s lines"
+msgstr "Øg diff konteksten med %(num)s linjer"
+
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "Ingen tilladelse til ændring af status for pull-forespørgsel"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Pull-forespørgsel %s slettet successfuldt"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
+msgid "Such revision does not exist for this repository"
+msgstr "En sådan revision findes ikke for dette repository"
+
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "Kunne ikke finde andet repository %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr "Kan ikke sammenligne repositories af forskellige typer"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr "Kan ikke vise en tom diff"
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr "Ingen forfader fundet for merge diff"
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr "Flere merge forfædre fundet for merge sammenligning"
+
+#: kallithea/controllers/compare.py:266
+msgid "Cannot compare repositories without using common ancestor"
+msgstr "Kan ikke sammenligne repositories uden en fælles forfader"
+
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "Intet svar"
+
+#: kallithea/controllers/error.py:71
+msgid "Unknown error"
+msgstr "Ukendt fejl"
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"Forespørgslen kunne ikke forstås af serveren på grund af fejlformet "
+"syntaks."
+
+#: kallithea/controllers/error.py:87
+msgid "Unauthorized access to resource"
+msgstr "Uautoriseret adgang til ressource"
+
+#: kallithea/controllers/error.py:89
+msgid "You don't have permission to view this page"
+msgstr "Du har ikke tilladelse til at se denne side"
+
+#: kallithea/controllers/error.py:91
+msgid "The resource could not be found"
+msgstr "Kunne ikke finde ressourcen"
+
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+"Serveren stødte på en uventet tilstand, som forhindrede den i at opfylde "
+"anmodningen."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s committed den %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Changesettet var for stor og blev afskåret..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr "Feed for %s %s"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Ændringer på repository %s"
+
+#: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr "Klik her  for at tilføje en ny fil"
+
+#: kallithea/controllers/files.py:93
+#, python-format
+msgid "There are no files yet. %s"
+msgstr "Der er ingen filer endnu. %s"
+
+#: kallithea/controllers/files.py:194
+#, python-format
+msgid "%s at %s"
+msgstr "%s fra %s"
+
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
+#, python-format
+msgid "This repository has been locked by %s on %s"
+msgstr "Dette repository er låst af %s den %s"
+
+#: kallithea/controllers/files.py:312
+msgid "You can only delete files with revision being a valid branch"
+msgstr "Du kan kun slette filer, hvor revisionen er en gyldig branch"
+
+#: kallithea/controllers/files.py:323
+#, python-format
+msgid "Deleted file %s via Kallithea"
+msgstr "Slettet fil %s via Kallithea"
+
+#: kallithea/controllers/files.py:345
+#, python-format
+msgid "Successfully deleted file %s"
+msgstr "Successfuldt slettet filen %s"
+
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
+msgid "Error occurred during commit"
+msgstr "Fejl opstået under commit"
+
+#: kallithea/controllers/files.py:372
+msgid "You can only edit files with revision being a valid branch"
+msgstr "Du kan kun redigere filer, hvor revisionen er en gyldig branch"
+
+#: kallithea/controllers/files.py:386
+#, python-format
+msgid "Edited file %s via Kallithea"
+msgstr "Redigeret fil %s via Kallithea"
+
+#: kallithea/controllers/files.py:402
+msgid "No changes"
+msgstr "Ingen ændringer"
+
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "Successfuldt committed til %s"
+
+#: kallithea/controllers/files.py:438
+msgid "Added file via Kallithea"
+msgstr "Tilføjet fil via Kallithea"
+
+#: kallithea/controllers/files.py:459
+msgid "No content"
+msgstr "Intet indhold"
+
+#: kallithea/controllers/files.py:463
+msgid "No filename"
+msgstr "Intet filnavn"
+
+#: kallithea/controllers/files.py:488
+msgid "Location must be relative path and must not contain .. in path"
+msgstr ""
+"Placeringen skal være en relativ sti og må ikke indeholde .. i stien"
+
+#: kallithea/controllers/files.py:520
+msgid "Downloads disabled"
+msgstr "Downloads er deaktiveret"
+
+#: kallithea/controllers/files.py:531
+#, python-format
+msgid "Unknown revision %s"
+msgstr "Ukendt revision %s"
+
+#: kallithea/controllers/files.py:533
+msgid "Empty repository"
+msgstr "Tomt repository"
+
+#: kallithea/controllers/files.py:535
+msgid "Unknown archive type"
+msgstr "Ukendt arkivtype"
+
+#: kallithea/controllers/files.py:756
+#: kallithea/templates/changeset/changeset_range.html:9
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
+#, fuzzy
+msgid "Changesets"
+msgstr "Changesets"
+
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
+#, fuzzy
+msgid "Branches"
+msgstr "Branches"
+
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
+msgid "Tags"
+msgstr "Tags"
+
+#: kallithea/controllers/forks.py:174
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "Der opstod en fejl under repository forking %s"
+
+#: kallithea/controllers/home.py:78
+msgid "Groups"
+msgstr "Grupper"
+
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
+#: kallithea/templates/admin/repos/repo_add.html:12
+#: kallithea/templates/admin/repos/repo_add.html:16
+#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/users/user_edit_advanced.html:6
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
+#, fuzzy
+msgid "Repositories"
+msgstr "Repositories"
+
+#: kallithea/controllers/home.py:121
+#: kallithea/templates/files/files_add.html:32
+#: kallithea/templates/files/files_delete.html:23
+#: kallithea/templates/files/files_edit.html:32
+#, fuzzy
+msgid "Branch"
+msgstr "Branch"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Lukkede Branches"
+
+#: kallithea/controllers/home.py:133
+msgid "Tag"
+msgstr "Tag"
+
+#: kallithea/controllers/home.py:139
+msgid "Bookmark"
+msgstr "Bogmærke"
+
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
+#: kallithea/templates/journal/public_journal.html:4
+#: kallithea/templates/journal/public_journal.html:18
+msgid "Public Journal"
+msgstr "Offentlig journal"
+
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
+msgid "Journal"
+msgstr "Journal"
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
+msgid "Bad captcha"
+msgstr "Dårlig captcha"
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Du har succesfuldt registreret med %s"
+
+#: kallithea/controllers/login.py:189
+msgid "A password reset confirmation code has been sent"
+msgstr "En bekræftelseskode til ændring af adgangskode er sendt"
+
+#: kallithea/controllers/login.py:238
+msgid "Invalid password reset token"
+msgstr "Ugyldig token for ændring af adgangskode"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
+msgid "Successfully updated password"
+msgstr "Successfuld ændring af adgangskode"
+
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "Ugyldig reviewer \"%s\" angivet"
+
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr "%s (lukket)"
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+#, fuzzy
+msgid "Changeset"
+msgstr "Changeset"
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr "Speciel"
+
+#: kallithea/controllers/pullrequests.py:182
+msgid "Peer branches"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr "Bogmærker"
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr "Fejl ved oprettelse af pull-forespørgsel: %s"
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr "Der opstod en fejl under oprettelse af pull-forespørgsel"
+
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Åbnede ny pull-forespørgsel med success"
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr "Ny pull-forespørgsel iteration oprettet"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr "I mellemtiden er de følgende reviewers tilføjet: %s"
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr "I mellemtiden er de følgende reviewers fjernet: %s"
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Ingen beskrivelse"
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr "Pull-forespørgsel opdateret"
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr "Slettede pull-forespørgsel med success"
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr "Revision %s er ikke fundet i %s"
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+"Fejl: Changesets ikke fundet, ved visning af pull-forespørgsel fra %s."
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr "Denne pull-forespørgsel er allerede merged til %s."
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr "Denne pull-forespørgsel er lukket og kan ikke opdateres."
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr "Følgende yderligere ændringer er tilgængelige på %s:"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+"Ingen yderligere changesets fundet ved iteration på denne pull-"
+"forespørgsel."
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr "Bemærk: Branch %s har et andet head: %s."
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr "Git pull-forespørgsler supportere ej iteration endnu."
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+"Fejl: Nogle changesets kunne ikke findes ved visning af pull-forespørgsel "
+"fra %s."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+"Diff'en kunne ikke vises - pull-forespørgslens revisions kunne ikke "
+"findes."
+
+#: kallithea/controllers/search.py:136
+msgid "Invalid search query. Try quoting it."
+msgstr "Ugyldig søgning. Prøv at citere det."
+
+#: kallithea/controllers/search.py:140
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
+msgid "An error occurred during search operation."
+msgstr "Der opstod en fejl under søgning."
+
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
+msgid "No data ready yet"
+msgstr "Ingen data er klar endnu"
+
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
+msgid "Statistics are disabled for this repository"
+msgstr "Statistik er slået fra for dette repository"
+
+#: kallithea/controllers/admin/auth_settings.py:135
+msgid "Auth settings updated successfully"
+msgstr "Auth-indstillinger opdateret successfuldt"
+
+#: kallithea/controllers/admin/auth_settings.py:146
+msgid "error occurred during update of auth settings"
+msgstr "Der opstod en fejl under opdatering af auth-indstillinger"
+
+#: kallithea/controllers/admin/defaults.py:75
+msgid "Default settings updated successfully"
+msgstr "Standard-indstillinger opdateret successfuldt"
+
+#: kallithea/controllers/admin/defaults.py:90
+msgid "Error occurred during update of defaults"
+msgstr "Der opstod en fejl under opdatering af standarder"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "For evigt"
+
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 minutter"
+
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 time"
+
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 dag"
+
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
+msgid "1 month"
+msgstr "1 måned"
+
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
+msgid "Lifetime"
+msgstr "Levetid"
+
+#: kallithea/controllers/admin/gists.py:140
+msgid "Error occurred during gist creation"
+msgstr "Der opstod en fejl under oprettelse af gist"
+
+#: kallithea/controllers/admin/gists.py:156
+#, python-format
+msgid "Deleted gist %s"
+msgstr "Slettet gist %s"
+
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr "Uændret"
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr "Opdateret gist-indhold successfuldt"
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr "Opdateret gist-data successfuldt"
+
+#: kallithea/controllers/admin/gists.py:233
+#, python-format
+msgid "Error occurred during update of gist %s"
+msgstr "Der opstod en fejl under opdatering af gist %s"
+
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+"Du kan ikke redigere denne bruger, da den er afgørende for hele "
+"applikationen"
+
+#: kallithea/controllers/admin/my_account.py:117
+msgid "Your account was updated successfully"
+msgstr "Din konto er blevet opdateret successfuldt"
+
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
+#, python-format
+msgid "Error occurred during update of user %s"
+msgstr "Der opstod en fejl under opdatering af bruger %s"
+
+#: kallithea/controllers/admin/my_account.py:166
+msgid "Error occurred during update of user password"
+msgstr "Der opstod en fejl under opdatering af bruger adgangskode"
+
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
+#, python-format
+msgid "Added email %s to user"
+msgstr "Tilføjet email %s til bruger"
+
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
+msgid "An error occurred during email saving"
+msgstr "Der opstod en fejl under tilføjelse af email"
+
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
+msgid "Removed email from user"
+msgstr "Fjernet email fra brugeren"
+
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr "API-nøgle oprettet successfuldt"
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr "API-nøgle nulstillet successfuldt"
+
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
+msgid "API key successfully deleted"
+msgstr "API-nøgle slettet successfuldt"
+
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
+msgstr "Læs"
+
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr "Skriv"
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
+#: kallithea/templates/admin/auth/auth_settings.html:9
+#: kallithea/templates/admin/defaults/defaults.html:9
+#: kallithea/templates/admin/permissions/permissions.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/admin/repos/repo_add.html:10
+#: kallithea/templates/admin/repos/repo_add.html:14
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/settings/settings.html:9
+#: kallithea/templates/admin/user_groups/user_group_add.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/admin/users/user_add.html:8
+#: kallithea/templates/admin/users/user_edit.html:9
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
+#: kallithea/templates/base/perms_summary.html:17
+msgid "Admin"
+msgstr "Admin"
+
+#: kallithea/controllers/admin/permissions.py:76
+#: kallithea/controllers/admin/permissions.py:87
+#: kallithea/controllers/admin/permissions.py:92
+#: kallithea/controllers/admin/permissions.py:95
+#: kallithea/controllers/admin/permissions.py:98
+#: kallithea/controllers/admin/permissions.py:101
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Deaktiveret"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Tilladt med manuel kontoaktivering"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Tilladt med automatisk kontoaktivering"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Manuel aktivering af ekstern konto"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Automatisk aktivering af ekstern konto"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
+msgid "Enabled"
+msgstr "Aktiveret"
+
+#: kallithea/controllers/admin/permissions.py:125
+msgid "Global permissions updated successfully"
+msgstr "Globale tilladelser opdateret"
+
+#: kallithea/controllers/admin/permissions.py:140
+msgid "Error occurred during update of permissions"
+msgstr "Der opstod en fejl under opdatering af tilladelser"
+
+#: kallithea/controllers/admin/repo_groups.py:174
+#, python-format
+msgid "Error occurred during creation of repository group %s"
+msgstr "Der opstod en fejl under oprettelse af repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:179
+#, python-format
+msgid "Created repository group %s"
+msgstr "Oprettet repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:226
+#, python-format
+msgid "Updated repository group %s"
+msgstr "Opdateret repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:242
+#, python-format
+msgid "Error occurred during update of repository group %s"
+msgstr "Der opstod en fejl under opdatering af repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:252
+#, python-format
+msgid "This group contains %s repositories and cannot be deleted"
+msgstr "Denne gruppe indeholder %s repositories og kan ikke slettes"
+
+#: kallithea/controllers/admin/repo_groups.py:259
+#, python-format
+msgid "This group contains %s subgroups and cannot be deleted"
+msgstr "Denne gruppe indeholder %s undergrupper og kan ikke slettes"
+
+#: kallithea/controllers/admin/repo_groups.py:265
+#, python-format
+msgid "Removed repository group %s"
+msgstr "Fjernet repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:270
+#, python-format
+msgid "Error occurred during deletion of repository group %s"
+msgstr "Der opstod en fejl under sletning af repository-gruppen %s"
+
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
+msgid "Cannot revoke permission for yourself as admin"
+msgstr "Kan ikke tilbagekalde tilladelse for én selv som admin"
+
+#: kallithea/controllers/admin/repo_groups.py:369
+msgid "Repository group permissions updated"
+msgstr "Repository-gruppe tilladelser opdateret"
+
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
+msgid "An error occurred during revoking of permission"
+msgstr "Der opstod en fejl under tilbagekaldelse af tilladelse"
+
+#: kallithea/controllers/admin/repos.py:137
+#, python-format
+msgid "Error creating repository %s"
+msgstr "Fejl ved oprettelse af repository %s"
+
+#: kallithea/controllers/admin/repos.py:195
+#, python-format
+msgid "Created repository %s from %s"
+msgstr "Oprettet repository %s fra %s"
+
+#: kallithea/controllers/admin/repos.py:204
+#, python-format
+msgid "Forked repository %s as %s"
+msgstr "Forked repository %s som %s"
+
+#: kallithea/controllers/admin/repos.py:207
+#, python-format
+msgid "Created repository %s"
+msgstr "Oprettet repository %s"
+
+#: kallithea/controllers/admin/repos.py:236
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr "Repository %s opdateret"
+
+#: kallithea/controllers/admin/repos.py:256
+#, python-format
+msgid "Error occurred during update of repository %s"
+msgstr "Der opstod en fejl under opdatering af repository %s"
+
+#: kallithea/controllers/admin/repos.py:274
+#, python-format
+msgid "Detached %s forks"
+msgstr "Fraskilt %s forks"
+
+#: kallithea/controllers/admin/repos.py:277
+#, python-format
+msgid "Deleted %s forks"
+msgstr "Slettet %s forks"
+
+#: kallithea/controllers/admin/repos.py:282
+#, python-format
+msgid "Deleted repository %s"
+msgstr "Slettet repository %s"
+
+#: kallithea/controllers/admin/repos.py:285
+#, python-format
+msgid "Cannot delete repository %s which still has forks"
+msgstr "Kan ikke slette repository %s, da den stadig har forks"
+
+#: kallithea/controllers/admin/repos.py:290
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr "Der opstod en fejl under sletning af %s"
+
+#: kallithea/controllers/admin/repos.py:330
+msgid "Repository permissions updated"
+msgstr "Repository tilladelser opdateret"
+
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr "Feltvaliderings fejl: %s"
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr "Der opstod en fejl under oprettelse af felt: %r"
+
+#: kallithea/controllers/admin/repos.py:401
+msgid "An error occurred during removal of field"
+msgstr "Der opstod en fejl under fjernelse af feltet"
+
+#: kallithea/controllers/admin/repos.py:415
+msgid "-- Not a fork --"
+msgstr "-- Ikke en fork --"
+
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr "Opdateret repository's synlighed i den offentlige journal"
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+"Der opstod en fejl under indstilling af dette repository, i den "
+"offentlige journal"
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr "Intet"
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr "Mærket repository %s som fork af %s"
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr "Der opstod en fejl under denne operation"
+
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
+msgid "Repository has been locked"
+msgstr "Repository er blevet låst"
+
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
+msgid "Repository has been unlocked"
+msgstr "Repository er blevet låst op"
+
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
+msgid "An error occurred during unlocking"
+msgstr "Der opstod en fejl ved oplåsning"
+
+#: kallithea/controllers/admin/repos.py:528
+msgid "Cache invalidation successful"
+msgstr "Ugyldiggørelse af cache er succesfuld"
+
+#: kallithea/controllers/admin/repos.py:532
+msgid "An error occurred during cache invalidation"
+msgstr "Der opstod en fejl under cache ugyldiggørelse"
+
+#: kallithea/controllers/admin/repos.py:545
+#, fuzzy
+msgid "Pulled from remote location"
+msgstr "Pulled fra remote placering"
+
+#: kallithea/controllers/admin/repos.py:548
+msgid "An error occurred during pull from remote location"
+msgstr "Der opstod en fejl under pull fra remote placering"
+
+#: kallithea/controllers/admin/repos.py:579
+msgid "An error occurred during deletion of repository stats"
+msgstr "Der opstod en fejl under sletning af repository statistik"
+
+#: kallithea/controllers/admin/settings.py:135
+msgid "Updated VCS settings"
+msgstr "Opdateret VCS-indstillinger"
+
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+"Ude af stand til at aktivere hgsubversion understøttelse. \"hgsubversion"
+"\" biblioteket mangler"
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr "Der opstod en fejl ved opdatering af applikationsindstillinger"
+
+#: kallithea/controllers/admin/settings.py:174
+#, python-format
+msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
+msgstr "Repositories genscannet successfuldt. Tilføjet: %s. Fjernet: %s."
+
+#: kallithea/controllers/admin/settings.py:189
+#, python-format
+msgid "Invalidated %s repositories"
+msgstr "Ugyldiggjort %s repositories"
+
+#: kallithea/controllers/admin/settings.py:230
+msgid "Updated application settings"
+msgstr "Opdateret applikationsindstillinger"
+
+#: kallithea/controllers/admin/settings.py:283
+msgid "Updated visualisation settings"
+msgstr "Opdateret visualiseringsindstillinger"
+
+#: kallithea/controllers/admin/settings.py:288
+msgid "Error occurred during updating visualisation settings"
+msgstr "Der opstod en fejl under opdatering af visualiseringsindstillinger"
+
+#: kallithea/controllers/admin/settings.py:312
+msgid "Please enter email address"
+msgstr "Indtast email-adresse"
+
+#: kallithea/controllers/admin/settings.py:327
+msgid "Send email task created"
+msgstr "Send email-opgave oprettet"
+
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Ingen data er klar endnu"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
+msgid "Added new hook"
+msgstr "Tilføjet nyt hook"
+
+#: kallithea/controllers/admin/settings.py:376
+msgid "Updated hooks"
+msgstr "Opdateret hooks"
+
+#: kallithea/controllers/admin/settings.py:380
+msgid "Error occurred during hook creation"
+msgstr "Der opstod en fejl under oprettelse af et hook"
+
+#: kallithea/controllers/admin/settings.py:404
+msgid "Whoosh reindex task scheduled"
+msgstr "Whoosh reindex-opgave skeduleret"
+
+#: kallithea/controllers/admin/user_groups.py:143
+#, python-format
+msgid "Created user group %s"
+msgstr "Oprettet brugergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:156
+#, python-format
+msgid "Error occurred during creation of user group %s"
+msgstr "Der opstod en fejl under oprettelse af brugergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:184
+#, python-format
+msgid "Updated user group %s"
+msgstr "Opdateret brugergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:206
+#, python-format
+msgid "Error occurred during update of user group %s"
+msgstr "Der opstod en fejl under opdatering af brugergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:217
+msgid "Successfully deleted user group"
+msgstr "Brugergruppe slettet succesfuldt"
+
+#: kallithea/controllers/admin/user_groups.py:222
+msgid "An error occurred during deletion of user group"
+msgstr "Der opstod en fejl under sletning af brugergruppe"
+
+#: kallithea/controllers/admin/user_groups.py:278
+msgid "Target group cannot be the same"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:284
+msgid "User group permissions updated"
+msgstr "Brugergrupper-tilladelser opdateret"
+
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
+msgid "Updated permissions"
+msgstr "Tilladelser opdateret"
+
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
+msgid "An error occurred during permissions saving"
+msgstr "Der opstod en fejl under gemning af tilladelser"
+
+#: kallithea/controllers/admin/users.py:123
+#, python-format
+msgid "Created user %s"
+msgstr "Bruger %s oprettet"
+
+#: kallithea/controllers/admin/users.py:138
+#, python-format
+msgid "Error occurred during creation of user %s"
+msgstr "Der opstod en fejl under oprettelse af bruger %s"
+
+#: kallithea/controllers/admin/users.py:162
+msgid "User updated successfully"
+msgstr "Bruger opdateret"
+
+#: kallithea/controllers/admin/users.py:190
+msgid "Successfully deleted user"
+msgstr "Slettet bruger"
+
+#: kallithea/controllers/admin/users.py:195
+msgid "An error occurred during deletion of user"
+msgstr "Der opstod en fejl under sletning af bruger %s"
+
+#: kallithea/controllers/admin/users.py:203
+msgid "The default user cannot be edited"
+msgstr "Standardbrugeren kan ikke redigeres"
+
+#: kallithea/controllers/admin/users.py:412
+#, python-format
+msgid "Added IP address %s to user whitelist"
+msgstr "Tilføjet IP-adresse %s til bruger-whitelist"
+
+#: kallithea/controllers/admin/users.py:418
+msgid "An error occurred while adding IP address"
+msgstr "Der opstod en fejl under tilføjelse af IP-adresse"
+
+#: kallithea/controllers/admin/users.py:430
+msgid "Removed IP address from user whitelist"
+msgstr "Fjernet IP-adresse fra bruger-whitelist"
+
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr "Du skal være registreret bruger for at kunne udføre denne handling"
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr "Du skal være logget ind for at se denne side"
+
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr "Ugyldig API-nøgle"
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr "CSRF-token lækage opdaget, alle form-tokens er invalideret"
+
+#: kallithea/lib/base.py:583
+msgid "Repository not found in the filesystem"
+msgstr "Repository ikke fundet i filsystemet"
+
+#: kallithea/lib/base.py:608
+#, python-format
+msgid "Changeset for %s %s not found in %s"
+msgstr "Changeset for %s %s ikke fundet i %s"
+
+#: kallithea/lib/diffs.py:193
+msgid "Binary file"
+msgstr "Binær fil"
+
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+"Changeset var for stor, og blev afskåret, brug diff menu for at få vist "
+"denne diff"
+
+#: kallithea/lib/diffs.py:223
+msgid "No changes detected"
+msgstr "Ingen ændringer fundet"
+
+#: kallithea/lib/helpers.py:612
+#, python-format
+msgid "Deleted branch: %s"
+msgstr "Slettet branch: %s"
+
+#: kallithea/lib/helpers.py:614
+#, python-format
+msgid "Created tag: %s"
+msgstr "Oprettet tag: %s"
+
+#: kallithea/lib/helpers.py:625
+#, python-format
+msgid "Changeset %s not found"
+msgstr "Changeset %s ikke fundet"
+
+#: kallithea/lib/helpers.py:674
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr "Vis alle kombineret changesets %s->%s"
+
+#: kallithea/lib/helpers.py:680
+msgid "Compare view"
+msgstr "Sammenlign visning"
+
+#: kallithea/lib/helpers.py:699
+msgid "and"
+msgstr "og"
+
+#: kallithea/lib/helpers.py:700
+#, python-format
+msgid "%s more"
+msgstr "%s flere"
+
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
+msgid "revisions"
+msgstr "revisioner"
+
+#: kallithea/lib/helpers.py:725
+#, python-format
+msgid "Fork name %s"
+msgstr "Fork-navn %s"
+
+#: kallithea/lib/helpers.py:746
+#, python-format
+msgid "Pull request %s"
+msgstr "Pull-forespørgsel %s"
+
+#: kallithea/lib/helpers.py:756
+msgid "[deleted] repository"
+msgstr "[slettet] repository"
+
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
+msgid "[created] repository"
+msgstr "[oprettet] repository"
+
+#: kallithea/lib/helpers.py:760
+msgid "[created] repository as fork"
+msgstr "[oprettet] repository som fork"
+
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
+msgid "[forked] repository"
+msgstr "[forked] repository"
+
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
+msgid "[updated] repository"
+msgstr "[opdateret] repository"
+
+#: kallithea/lib/helpers.py:766
+msgid "[downloaded] archive from repository"
+msgstr "[hentet] arkiv fra repository"
+
+#: kallithea/lib/helpers.py:768
+msgid "[delete] repository"
+msgstr "[slettet] repository"
+
+#: kallithea/lib/helpers.py:776
+msgid "[created] user"
+msgstr "[oprettet] bruger"
+
+#: kallithea/lib/helpers.py:778
+msgid "[updated] user"
+msgstr "[opdateret] bruger"
+
+#: kallithea/lib/helpers.py:780
+msgid "[created] user group"
+msgstr "[oprettet] brugergruppe"
+
+#: kallithea/lib/helpers.py:782
+msgid "[updated] user group"
+msgstr "[opdateret] brugergruppe"
+
+#: kallithea/lib/helpers.py:784
+msgid "[commented] on revision in repository"
+msgstr "[kommenterede] på revision i repository"
+
+#: kallithea/lib/helpers.py:786
+msgid "[commented] on pull request for"
+msgstr "[kommenterede] på pull-forespørgsel for"
+
+#: kallithea/lib/helpers.py:788
+msgid "[closed] pull request for"
+msgstr "[lukket] pull-forespørgsel for"
+
+#: kallithea/lib/helpers.py:790
+msgid "[pushed] into"
+msgstr "[pushed] ind i"
+
+#: kallithea/lib/helpers.py:792
+msgid "[committed via Kallithea] into repository"
+msgstr "[committed via kallithea] ind i repository"
+
+#: kallithea/lib/helpers.py:794
+msgid "[pulled from remote] into repository"
+msgstr "[pulled fra remote] ind i repository"
+
+#: kallithea/lib/helpers.py:796
+msgid "[pulled] from"
+msgstr "[pulled] fra"
+
+#: kallithea/lib/helpers.py:798
+msgid "[started following] repository"
+msgstr "[begyndt at følge] repository"
+
+#: kallithea/lib/helpers.py:800
+msgid "[stopped following] repository"
+msgstr "[stoppet at følge] repository"
+
+#: kallithea/lib/helpers.py:928
+#, python-format
+msgid " and %s more"
+msgstr " og %s flere"
+
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
+msgid "No files"
+msgstr "Ingen filer"
+
+#: kallithea/lib/helpers.py:957
+msgid "new file"
+msgstr "ny fil"
+
+#: kallithea/lib/helpers.py:960
+msgid "mod"
+msgstr "mod"
+
+#: kallithea/lib/helpers.py:963
+msgid "del"
+msgstr "del"
+
+#: kallithea/lib/helpers.py:966
+msgid "rename"
+msgstr "omdøb"
+
+#: kallithea/lib/helpers.py:971
+msgid "chmod"
+msgstr "chmod"
+
+#: kallithea/lib/helpers.py:1264
+#, python-format
+msgid ""
+"%s repository is not mapped to db perhaps it was created or renamed from "
+"the filesystem please run the application again in order to rescan "
+"repositories"
+msgstr ""
+"%s repository er ikke knyttet til db, måske var det skabt eller omdøbt "
+"fra filsystemet, kør applikationen igen for at scanne repositories"
+
+#: kallithea/lib/utils2.py:333
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:334
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:335
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:336
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:337
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:338
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:354
+#, python-format
+msgid "in %s"
+msgstr "i %s"
+
+#: kallithea/lib/utils2.py:356
+#, python-format
+msgid "%s ago"
+msgstr "%s siden"
+
+#: kallithea/lib/utils2.py:358
+#, python-format
+msgid "in %s and %s"
+msgstr "i %s og %s"
+
+#: kallithea/lib/utils2.py:361
+#, python-format
+msgid "%s and %s ago"
+msgstr "%s og %s siden"
+
+#: kallithea/lib/utils2.py:364
+msgid "just now"
+msgstr "lige nu"
+
+#: kallithea/model/comment.py:68
+#, python-format
+msgid "on line %s"
+msgstr "på linje %s"
+
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
+msgid "[Mention]"
+msgstr "[Omtale]"
+
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "top-niveau"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Kallithea Administrator"
+
+#: kallithea/model/db.py:1705
+msgid "Default user has no access to new repositories"
+msgstr "Standard-bruger har ikke adgang til nye repositories"
+
+#: kallithea/model/db.py:1706
+msgid "Default user has read access to new repositories"
+msgstr "Standard-bruger har læse-adgang til nye repositories"
+
+#: kallithea/model/db.py:1707
+msgid "Default user has write access to new repositories"
+msgstr "Standard-bruger har skrive-adgang til nye repositories"
+
+#: kallithea/model/db.py:1708
+msgid "Default user has admin access to new repositories"
+msgstr "Standard-bruger har admin-adgang til nye repositories"
+
+#: kallithea/model/db.py:1710
+msgid "Default user has no access to new repository groups"
+msgstr "Standard-bruger har ikke adgang til nye repository-grupper"
+
+#: kallithea/model/db.py:1711
+msgid "Default user has read access to new repository groups"
+msgstr "Standard-bruger har læse-adgang til nye repository-grupper"
+
+#: kallithea/model/db.py:1712
+msgid "Default user has write access to new repository groups"
+msgstr "Standard-bruger har skrive-adgang til nye repository-grupper"
+
+#: kallithea/model/db.py:1713
+msgid "Default user has admin access to new repository groups"
+msgstr "Standard-bruger har admin-adgang til nye repository-grupper"
+
+#: kallithea/model/db.py:1715
+msgid "Default user has no access to new user groups"
+msgstr "Standard-bruger har ikke adgang til nye brugergrupper"
+
+#: kallithea/model/db.py:1716
+msgid "Default user has read access to new user groups"
+msgstr "Standard-bruger har læse-adgang til nye brugergrupper"
+
+#: kallithea/model/db.py:1717
+msgid "Default user has write access to new user groups"
+msgstr "Standard-bruger har skrive-adgang til nye brugergrupper"
+
+#: kallithea/model/db.py:1718
+msgid "Default user has admin access to new user groups"
+msgstr "Standard-bruger har admin-adgang til nye brugergrupper"
+
+#: kallithea/model/db.py:1720
+msgid "Only admins can create repository groups"
+msgstr "Kun administratorer kan oprette repository-grupper"
+
+#: kallithea/model/db.py:1721
+msgid "Non-admins can create repository groups"
+msgstr "Ikke-administratorer kan oprette repository-grupper"
+
+#: kallithea/model/db.py:1723
+msgid "Only admins can create user groups"
+msgstr "Kun administratorer kan oprette brugergrupper"
+
+#: kallithea/model/db.py:1724
+msgid "Non-admins can create user groups"
+msgstr "Ikke-administratorer kan oprette brugergrupper"
+
+#: kallithea/model/db.py:1726
+msgid "Only admins can create top level repositories"
+msgstr "Kun administratorer kan oprette top-niveau repositories"
+
+#: kallithea/model/db.py:1727
+msgid "Non-admins can create top level repositories"
+msgstr "Ikke-administratorer kan oprette top-niveau repositories"
+
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+"Repository oprettelse aktiveret med skriveadgang til en repository-gruppe"
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+"Repository oprettelse deaktiveret med skriveadgang til en repository-"
+"gruppe"
+
+#: kallithea/model/db.py:1732
+msgid "Only admins can fork repositories"
+msgstr "Kun admins kan fork repositories"
+
+#: kallithea/model/db.py:1733
+msgid "Non-admins can fork repositories"
+msgstr "Ikke-administratorer kan forke repositories"
+
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Registrering deaktiveret"
+
+#: kallithea/model/db.py:1736
+msgid "User registration with manual account activation"
+msgstr "Brugerregistrering med manuel kontoaktivering"
+
+#: kallithea/model/db.py:1737
+msgid "User registration with automatic account activation"
+msgstr "Brugerregistrering med automatisk kontoaktivering"
+
+#: kallithea/model/db.py:2263
+msgid "Not reviewed"
+msgstr "Ikke gennemgået"
+
+#: kallithea/model/db.py:2264
+msgid "Under review"
+msgstr "Under gennemgang"
+
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr "Ikke godkendt"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Godkendt"
+
+#: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr "Indtast venligst et login"
+
+#: kallithea/model/forms.py:59
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr "Indtast en værdi med mindst %(min)i tegn"
+
+#: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr "Skriv venligst et kodeord"
+
+#: kallithea/model/forms.py:68
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr "Indtast %(min)i tegn eller flere"
+
+#: kallithea/model/forms.py:170
+msgid "Name must not contain only digits"
+msgstr "Navn må ikke kun indeholde cifre"
+
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+"[Kommentar] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" på "
+"%(branch)s"
+
+#: kallithea/model/notification.py:168
+#, python-format
+msgid "New user %(new_username)s registered"
+msgstr "Ny bruger %(new_username)s registreret"
+
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+"[Gennemgang] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" fra "
+"%(pr_source_branch)s af %(pr_owner_username)s"
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+"[Kommentar] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" fra "
+"%(pr_source_branch)s af %(pr_owner_username)s"
+
+#: kallithea/model/notification.py:184
+msgid "Closing"
+msgstr "Lukning"
+
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s vil have dig til at gennemgå pull-forespørgsel %(pr_nice_id)s: "
+"%(pr_title)s"
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr "Kan ikke oprette en tom pull-forespørgsel"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
+msgid "latest tip"
+msgstr ""
+
+#: kallithea/model/user.py:189
+msgid "New user registration"
+msgstr ""
+
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:263
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:364
+msgid "Password reset link"
+msgstr ""
+
+#: kallithea/model/user.py:413
+msgid "Password reset notification"
+msgstr ""
+
+#: kallithea/model/user.py:414
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: kallithea/model/validators.py:74
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:76
+#, python-format
+msgid "Username \"%(username)s\" cannot be used"
+msgstr ""
+
+#: kallithea/model/validators.py:78
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
+msgid "The input is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:133
+msgid "Invalid user group name"
+msgstr ""
+
+#: kallithea/model/validators.py:134
+#, python-format
+msgid "User group \"%(usergroup)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:136
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:176
+msgid "Cannot assign this group as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:177
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:179
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: kallithea/model/validators.py:250
+msgid "Invalid old password"
+msgstr ""
+
+#: kallithea/model/validators.py:266
+msgid "Passwords do not match"
+msgstr ""
+
+#: kallithea/model/validators.py:281
+msgid "Invalid username or password"
+msgstr ""
+
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:331
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: kallithea/model/validators.py:333
+#, python-format
+msgid "Repository group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:419
+msgid "Invalid repository URL"
+msgstr ""
+
+#: kallithea/model/validators.py:420
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:445
+msgid "Fork has to be the same type as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:460
+msgid "You don't have permissions to create repository in this group"
+msgstr ""
+
+#: kallithea/model/validators.py:462
+msgid "no permission to create repository in root location"
+msgstr ""
+
+#: kallithea/model/validators.py:512
+msgid "You don't have permissions to create a group in this location"
+msgstr ""
+
+#: kallithea/model/validators.py:552
+msgid "This username or user group name is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:645
+msgid "This is not a valid path"
+msgstr ""
+
+#: kallithea/model/validators.py:662
+msgid "This email address is already in use"
+msgstr ""
+
+#: kallithea/model/validators.py:682
+#, python-format
+msgid "Email address \"%(email)s\" not found"
+msgstr ""
+
+#: kallithea/model/validators.py:719
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:731
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr ""
+
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
+msgid "Key name can only consist of letters, underscore, dash or numbers"
+msgstr ""
+
+#: kallithea/model/validators.py:779
+msgid "Filename cannot be inside a directory"
+msgstr ""
+
+#: kallithea/model/validators.py:795
+#, python-format
+msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
+msgstr ""
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
+msgid "About"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add.html:5
+#: kallithea/templates/admin/repos/repo_add.html:19
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
+msgid "Add Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "You have admin right to this group, and can edit it"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "Edit Repository Group"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
+#: kallithea/templates/pullrequests/pullrequest_show.html:38
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
+msgid "Description"
+msgstr ""
+
+#: kallithea/templates/index_base.html:60
+msgid "Last Change"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
+msgid "Tip"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
+#: kallithea/templates/pullrequests/pullrequest_data.html:16
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
+msgid "Owner"
+msgstr ""
+
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
+msgid "Log In"
+msgstr ""
+
+#: kallithea/templates/login.html:17
+#, python-format
+msgid "Log In to %s"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:5
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
+msgid "Username"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
+msgid "Password"
+msgstr ""
+
+#: kallithea/templates/login.html:44
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
+msgid "Forgot your password ?"
+msgstr ""
+
+#: kallithea/templates/login.html:55
+msgid "Don't have an account ?"
+msgstr ""
+
+#: kallithea/templates/login.html:62
+msgid "Sign In"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:5
+msgid "Password Reset"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
+msgid "Reset Your Password to %s"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:23
+#: kallithea/templates/password_reset_confirmation.html:5
+#: kallithea/templates/password_reset_confirmation.html:18
+msgid "Reset Your Password"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:30
+msgid "Email Address"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
+msgid "Captcha"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
+#, python-format
+msgid "You are about to set a new password for the email address %s."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
+msgid "Code you received in the email"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:36
+msgid "New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:43
+msgid "Confirm New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:51
+msgid "Confirm"
+msgstr ""
+
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
+msgid "Sign Up"
+msgstr ""
+
+#: kallithea/templates/register.html:22
+#, python-format
+msgid "Sign Up to %s"
+msgstr ""
+
+#: kallithea/templates/register.html:45
+msgid "Re-enter password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
+msgid "First Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
+msgid "Last Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
+#: kallithea/templates/admin/settings/settings.html:31
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
+msgid "Email"
+msgstr ""
+
+#: kallithea/templates/register.html:85
+msgid "Registered accounts are ready to use and need no further action."
+msgstr ""
+
+#: kallithea/templates/register.html:87
+msgid "Please wait for an administrator to activate your account."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:5
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/base/base.html:55
+msgid "Admin Journal"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
+msgid "journal filter..."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:12
+#: kallithea/templates/journal/journal.html:12
+msgid "Filter"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/journal/journal.html:13
+#, python-format
+msgid "%s Entry"
+msgid_plural "%s Entries"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/admin_log.html:6
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
+msgid "Action"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:8
+msgid "Date"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:61
+msgid "No actions yet"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:5
+msgid "Authentication Settings"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:11
+#: kallithea/templates/base/base.html:61
+msgid "Authentication"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:27
+msgid "Authentication Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
+msgid "Enabled Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
+msgid "Available built-in plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
+msgid "Plugin"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:101
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
+msgid "Save"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:5
+#: kallithea/templates/admin/defaults/defaults.html:11
+#: kallithea/templates/base/base.html:62
+msgid "Repository Defaults"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
+msgid "Type"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
+msgid "Enable locking"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:5
+#: kallithea/templates/admin/gists/edit.html:18
+msgid "Edit Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was update since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
+msgid "Gist description ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
+msgid "Gist lifetime"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/edit.html:61
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/gists/show.html:47
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
+msgid "Expires"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+msgid "Never"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:145
+msgid "Update Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:146
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
+msgid "Cancel"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:6
+#: kallithea/templates/admin/gists/index.html:16
+#, python-format
+msgid "Private Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:8
+#: kallithea/templates/admin/gists/index.html:18
+#, python-format
+msgid "Public Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:10
+#: kallithea/templates/admin/gists/index.html:20
+msgid "Public Gists"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:37
+#: kallithea/templates/admin/gists/show.html:25
+#: kallithea/templates/base/base.html:312
+msgid "Create New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
+msgid "Created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:66
+msgid "There are no gists yet"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:5
+#: kallithea/templates/admin/gists/new.html:18
+msgid "New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
+msgid "Create Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:54
+msgid "Create Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
+msgid "Reset"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:5
+#: kallithea/templates/admin/gists/show.html:9
+msgid "Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:10
+msgid "URL"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:37
+msgid "Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/pullrequests/pullrequest_data.html:20
+msgid "Delete"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+msgid "Confirm to delete this Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
+msgid "Show as Raw"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:69
+msgid "created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:82
+msgid "Show as raw"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:5
+#: kallithea/templates/admin/my_account/my_account.html:9
+#: kallithea/templates/base/base.html:397
+msgid "My Account"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:25
+#: kallithea/templates/admin/users/user_edit.html:29
+msgid "Profile"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:26
+msgid "Email Addresses"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:28
+#: kallithea/templates/admin/users/user_edit.html:31
+msgid "API Keys"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:29
+msgid "Owned Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
+msgid "Watched Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:31
+#: kallithea/templates/admin/permissions/permissions.html:30
+#: kallithea/templates/admin/user_groups/user_group_edit.html:32
+#: kallithea/templates/admin/users/user_edit.html:34
+msgid "Show Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
+msgid "Built-in"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
+#, python-format
+msgid "Confirm to reset this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
+msgid "Expired"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
+#, python-format
+msgid "Confirm to remove this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
+msgid "Remove"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
+msgid "No additional API keys specified"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
+msgid "New API key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
+msgid "Add"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
+msgid "Primary"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
+msgid "No additional emails specified."
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
+msgid "New email address"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:1
+msgid "Change Your Account Password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:8
+msgid "Current password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
+msgid "New password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:22
+msgid "Confirm new password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:12
+#: kallithea/templates/admin/users/user_edit_profile.html:12
+msgid "Avatars are disabled"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:1
+msgid "Repositories You Own"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_watched.html:1
+msgid "Repositories You are Watching"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:5
+#: kallithea/templates/admin/permissions/permissions.html:11
+#: kallithea/templates/base/base.html:60
+msgid "Default Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:28
+#: kallithea/templates/admin/settings/settings.html:29
+msgid "Global"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:29
+#: kallithea/templates/admin/users/user_edit.html:32
+msgid "IP Whitelist"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
+msgid "Anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+msgid ""
+"All default permissions on each repository will be reset to chosen "
+"permission, note that all custom default permission on repositories will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
+msgid "Apply to all existing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:32
+msgid ""
+"All default permissions on each repository group will be reset to chosen "
+"permission, note that all custom default permission on repository groups "
+"will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid ""
+"All default permissions on each user group will be reset to chosen "
+"permission, note that all custom default permission on user groups will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
+msgid "Top level repository creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
+msgid ""
+"With this, write permission to a repository group allows creating "
+"repositories inside that group. Without this, group write permissions "
+"mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
+msgid "User group creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
+msgid "Enable this to allow non-admins to create user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
+msgid "External auth account activation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
+msgid "Confirm to delete this IP address: %s"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
+msgid "All IP addresses are allowed."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
+msgid "New IP address"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
+msgid "Repository Groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
+msgid "Group name"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
+msgid "Group parent"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
+msgid "Copy parent group permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
+msgid "Copy permission set from parent repository group."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
+#, python-format
+msgid "%s Repository Group Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
+msgid "Add Child Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
+#: kallithea/templates/admin/repos/repo_edit.html:12
+#: kallithea/templates/admin/repos/repo_edit.html:25
+#: kallithea/templates/admin/settings/settings.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit.html:29
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
+msgid "Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
+#: kallithea/templates/admin/user_groups/user_group_edit.html:30
+#: kallithea/templates/admin/users/user_edit.html:33
+msgid "Advanced"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit.html:31
+msgid "Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1
+#, python-format
+msgid "Repository Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6
+msgid "Top level repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
+msgid "Total repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
+msgid "Children groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_advanced.html:8
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
+msgid "Created on"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:121
+#, python-format
+msgid "Confirm to delete this group: %s with %s repository"
+msgid_plural "Confirm to delete this group: %s with %s repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
+msgid "Delete this repository group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
+msgid "User/User Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
+msgid "Default"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
+msgid "Revoke"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
+msgid "Add new"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
+msgid "Apply to children"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
+msgid "Both"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
+msgid ""
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Remove this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Confirm to delete this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_show.html:4
+#, python-format
+msgid "Repository group %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:5
+msgid "Repository Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
+msgid "Number of Top-level Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:12
+msgid "Clone remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+msgid ""
+"Optional: URL of a remote repository. If set, the repository will be "
+"created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
+msgid "Optionally select a group to put this repository into."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
+msgid "Type of repository to create."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
+msgid "Landing revision"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:9
+#, python-format
+msgid "%s Creating Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:13
+msgid "Creating repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:27
+#, python-format
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:39
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:8
+#, python-format
+msgid "%s Repository Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:34
+msgid "Extra Fields"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:37
+msgid "Caches"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:40
+msgid "Remote"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/summary/statistics.html:8
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
+msgid "Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:1
+msgid "Parent"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:5
+msgid "Set"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
+msgid "Manually set this repository as a fork of another from the list."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
+msgid "Public Journal Visibility"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
+msgid "Remove from public journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
+msgid "Add to Public Journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
+msgid "Change Locking"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#, python-format
+msgid "Locked by %s on %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+msgid "Repository is not locked"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
+msgid ""
+"Force locking on the repository. Works only when anonymous access is "
+"disabled. Triggering a pull locks the repository.  The user who is "
+"pulling locks the repository; only the user who pulled and locked it can "
+"unlock it by doing a push."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
+msgid "Delete this Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
+msgid "This repository has %s fork"
+msgid_plural "This repository has %s forks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
+msgid "Detach forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+msgid "Delete forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:4
+msgid "Invalidate Repository Cache"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgid "Prefix"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+msgid "Key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
+msgid "Active"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+msgid "Label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
+#, python-format
+msgid "Confirm to delete this field: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
+msgid "New field key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
+msgid "New field label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
+msgid "Enter short label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
+msgid "New field description"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
+msgid "Enter description of a field"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
+msgid "Extra fields are disabled."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
+msgid "Private Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
+msgid "Remote repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
+msgid "Pull Changes from Remote Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
+msgid "This repository does not have a remote repository URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "Permanent Repository ID"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "What is that?"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
+msgid "URL by id"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
+msgid ""
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
+msgid "Remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
+msgid "Repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
+msgid ""
+"Optional: URL of a remote repository. If set, the repository can be "
+"pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
+msgid "Change owner of this repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+msgid "Processed progress"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Reset Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Confirm to remove current statistics."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:5
+msgid "Repositories Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:43
+msgid "State"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:5
+msgid "Settings Administration"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:27
+msgid "VCS"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:28
+msgid "Remap and Rescan"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:30
+msgid "Visual"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:4
+msgid "Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:33
+msgid "Full Text Search"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:34
+msgid "System Info"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:4
+msgid "Send test email to"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:12
+msgid "Send"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:4
+msgid "Site branding"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:12
+msgid "HTTP authentication realm"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
+msgid "ReCaptcha public key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:35
+msgid "Public key for reCaptcha system."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:40
+msgid "ReCaptcha private key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
+msgid "Save Settings"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:3
+msgid "Built-in Mercurial Hooks (Read-Only)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
+msgid "Failed to remove hook"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
+msgid "Delete records of missing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:17
+msgid "Invalidate cache for all repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:20
+msgid "Check this to reload data and clear cache keys for all repositories."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:25
+msgid "Install Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
+msgid "Overwrite existing Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
+msgid "Rescan Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:4
+msgid "Index build option"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:12
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
+msgid "Reindex"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Check for updates"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Upgrade info endpoint"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Note: please make sure this server can access this URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:23
+msgid "Python Packages"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:9
+msgid "Show repository size after push"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:15
+msgid "Log user push commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:21
+msgid "Log user pull commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:27
+msgid "Update repository after push (hg update)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:33
+msgid "Mercurial extensions"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:47
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:64
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
+msgid "General"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:9
+msgid "Use repository extra fields"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:12
+msgid "Allows storing additional customized fields per repository."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:17
+msgid "Show Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:20
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
+msgid ""
+"Gravatar URL allows you to use another avatar server application.\n"
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
+msgid ""
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:65
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:75
+msgid "Show public repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:84
+msgid "Show public/private icons next to repository names."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:89
+msgid "Meta Tagging"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:5
+msgid "Add user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit.html:11
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
+msgid "User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:12
+#: kallithea/templates/admin/user_groups/user_groups.html:24
+msgid "Add User Group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
+msgid "Short, optional description for this user group."
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:5
+#, python-format
+msgid "%s user group settings"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:33
+msgid "Show Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
+#, python-format
+msgid "User Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
+msgid "Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
+#: kallithea/templates/data_table/_dt_elements.html:102
+#, python-format
+msgid "Confirm to delete this user group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
+msgid "Delete this user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
+msgid "No members yet"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
+msgid "Chosen group members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
+msgid "Available members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_groups.html:5
+msgid "User Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:10
+#: kallithea/templates/admin/users/user_edit.html:11
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
+msgid "Users"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:12
+#: kallithea/templates/admin/users/users.html:23
+msgid "Add User"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:41
+msgid "Password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:5
+#, python-format
+msgid "%s user settings"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:30
+msgid "Emails"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:1
+#, python-format
+msgid "User: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_profile.html:32
+msgid "Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:9
+#: kallithea/templates/admin/users/users.html:41
+msgid "Last Login"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:10
+msgid "Member of User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:90
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:23
+msgid "Delete this user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_ips.html:7
+#, python-format
+msgid "Inherited from %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:39
+msgid "Name in Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:53
+msgid "New password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:5
+msgid "Users Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:44
+msgid "Auth Type"
+msgstr ""
+
+#: kallithea/templates/base/base.html:16
+#, python-format
+msgid "Server instance: %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:28
+msgid "Support"
+msgstr ""
+
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
+msgid "Mercurial repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
+msgid "Git repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:115
+msgid "Create Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
+msgid "Summary"
+msgstr ""
+
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
+msgid "Changelog"
+msgstr ""
+
+#: kallithea/templates/base/base.html:133
+#: kallithea/templates/files/files.html:11
+msgid "Files"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
+msgid "Options"
+msgstr ""
+
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
+msgid "Compare Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:158
+msgid "Compare"
+msgstr ""
+
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
+#: kallithea/templates/search/search.html:14
+#: kallithea/templates/search/search.html:67
+msgid "Search"
+msgstr ""
+
+#: kallithea/templates/base/base.html:164
+msgid "Unlock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:166
+msgid "Lock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
+msgid "Unfollow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:178
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
+msgid "Create Pull Request"
+msgstr ""
+
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:313
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:315
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:316
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:321
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:360
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:369
+msgid "Login to Your Account"
+msgstr ""
+
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
+msgid "Log Out"
+msgstr ""
+
+#: kallithea/templates/base/base.html:524
+msgid "Parent rev."
+msgstr ""
+
+#: kallithea/templates/base/base.html:533
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
+msgid "Inherit defaults"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:15
+#, python-format
+msgid ""
+"Select to inherit global settings, IP whitelist and permissions from the "
+"%s."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:23
+msgid "Create repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:33
+msgid "Create user groups"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:37
+msgid "Select this option to allow user group creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:43
+msgid "Fork repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:47
+msgid "Select this option to allow repository forking for this user"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:13
+#: kallithea/templates/changelog/changelog.html:41
+msgid "Show"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:22
+msgid "No permissions defined yet"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:30
+#: kallithea/templates/base/perms_summary.html:55
+msgid "Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:32
+#: kallithea/templates/base/perms_summary.html:57
+msgid "Edit Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:92
+msgid "No permission defined"
+msgstr ""
+
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:30
+msgid "Unable to post"
+msgstr ""
+
+#: kallithea/templates/base/root.html:31
+msgid "Add Another Comment"
+msgstr ""
+
+#: kallithea/templates/base/root.html:32
+msgid "Stop following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:33
+msgid "Start following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:34
+msgid "Group"
+msgstr ""
+
+#: kallithea/templates/base/root.html:35
+msgid "Loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:36
+msgid "loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:37
+msgid "Search truncated"
+msgstr ""
+
+#: kallithea/templates/base/root.html:38
+msgid "No matching files"
+msgstr ""
+
+#: kallithea/templates/base/root.html:39
+msgid "Open New Pull Request from {0}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:40
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
+msgid "Specify changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:23
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changelog/changelog.html:47
+msgid "Clear selection"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:54
+msgid "Go to tip of repository"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
+#, python-format
+msgid "Compare fork with %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:61
+#, python-format
+msgid "Compare fork with parent repository (%s)"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:65
+#: kallithea/templates/files/files.html:29
+msgid "Branch filter:"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:221
+msgid "There are no changes yet"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:4
+#: kallithea/templates/changeset/changeset.html:77
+msgid "Removed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:5
+#: kallithea/templates/changeset/changeset.html:78
+msgid "Changed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:6
+#: kallithea/templates/changeset/changeset.html:79
+#: kallithea/templates/changeset/diff_block.html:38
+msgid "Added"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:8
+#: kallithea/templates/changelog/changelog_details.html:9
+#: kallithea/templates/changelog/changelog_details.html:10
+#: kallithea/templates/changeset/changeset.html:81
+#: kallithea/templates/changeset/changeset.html:82
+#: kallithea/templates/changeset/changeset.html:83
+#, python-format
+msgid "Affected %s files"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%s committed on %s"
+msgid "%s comments"
+msgstr "%s committed den %s"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:8
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:34
+msgid "Changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
+msgid "Raw diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:46
+msgid "Patch diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
+msgid "Download diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
+msgid "Merge"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:96
+msgid "Grafted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:102
+msgid "Transplanted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:108
+msgid "Replaced by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:122
+msgid "Preceded by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
+#, python-format
+msgid "%s file changed"
+msgid_plural "%s files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
+#, python-format
+msgid "%s file changed with %s insertions and %s deletions"
+msgid_plural "%s files changed with %s insertions and %s deletions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Show full diff anyway"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "%s committed on %s"
+msgid "comment"
+msgstr "%s committed den %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:21
+msgid "on pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:22
+msgid "No title"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:24
+msgid "on this changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+msgid "Delete comment?"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
+msgid "Status change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
+msgid "Comment"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "Login now"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:141
+msgid "Hide"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:153
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
+msgid "%d inline"
+msgid_plural "%d inline"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
+msgid "%d general"
+msgid_plural "%d general"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
+msgid "Files affected"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
+msgid "Show merge diff"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:54
+msgid "is"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
+msgid "%s changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:56
+msgid "behind"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:6
+#: kallithea/templates/compare/compare_diff.html:8
+#, python-format
+msgid "%s Compare"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:13
+#: kallithea/templates/compare/compare_diff.html:41
+msgid "Compare Revisions"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:39
+msgid "Swap"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:48
+msgid "Compare revisions, branches, bookmarks, or tags."
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
+#, python-format
+msgid "Showing %s commit"
+msgid_plural "Showing %s commits"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/compare/compare_diff.html:95
+msgid "Show full diff"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:23
+msgid "Public repository"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:29
+msgid "Repository creation in progress..."
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:42
+msgid "No changesets yet"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:76
+msgid "Creating"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
+#, python-format
+msgid "Hello %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:16
+msgid "We have received a request to reset the password for your account."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
+msgid "To set a new password, click the following link"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "Pull-forespørgsel %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:15
+#, python-format
+msgid "%s File side-by-side diff"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:19
+#: kallithea/templates/files/file_diff.html:8
+msgid "File diff"
+msgstr ""
+
+#: kallithea/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File Diff"
+msgstr ""
+
+#: kallithea/templates/files/files.html:4
+#: kallithea/templates/files/files.html:77
+#, python-format
+msgid "%s Files"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:4
+#, python-format
+msgid "%s Files Add"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
+#: kallithea/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:41
+msgid "Enter filename..."
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
+msgid "or"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+msgid "Upload File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:47
+msgid "Create New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:53
+msgid "New file type"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:64
+#: kallithea/templates/files/files_delete.html:34
+#: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
+msgid "Commit Changes"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:37
+msgid "Search File List"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:42
+msgid "Loading file list..."
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
+msgid "Size"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:53
+msgid "Last Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:54
+msgid "Last Modified"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:55
+msgid "Last Committer"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:4
+#, python-format
+msgid "%s Files Delete"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:12
+#: kallithea/templates/files/files_delete.html:30
+msgid "Delete file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:4
+#, python-format
+msgid "%s File Edit"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:21
+msgid "Edit file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
+msgid "Show Annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
+msgid "Source"
+msgstr ""
+
+#: kallithea/templates/files/files_history_box.html:2
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/files/files_source.html:6
+msgid "Diff to Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:7
+msgid "Show at Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:10
+msgid "Show Authors"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:26
+msgid "Show Source"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:41
+msgid "Deleting files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:58
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:73
+msgid "Show as raw."
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:23
+msgid "Go Back"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:24
+msgid "No files at given path"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:9
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
+msgid "Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers_data.html:9
+msgid "Started following -"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:5
+#, python-format
+msgid "Fork repository %s"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:25
+msgid "Fork name"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:53
+msgid "Default revision for files page, downloads, whoosh, and readme."
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:58
+msgid "Private"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:66
+msgid "Copy permissions"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:69
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:75
+msgid "Update after clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:78
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:85
+msgid "Fork this Repository"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:9
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
+msgid "Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:14
+msgid "Forked"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:24
+msgid "There are no forks yet"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
+msgid "RSS journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:34
+msgid "My Repositories"
+msgstr ""
+
+#: kallithea/templates/journal/journal_data.html:42
+msgid "No entries yet"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:10
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:11
+msgid "RSS public journal feed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:4
+#: kallithea/templates/pullrequests/pullrequest.html:8
+msgid "New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:26
+#: kallithea/templates/pullrequests/pullrequest_data.html:15
+#: kallithea/templates/pullrequests/pullrequest_show.html:29
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
+msgid "Title"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:28
+msgid "Summarize the changes - or leave empty"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
+msgid "Write a short description on this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:40
+msgid "Changeset flow"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:46
+msgid "Origin repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
+msgid "Destination repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:6
+msgid "No entries"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:14
+msgid "Vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:18
+msgid "From"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:19
+msgid "To"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:28
+#, python-format
+msgid "You voted: %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:30
+msgid "You didn't vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:35
+msgid "(no title)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:37
+#: kallithea/templates/pullrequests/pullrequest_show.html:31
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
+msgid "Closed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:67
+msgid "Delete Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:68
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:70
+#, python-format
+msgid "Confirm again to delete this pull request with %s comments"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:6
+#, python-format
+msgid "%s Pull Request %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:10
+#, python-format
+msgid "Pull request %s from %s#%s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
+msgid "Summarize the changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
+msgid "Pull request status calculated from votes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
+msgid "Origin"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
+msgid "on"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
+msgid "Target"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
+msgid "Pull changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
+msgid "Current revision - no change"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
+msgid "Pull Request Content"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:6
+#, python-format
+msgid "%s Pull Requests"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:11
+#, python-format
+msgid "Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:13
+#, python-format
+msgid "Pull Requests to '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
+msgid "Open New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
+#, python-format
+msgid "Show Pull Requests to %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
+#, python-format
+msgid "Show Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:28
+msgid "Hide closed pull requests (only show open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:30
+msgid "Show closed pull requests (in addition to open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
+msgid "Pull Requests Created by Me"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
+msgid "Pull Requests I Participate In"
+msgstr ""
+
+#: kallithea/templates/search/search.html:6
+#, python-format
+msgid "%s Search"
+msgstr ""
+
+#: kallithea/templates/search/search.html:8
+#: kallithea/templates/search/search.html:16
+msgid "Search in All Repositories"
+msgstr ""
+
+#: kallithea/templates/search/search.html:47
+msgid "Search term"
+msgstr ""
+
+#: kallithea/templates/search/search.html:54
+msgid "Search in"
+msgstr ""
+
+#: kallithea/templates/search/search.html:56
+msgid "File contents"
+msgstr ""
+
+#: kallithea/templates/search/search.html:57
+msgid "Commit messages"
+msgstr ""
+
+#: kallithea/templates/search/search.html:58
+msgid "File names"
+msgstr ""
+
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
+msgid "Permission denied"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:4
+#, python-format
+msgid "%s Statistics"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:16
+#: kallithea/templates/summary/summary.html:36
+#, python-format
+msgid "%s ATOM feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:17
+#: kallithea/templates/summary/summary.html:37
+#, python-format
+msgid "%s RSS feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
+msgid "Enable"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:38
+msgid "Stats gathered: "
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
+msgid "files"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
+msgid "Show more"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:405
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:406
+msgid "files added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:407
+msgid "files changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:408
+msgid "files removed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:410
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:411
+msgid "file added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:412
+msgid "file changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:413
+msgid "file removed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:5
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:14
+#, python-format
+msgid "Repository locked by %s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:16
+msgid "Repository unlocked"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:22
+msgid "Fork of"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:27
+msgid "Clone from"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:84
+msgid "Trending files"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:98
+msgid "Download"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:101
+msgid "There are no downloads yet"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:103
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:109
+msgid "Download as zip"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:113
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:115
+msgid "With subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
+msgid "Feed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:175
+msgid "Latest Changes"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
+#, python-format
+msgid "Readme file from revision %s:%s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:298
+#, python-format
+msgid "Download %s as %s"
+msgstr ""
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "Der er intet indekseret til at søge i. Kør whoosh indexer"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP-adresse %s er ikke tilladt"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s kommenterede på changeset %(age)s"
+
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s sendte besked %(age)s"
+
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s har nævnt dig %(age)s"
+
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s er registreret i Kallithea %(age)s"
+
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s har åbnet en ny pull-forespørgsel %(age)s"
+
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s kommenterede på pull-forespørgsel %(age)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s kommenterede på changeset %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s sendte en besked %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s har nævnt dig %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s er registreret i Kallithea %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s åbnede en ny pull-forespørgsel %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s kommenterede på en pull-forespørgsel %(when)s"
--- a/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/de/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,103 +7,130 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2018-12-04 20:10+0000\n"
-"Last-Translator: ssantos <ssantos@web.de>\n"
-"Language-Team: German <https://hosted.weblate.org/projects/kallithea/stable/"
-"de/>\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2019-03-14 01:03+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: German <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/de/>\n"
 "Language: de\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 3.4-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"X-Generator: Weblate 3.5.1\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Es gibt noch keine Änderungssätze"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Keine"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(geschlossen)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Zeige unsichtbare Zeichen"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignoriere unsichtbare Zeichen"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Erhöhe diff-Kontext auf %(num)s Zeilen"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "Keine Berechtigung zum Ändern des Status des Pull Requests"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Pull-Request %s erfolgreich gelöscht"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Die angegebene Version existiert nicht in diesem Repository"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-"Eine Änderung des Status eines Änderungssatzes, der mit einem geschlossen"
-" Pull-Request assoziert ist, ist nicht erlaubt"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Änderungssätze auswählen"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Gehe zum Tip des Repositorys"
+
+#: kallithea/controllers/compare.py:72
+#, fuzzy
+#| msgid "Cannot compare repositories without using common ancestor"
+msgid "Cannot compare repositories of different types"
+msgstr ""
+"Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht "
+"möglich"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr "Kann leeren diff nicht anzeigen"
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr "Es konnte kein Vorfahre für den merge diff gefunden werden"
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr "Es wurden mehrere merge Vorfahren für den merge Vergleich gefunden"
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
-"Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht"
-" möglich"
+"Ohne einen gemeinsamen Vorfahren ist ein Vergleich der Repositories nicht "
+"möglich"
+
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "Keine Rückmeldung"
 
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr "Keine Rückmeldung"
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr "Unbekannter Fehler"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Die Anfrage konnte wegen ungültiger Syntax vom Server nicht ausgewertet "
 "werden."
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Unauthorisierter Zugang zur Ressource"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Du hast keine Rechte, um diese Seite zu betrachten"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Die Ressource konnte nicht gefunden werden"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -111,371 +138,366 @@
 "Aufgrund einer unerwarteten Gegebenheit konnte der Server diese Anfrage "
 "nicht vollenden."
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "Änderungen im %s Repository"
-
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr "%s %s Feed"
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Der Änderungssatz war zu groß und wurde abgeschnitten..."
-
-#: kallithea/controllers/feed.py:91
+#: kallithea/controllers/feed.py:63
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s committed am %s"
 
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Der Änderungssatz war zu groß und wurde abgeschnitten..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr "%s %s Feed"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Änderungen im %s Repository"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Hier klicken, um eine neue Datei hinzuzufügen"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Es gibt hier noch keine Dateien. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr "%s auf %s"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Dieses Repository ist von %s am %s gesperrt worden"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 #, fuzzy
-#| msgid "You can only delete files with revision being a valid branch "
 msgid "You can only delete files with revision being a valid branch"
-msgstr "Du kannst nur Dateien löschen, deren Revision ein gültiger Branch ist "
-
-#: kallithea/controllers/files.py:328
+msgstr ""
+"Dateien können nur gelöscht werden, deren Revision ein gültiger Branch ist"
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Datei %s via Kallithea gelöscht"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Datei %s erfolgreich gelöscht"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Während des Commits trat ein Fehler auf"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 #, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
 msgid "You can only edit files with revision being a valid branch"
-msgstr "Du kannst nur Dateien bearbeiten, deren Revision ein gültiger Branch ist "
-
-#: kallithea/controllers/files.py:391
+msgstr ""
+"Dateien können nur editiert werden, deren Revision ein gültiger Branch ist"
+
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Datei %s via Kallithea editiert"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Keine Änderungen"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Der Commit zu %s war erfolgreich"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Datei via Kallithea hinzugefügt"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Kein Inhalt"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Kein Dateiname"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "Der Ort muss ein relativer Pfad sein und darf nicht .. enthalten"
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Downloads gesperrt"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Unbekannte Revision %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Leeres Repository"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Unbekannter Archivtyp"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Änderungssätze"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Entwicklungszweige"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Tags"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "Während des Forkens des Repositorys trat ein Fehler auf: %s"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Gruppen"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Repositories"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Zweig"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Geschlossene Branches"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Marke"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Lesezeichen"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Öffentliches Logbuch"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Logbuch"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Falsches Captcha"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Sie haben sich erfolgreich bei Kallithea registriert"
-
-#: kallithea/controllers/login.py:197
-#, fuzzy
-#| msgid "Your password reset link was sent"
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Sie haben sich erfolgreich bei %s registriert"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr "Ihr Passwort Zurücksetzen link wurde versendet"
-
-#: kallithea/controllers/login.py:246
-#, fuzzy
-#| msgid "Password reset link"
+msgstr "Ihr Link um das Passwort zurückzusetzen wurde versendet"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
-msgstr "Link zum Zurücksetzen des Passworts"
-
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+msgstr "Ungültiges Token zum Zurücksetzen des Passworts."
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Erfolgreich Kennwort geändert"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "Ungültigen Begutachter \"%s\" angegeben"
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (geschlossen)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Änderungssatz"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "Spezial"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "Branches anderer"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Lesezeichen"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Fehler beim Erstellen des Pull-Requests: %s"
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Keine Beschreibung"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "Es wurde erfolgreich ein neuer Pullrequest eröffnet"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr "Ungültigen Begutachter \"%s\" angegeben"
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "Während des Erstellens des Pull Requests trat ein Fehler auf"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr "Fehlende Changesets seit letztem Pull Request:"
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr "Neue Changesets in %s %s seit dem letzten Pull Request:"
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-"Eltern-Commit hat sich nicht verändert - zeige Diff seit voriger Version:"
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-"Dieser Pull Request basiert auf einer anderen %s Revision. Daher ist kein"
-" Simple Diff verfügbar."
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr "Keine Änderungen seit der letzten Version gefunden in %s %s."
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "Geschlossen, ersetzt durch %s."
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Es wurde erfolgreich ein neuer Pullrequest eröffnet"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
 msgstr "Pull Request Update erstellt"
 
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr "Es wurden inzwischen folgende Begutachter hinzugefügt: %s"
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr "Es wurden inzwischen folgende Begutachter entfernt: %s"
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Keine Beschreibung"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "Pull Request aktualisiert"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Erfolgreich Pull-Request gelöscht"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr "Die Revision %s konnte in %s nicht gefunden werden"
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Dieser Pull Request wurde bereits in %s integriert."
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 "Dieser Pull Request wurde geschlossen und kann daher nicht aktualisiert "
 "werden."
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr "Dieser Pull Request kann mit Änderungen in %s aktualisiert werden:"
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
+#: kallithea/controllers/pullrequests.py:543
+#, fuzzy, python-format
+#| msgid "The following changes are available on %s:"
+msgid "The following additional changes are available on %s:"
+msgstr "Die folgenden Änderungen sind verfügbar unter %s:"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
 msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
 
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Hinweis: Branch %s hat einen anderen Head: %s."
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
 msgstr "Git Pull Request unterstützen bisher keine Updates."
 
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr "Keine Berechtigung zum Ändern des Status des Pull Requests"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Schließen."
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "Keine Changesets gefunden, um den Pull Request zu aktualisieren."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+"Der diff kann nicht angezeigt werden. Die Pull Request Revisionen konnten "
+"nicht gefunden werden."
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
-msgstr "Ungültige Suchanfrage. Versuchen sie es in Anführungzeichen zu setzen."
+msgstr ""
+"Ungültige Suchanfrage. Versuchen sie es in Anführungzeichen zu setzen."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Es gibt keinen durchsuchbaren Index. Bitte den Whoosh Indizierer ausführen"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Während der Suchoperation trat ein Fehler auf."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "Es stehen noch keine Daten zur Verfügung"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "Statistiken sind deaktiviert für dieses Repository"
 
@@ -487,151 +509,153 @@
 msgid "error occurred during update of auth settings"
 msgstr "Fehler bei der Änderung der Anmeldeeinstellungen aufgetreten"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Standardeinstellungen erfolgreich geupdated"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "Ein Fehler trat beim updaten der Standardeinstellungen auf"
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "Immer"
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr "Immer"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 Minuten"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 Minuten"
-
-#: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
 msgid "1 hour"
 msgstr "1 Stunde"
 
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 Tag"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 Monat"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Lebenszeit"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Ein fehler trat auf bei der Erstellung des gist"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "gist %s gelöscht"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
 msgstr "Ungeändert"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr "Erfolgreich Kerninhalt aktualisiert"
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr "Erfolgreich Kerndaten aktualisiert"
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Fehler beim Aktualisieren der Kerndaten %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Sie können diesen Benutzer nicht editieren, da er von entscheidender "
 "Bedeutung für die ganze Applikation ist"
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Ihr Account wurde erfolgreich aktualisiert"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Fehler beim Aktualisieren der Benutzer %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Fehler bei der Änderung des Kennworts"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "Die EMail Addresse %s wurde zum Benutzer hinzugefügt"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Währen der Speicherung der EMail Addresse trat ein Fehler auf"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "Die EMail Addresse wurde vom Benutzer entfernt"
 
-#: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
 msgid "API key successfully created"
 msgstr "API Key wurde erfolgreich erstellt"
 
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
 msgid "API key successfully reset"
 msgstr "API-Schlüssel erfolgreich zurückgesetzt"
 
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "API-Schlüssel erfolgreich gelöscht"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Lesen"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Schreiben"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -639,281 +663,273 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Admin"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Deaktiviert"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Erlaubt mit manueller Kontoaktivierung"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Erlaubt mit automatischer Kontoaktivierung"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Manuelle Aktivierung externen Kontos"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Automatische Aktivierung externen Kontos"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Deaktiviert"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Erlaubt mit manueller Kontoaktivierung"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Erlaubt mit automatischer Kontoaktivierung"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Manuelle Aktivierung externen Kontos"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Automatische Aktivierung externen Kontos"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Aktiviert"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr "Globale Berechtigungen erfolgreich geändert"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Fehler bei der Änderung der globalen Berechtigungen"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Fehler bei der Erstellung der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Repositoriumsgruppe %s erstellt"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Repositoriumsgruppe %s aktualisiert"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Fehler bei der Aktualisierung der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Die Gruppe enthält %s Repositorys und kann nicht gelöscht werden"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Diese Gruppe enthält %s Untergruppen und kann nicht gelöscht werden"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Repositoriumsgruppe %s entfernt"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Fehler beim Löschen der Repositoriumsgruppe %s"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Als Administrator kann man sich keine Berechtigungen entziehen"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Berechtigungen der Repositoriumsgruppe aktualisiert"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "Fehler beim Entzug der Berechtigungen"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Fehler beim Erstellen des Repositoriums %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Repositorium %s von %s erstellt"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "Aufgespaltenes Repositorium %s zu %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Repositorium erzeugt %s"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Repository %s wurde erfolgreich aktualisiert"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Fehler bei der Aktualisierung des Repositoriums %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "%s Spaltung abgetrennt"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "%s Spaltung gelöscht"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Repositorium %s gelöscht"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "%s konnte nicht gelöscht werden, da es noch Forks besitzt"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Beim Löschen von %s trat ein Fehler auf"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Repositoriumsberechtigungen aktualisiert"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr "Feldvalidierung fehlgeschlagen: %s"
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
 msgstr "Fehler während der Erzeugung des Feldes"
 
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Fehler beim Entfernen des Feldes"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Keine Abspaltung --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Sichtbarkeit des Repositorys im Öffentlichen Logbuch aktualisiert"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
 "Es trat ein Fehler während der Aktualisierung der Sicherbarkeit dieses "
 "Repositorys im Öffentlichen Logbuch auf"
 
-#: kallithea/controllers/admin/repos.py:512
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Nichts"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Markiere Repository %s als Abzweig von Repository %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "Während dieser operation trat ein Fehler auf"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "Repository wurde gesperrt"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "Repository nicht mehr gesperrt"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "Fehler beim Entsperren"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "Cache Entfernung war erfolgreich"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "Währen der Cache Invalidierung trat ein Fehler auf"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "Von entferntem Ort übertragen"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 "Es trat ein Fehler auf während das Repository von einem Entfernten "
 "Speicherort übertragen wurde"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "Während des löschens der Repository Statistiken trat ein Fehler auf"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "VCS-Einstellungen aktualisiert"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -921,354 +937,367 @@
 "hgsubversion-Unterstützung konnte nicht aktiviert werden. Die "
 "\"hgsubversion\"-Bibliothek fehlt"
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr ""
 "Ein Fehler ist während der Aktualisierung der Applikationseinstellungen "
 "aufgetreten"
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
-"Die Repositories wurden erfolgreich überprüft. Hinzugefügt: %s. Entfernt:"
-" %s."
-
-#: kallithea/controllers/admin/settings.py:226
+"Die Repositories wurden erfolgreich überprüft. Hinzugefügt: %s. Entfernt: "
+"%s."
+
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Invalidate Repository Cache"
 msgid "Invalidated %s repositories"
 msgstr "Ungültiger Repositorycache"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "Anwendungseinstellungen aktualisiert"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Visualisierungseinstellungen aktualisiert"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 "Es ist ein Fehler während der Aktualisierung der Layouteinstellung "
 "aufgetreten"
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr "Bitte gebe eine E-Mailadresse an"
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr "Task zum Versenden von E-Mails erstellt"
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Es stehen noch keine Daten zur Verfügung"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Neuer Hook hinzugefügt"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Die Hooks wurden aktutalisiert"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "Während der Erzeugung des Hooks ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh Reindizierungs Aufgabe wurde zur Ausführung geplant"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "Nutzergruppe %s erstellt"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
-msgstr "Es ist ein Fehler während der Erstellung der Nutzergruppe %s aufgetreten"
-
-#: kallithea/controllers/admin/user_groups.py:201
+msgstr ""
+"Es ist ein Fehler während der Erstellung der Nutzergruppe %s aufgetreten"
+
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "Aktualisierte Nutzergruppe %s"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
-msgstr "Während des Updates der Benutzergruppe %s ist ein Fehler aufgetreten"
-
-#: kallithea/controllers/admin/user_groups.py:242
+msgstr ""
+"Während des Updates der Benutzergruppe %s ist ein Fehler aufgetreten"
+
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "Die Nutzergruppe wurde erfolgreich entfernt"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr "Während des Löschens der Benutzergruppe ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "Zielgruppe kann nicht die gleiche Gruppe sein"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "Berechtigungen der Benutzergruppe wurden aktualisiert"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "Berechtigungen wurden aktualisiert"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
-msgstr "Es ist ein Fehler während des Speicherns der Berechtigungen aufgetreten"
-
-#: kallithea/controllers/admin/users.py:134
+msgstr ""
+"Es ist ein Fehler während des Speicherns der Berechtigungen aufgetreten"
+
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "Nutzer %s erstellt"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Während des Erstellens des Benutzers %s ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "Der Benutzer wurde erfolgreich aktualisiert"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "Der Nutzer wurde erfolgreich gelöscht"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "Während der Löschen des Benutzers trat ein Fehler auf"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr "Der Standard-Benutzer kann nicht bearbeitet werden"
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Die IP-Adresse %s wurde zur Nutzerwhitelist hinzugefügt"
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Während des Speicherns der IP-Adresse ist ein Fehler aufgetreten"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr "IP-Adresse wurde von der Nutzerwhitelist entfernt"
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IP-Adresse %s ist nicht erlaubt"
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+"Sie müssen ein Registrierter Nutzer sein um diese Aktion durchzuführen"
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr "Sie müssen sich anmelden um diese Seite aufzurufen"
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr "Ungültiger API Key"
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr "Es wurde ein CSRF Leck entdeckt. Alle Formular Token sind abgelaufen"
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr "Sie müssen ein Registrierter Nutzer sein um diese Aktion durchzuführen"
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr "Sie müssen sich anmelden um diese Seite aufzurufen"
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+"Es wurde ein CSRF Leck entdeckt. Alle Formular Token sind abgelaufen"
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr "Das Repository konnte nicht im Filesystem gefunden werden"
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "Änderungssatz nicht gefunden"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Binäre Datei"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Der Änderungssatz war zu groß und wurde abgeschnitten, benutzen sie das "
 "Diff Menü um die Unterschiede anzuzeigen"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Keine Änderungen erkannt"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Branch %s gelöscht"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Tag %s erstellt"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Änderungssatz nicht gefunden"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Zeige alle Kombinierten Änderungensätze %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr "Vergleichsansicht"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "und"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s mehr"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "revisionen"
 
-#: kallithea/lib/helpers.py:723
-#, fuzzy, python-format
+#: kallithea/lib/helpers.py:725
+#, python-format
 msgid "Fork name %s"
 msgstr "Fork Name %s"
 
-#: kallithea/lib/helpers.py:743
-#, fuzzy, python-format
+#: kallithea/lib/helpers.py:746
+#, python-format
 msgid "Pull request %s"
-msgstr "Pull Request #%s"
-
-#: kallithea/lib/helpers.py:753
+msgstr "Pull Request %s"
+
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[gelöscht] Repository"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[erstellt] Repository"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[erstellt] Repository als Fork"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[forked] Repository"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[aktualisiert] Repository"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "Archiv von Repository [heruntergeladen]"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "Repository [gelöscht]"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "Benutzer [erstellt]"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "Benutzer [akutalisiert]"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "Benutzergruppe [erstellt]"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "Benutzergruppe [aktualisiert]"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "Revision [kommentiert] in Repository"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "Pull Request [kommentiert] für"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "Pull Request [geschlossen] für"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[Pushed] in"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[via Kallithea] in Repository [committed]"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[Pulled von Remote] in Repository"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[Pulled] von"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[Following gestartet] für Repository"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[Following gestoppt] für Repository"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " und %s weitere"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Keine Dateien"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "neue Datei"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "entf"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "umbenennen"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1279,758 +1308,348 @@
 "es im Dateisystem erstellt oder umbenannt. Bitte starten sie die "
 "Applikation erneut um die Repositories neu zu Indizieren"
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d Jahr"
 msgstr[1] "%d Jahre"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d Monat"
 msgstr[1] "%d Monate"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d Tag"
 msgstr[1] "%d Tage"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d Stunde"
 msgstr[1] "%d Stunden"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d Minute"
 msgstr[1] "%d Minuten"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d Sekunde"
 msgstr[1] "%d Sekunden"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "in %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "vor %s"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "in %s und %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s und %s her"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "jetzt gerade"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Kein Zugriff auf Repository"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Lesender Zugriff auf Repository"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Schreibdender Zugriff auf Repository"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Administrativer Zugang zum Repository"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Repository Gruppe hat Keinen Zugriff"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Repository Gruppe hat lesenden Zugriff"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Repository Gruppe hat schreibenden Zugriff"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Repository Gruppe hat Administrativen Zugriff"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
+#: kallithea/model/comment.py:68
+#, python-format
+msgid "on line %s"
+msgstr "in Zeile %s"
+
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
+msgid "[Mention]"
+msgstr "[Mention]"
+
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "höchste Ebene"
+
+#: kallithea/model/db.py:1703
 msgid "Kallithea Administrator"
 msgstr "Kallithea Administrator"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Erstellung eines Repositorys deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Erstellung eines Repositorys aktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Forking eines Repositorys deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Forking eines Repositorys aktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Registrierung deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Registrierung neuer Benutzer in Kallithea mit manueller Aktivierung"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Registrierung neuer Benutzer in Kallithea mit Automatischer Aktivierung"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Nicht Begutachtet"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Akzeptiert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Abgelehnt"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "In Begutachtung"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "höchste Ebene"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Kein Zugriff für Repositorygruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Lesezugriff für Repositorygruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Schreibzugriff für Repositorygruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Administrativer Zugriff für Repositorygruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Kein Zugriff für Benutzergruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Lesezugriff für Benutzergruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Nutzergruppe Schreibzugriff"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Administrativer Zugriff für Benutzergruppe"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Erstellung von Repositorygruppen deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Erstellung von Repositorygruppen aktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Erstellung von Benutzergruppen deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Erstellung von Benutzergruppen aktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Registrierung deaktiviert"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Benutzerregistrierung mit manueller Kontoaktivierung"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Benutzerregistrierung mit automatische Kontoaktivierung"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
+#: kallithea/model/db.py:1705
+msgid "Default user has no access to new repositories"
+msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repositories"
+
+#: kallithea/model/db.py:1706
+msgid "Default user has read access to new repositories"
+msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repositories"
+
+#: kallithea/model/db.py:1707
+msgid "Default user has write access to new repositories"
+msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Repositories"
+
+#: kallithea/model/db.py:1708
+msgid "Default user has admin access to new repositories"
+msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Repositories"
+
+#: kallithea/model/db.py:1710
+msgid "Default user has no access to new repository groups"
+msgstr ""
+"Der Standard-Benutzer hat keinen Zugriff auf neue Repository-Gruppen"
+
+#: kallithea/model/db.py:1711
+msgid "Default user has read access to new repository groups"
+msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repository-Gruppen"
+
+#: kallithea/model/db.py:1712
+msgid "Default user has write access to new repository groups"
+msgstr "Der Standard-Benutzer Schreibrechte auf neuen Repository-Gruppen"
+
+#: kallithea/model/db.py:1713
+msgid "Default user has admin access to new repository groups"
+msgstr "Der Standard-Benutzer Admin-Rechte auf neuen Repository-Gruppen"
+
+#: kallithea/model/db.py:1715
+msgid "Default user has no access to new user groups"
+msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Benutzer-Gruppen"
+
+#: kallithea/model/db.py:1716
+msgid "Default user has read access to new user groups"
+msgstr "Der Standard-Benutzer hat Leserechte auf neuen Benutzer-Gruppen"
+
+#: kallithea/model/db.py:1717
+msgid "Default user has write access to new user groups"
+msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Benutzer-Gruppen"
+
+#: kallithea/model/db.py:1718
+msgid "Default user has admin access to new user groups"
+msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Benutzer-Gruppen"
+
+#: kallithea/model/db.py:1720
+msgid "Only admins can create repository groups"
+msgstr "Nur Admins können Repository-Gruppen erstellen"
+
+#: kallithea/model/db.py:1721
+msgid "Non-admins can create repository groups"
+msgstr "Nicht-Admins können Repository-Gruppen erstellen"
+
+#: kallithea/model/db.py:1723
+msgid "Only admins can create user groups"
+msgstr "Nur Admins können Benutzer-Gruppen erstellen"
+
+#: kallithea/model/db.py:1724
+msgid "Non-admins can create user groups"
+msgstr "Nicht-Admins können Benutzer-Gruppen erstellen"
+
+#: kallithea/model/db.py:1726
+msgid "Only admins can create top level repositories"
+msgstr "Nur Admins können Repositories auf oberster Ebene erstellen"
+
+#: kallithea/model/db.py:1727
+msgid "Non-admins can create top level repositories"
+msgstr "Nicht-Admins können Repositories oberster Ebene erstellen"
+
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
 msgstr ""
 "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe "
 "aktiviert"
 
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
 msgstr ""
 "Erstellung von Repositories mit Schreibzugriff für Repositorygruppe "
 "deaktiviert"
 
-#: kallithea/model/comment.py:72
-#, python-format
-msgid "on line %s"
-msgstr "in Zeile %s"
-
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
-msgid "[Mention]"
-msgstr "[Mention]"
-
-#: kallithea/model/db.py:1668
-msgid "Default user has no access to new repositories"
-msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repositories"
-
-#: kallithea/model/db.py:1669
-msgid "Default user has read access to new repositories"
-msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repositories"
-
-#: kallithea/model/db.py:1670
-msgid "Default user has write access to new repositories"
-msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Repositories"
-
-#: kallithea/model/db.py:1671
-msgid "Default user has admin access to new repositories"
-msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Repositories"
-
-#: kallithea/model/db.py:1673
-msgid "Default user has no access to new repository groups"
-msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Repository-Gruppen"
-
-#: kallithea/model/db.py:1674
-msgid "Default user has read access to new repository groups"
-msgstr "Der Standard-Benutzer hat Leserechte auf neuen Repository-Gruppen"
-
-#: kallithea/model/db.py:1675
-msgid "Default user has write access to new repository groups"
-msgstr "Der Standard-Benutzer Schreibrechte auf neuen Repository-Gruppen"
-
-#: kallithea/model/db.py:1676
-msgid "Default user has admin access to new repository groups"
-msgstr "Der Standard-Benutzer Admin-Rechte auf neuen Repository-Gruppen"
-
-#: kallithea/model/db.py:1678
-msgid "Default user has no access to new user groups"
-msgstr "Der Standard-Benutzer hat keinen Zugriff auf neue Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1679
-msgid "Default user has read access to new user groups"
-msgstr "Der Standard-Benutzer hat Leserechte auf neuen Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1680
-msgid "Default user has write access to new user groups"
-msgstr "Der Standard-Benutzer hat Schreibrechte auf neuen Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1681
-msgid "Default user has admin access to new user groups"
-msgstr "Der Standard-Benutzer hat Admin-Rechte auf neuen Benutzer-Gruppen"
-
-#: kallithea/model/db.py:1683
-msgid "Only admins can create repository groups"
-msgstr "Nur Admins können Repository-Gruppen erstellen"
-
-#: kallithea/model/db.py:1684
-#, fuzzy
-msgid "Non-admins can create repository groups"
-msgstr "Nicht-Admins können Repository-Gruppen erstellen"
-
-#: kallithea/model/db.py:1686
-msgid "Only admins can create user groups"
-msgstr "Nur Admins können Benutzer-Gruppen erstellen"
-
-#: kallithea/model/db.py:1687
-msgid "Non-admins can create user groups"
-msgstr "Nicht-Admins können Benutzer-Gruppen erstellen"
-
-#: kallithea/model/db.py:1689
-#, fuzzy
-msgid "Only admins can create top level repositories"
-msgstr "Repositories oberster Ebene"
-
-#: kallithea/model/db.py:1690
-#, fuzzy
-msgid "Non-admins can create top level repositories"
-msgstr "Repositories oberster Ebene"
-
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr "Nur Admins können Repositories forken"
 
-#: kallithea/model/db.py:1696
-#, fuzzy
-#| msgid "Non-admins can can fork repositories"
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
-msgstr "Nicht-Admins können Repositories forken"
-
-#: kallithea/model/db.py:1699
-#, fuzzy
+msgstr "Nicht-Admins können Repositorys forken"
+
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Registrierung deaktiviert"
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr "Benutzerregistrierung mit manueller Kontoaktivierung"
 
-#: kallithea/model/db.py:1700
-#, fuzzy
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
-msgstr "Benutzerregistrierung mit automatische Kontoaktivierung"
-
-#: kallithea/model/db.py:2238
-#, fuzzy
+msgstr "Benutzerregistrierung mit automatischer Kontoaktivierung"
+
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr "Nicht Begutachtet"
 
-#: kallithea/model/db.py:2241
-#, fuzzy
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr "In Begutachtung"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "Akzeptiert"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Akzeptiert"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Bitte einen Benutzernamen eingeben"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Bitte einen Wert mit mindestens %(min)i Zeichen eingeben"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Bitte ein Passwort eingeben"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Bitte mindestens %(min)i Zeichen eingeben"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr "Name darf nicht nur Ziffern enthalten"
 
-#: kallithea/model/notification.py:255
-#, fuzzy, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
-
-#: kallithea/model/notification.py:256
-#, fuzzy, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s hat am %(when)s eine Nachricht gesendet"
-
-#: kallithea/model/notification.py:257
-#, fuzzy, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s hat Sie am %(when)s erwähnt"
-
-#: kallithea/model/notification.py:258
-#, fuzzy, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s hat sich am %(when)s bei Kallithea registriert"
-
-#: kallithea/model/notification.py:259
-#, fuzzy, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s hat am %(when)s einen neuen Pull Request eröffnet"
-
-#: kallithea/model/notification.py:260
+#: kallithea/model/notification.py:165
 #, fuzzy, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s hat am %(when)s eine Nachricht gesendet"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s hat Sie am %(when)s erwähnt"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s hat sich am %(when)s bei Kallithea registriert"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s hat am %(when)s einen neuen Pull Request eröffnet"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert"
-
-#: kallithea/model/notification.py:303
-#, fuzzy, python-format
-#| msgid "" "
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-"Kommentar für %(repo_name)s Changeset %(short_id)s in %(branch)s erstellt"
-" von %(comment_username)s"
-
-#: kallithea/model/notification.py:306
+#| msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+"Kommentar für %(repo_name)s Changeset %(short_id)s in %(branch)s erstellt "
+"von %(comment_username)s"
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Neuer Benutzer %(new_username)s registriert"
 
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-#| msgid "" "
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-"Review Request für %(repo_name)s Pull Request #%(pr_id)s von %(ref)s "
-"erstellt von %(pr_username)s"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-#| msgid "" "[Comment from %(comment_username)s] %(repo_name)s pull request "
-#| "%(pr_nice_id)s from %(ref)s"
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-"Kommentar von %(comment_username)s für %(repo_name)s Pull Request "
-"#%(pr_id)s von %(ref)s"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "Schließen"
 
-#: kallithea/model/pull_request.py:137
+#: kallithea/model/pull_request.py:76
+#, fuzzy, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s möchte ein Review des Pull Request #%(pr_id)s: %(pr_title)s"
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "Fehler beim Erstellen des Pull-Requests: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "Fehlende Changesets seit letztem Pull Request:"
+
+#: kallithea/model/pull_request.py:348
 #, fuzzy, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr "%(user)s möchte ein Review des Pull Request #%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/scm.py:812
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Neue Changesets in %s %s seit dem letzten Pull Request:"
+
+#: kallithea/model/pull_request.py:355
+#, fuzzy
+#| msgid "Ancestor didn't change - show diff since previous version:"
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr "Vorgänger unverändert - zeige Diff zu lezter Version:"
+
+#: kallithea/model/pull_request.py:362
+#, fuzzy, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"Dieser Pull Request basiert auf einer anderen %s Revision. Daher ist kein "
+"Simple Diff verfügbar."
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "No changes found on %s %s since previous version."
+msgid "No changes found on %s %s since previous iteration."
+msgstr "Keine Änderungen seit der letzten Version gefunden in %s %s."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "Letzter Tip"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "Neue Benutzerregistrierung"
 
-#: kallithea/model/user.py:256
+#: kallithea/model/user.py:253
 #, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Sie können diesen Benutzer nicht löschen, da er von entscheidender "
 "Bedeutung für die gesamte Applikation ist"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -2040,17 +1659,17 @@
 "nicht entfernt werden. Entweder muss der Besitzer geändert oder das "
 "Repository entfernt werden: %s"
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
 "Der Benutzer \"%s\" ist noch der Besitzer von %s Repositorygruppen und "
 "kann daher nicht entfernt werden. Entweder muss der Besitzer geändert "
 "oder die Repositorygruppen müssen entfernt werden: %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -2060,68 +1679,67 @@
 "nicht entfernt werden. Entweder muss der Besitzer geändert oder die "
 "Benutzergruppen müssen gelöscht werden: %s"
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "Link zum Zurücksetzen des Passworts"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 #, fuzzy
-#| msgid "Password reset link"
 msgid "Password reset notification"
 msgstr "Link zum Zurücksetzen des Passworts"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
-"Das Passwort für dein Konto %s wurde mit dem Formular zum Zurücksetzen des "
-"Passworts geändert."
-
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+"Das Passwort für dein Konto %s wurde mit dem Formular zum Zurücksetzen "
+"des Passworts geändert."
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "Eine leere Liste ist kein gültiger Wert"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "Benutezrname \"%(username)s\" existiert bereits"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, fuzzy, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "Benutzername \"%(username)s\" ist ungültig"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 #, fuzzy
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
 "Der Benutzername darf nur alphanumerische Zeichen, Unterstriche, Punkte "
 "oder Bindestriche enthalten und muss mit einem alphanumerischen Zeichen "
 "oder einem Unterstrich beginnen"
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr "Die Eingabe ist nicht gültig"
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "Benutzername \"%(username)s\" ist ungültig"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "Ungültiger Benutzergruppenname"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "Benutzergruppe \"%(usergroup)s\" existiert bereits"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
@@ -2130,69 +1748,68 @@
 "Unterstriche, Punkte oder Bindestriche enthalten und muss mit einem "
 "alphanumerischen Zeichen beginnen"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Kann diese Gruppe nicht als vorgesetzt setzen"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "Gruppe \"%(group_name)s\" existiert bereits"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Es gibt bereits ein Repository mit \"%(group_name)s\""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Üngültige(nicht ASCII) Zeichen im Passwort"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr "Ungültiges altes Passwort"
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Die Passwörter stimmen nicht überein"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 #, fuzzy
 msgid "Invalid username or password"
 msgstr "Ungültiges Passwort"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
-msgstr "Schlüssel  stimmt nicht überein"
-
-#: kallithea/model/validators.py:345
+msgstr "Schlüssel stimmt nicht überein"
+
+#: kallithea/model/validators.py:328
 #, fuzzy, python-format
 msgid "Repository name %(repo)s is not allowed"
-msgstr "Repository  Name \"%(repo)s\" ist verboten"
-
-#: kallithea/model/validators.py:347
+msgstr "Repository Name \"%(repo)s\" ist nicht erlaubt"
+
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Es gibt bereits ein Repository mit \"%(repo)s\""
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
-"Es gibt bereits ein Repository mit \"%(repo)s\" in der Gruppe "
-"\"%(group)s\""
-
-#: kallithea/model/validators.py:350
+"Es gibt bereits ein Repository mit \"%(repo)s\" in der Gruppe \"%(group)s"
+"\""
+
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Eine Repositorygruppe mit dem Namen \"%(repo)s\" existiert bereits"
 
-#: kallithea/model/validators.py:465
-#, fuzzy
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
-msgstr "privates Repository"
-
-#: kallithea/model/validators.py:466
+msgstr "Ungültige Repository-URL"
+
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
@@ -2200,43 +1817,44 @@
 "Ungültige Repository-URL. Es muss eine gültige http, https, ssh, svn+http "
 "oder svn+https URL sein"
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "Forke um den selben typ wie der Vorgesetze zu haben"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 "Du hast nicht die erforderlichen Berechtigungen, um in dieser Gruppe ein "
 "Repository zu erzeugen"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "keine Berechtigung, um ein Repository auf höchster Ebene anzulegen"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
-msgstr "Sie haben keine Berechtigung, um an diesem Ort ein Repository anzulegen"
-
-#: kallithea/model/validators.py:597
+msgstr ""
+"Sie haben keine Berechtigung, um an diesem Ort ein Repository anzulegen"
+
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr "Dieser Benutzername oder Benutzergruppenname ist nicht gültig"
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "Dies ist ein Ungültiger Pfad"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "Diese E-Mailaddresse ist bereits in Benutzung"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "E-MailAddresse \"%(email)s\" existiert nicht."
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
@@ -2244,254 +1862,158 @@
 "Das LDAP-Login-Attribut des CN muss angeben werden - Es ist der Name des "
 "Attributes äquivalent zu \"Benutzername\""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Bitte eine gültige IPv4- oder IPv6-Adresse angeben"
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 msgstr ""
 "Die Größe (in Bits) des Netzwerks muss im Bereich 0-32 liegen (nicht "
 "%(bits)r)"
 
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 "Der Name eines Schlüssels darf nur aus Buchstaben, Ziffern, Unterstrich "
 "und Bindestrich bestehen"
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "Dateiname darf kein Unterverzeichnis enthalten"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
-"Die Plug-Ins %(loaded)s und %(next_to_load)s exportieren beide den selben"
-" Namen"
-
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+"Die Plug-Ins %(loaded)s und %(next_to_load)s exportieren beide den selben "
+"Namen"
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr "Über"
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Übersichtsseite"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "Schnellfilter..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "Repositories"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "Repository hinzufügen"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "Repositorygruppe hinzufügen"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr "Du hast Adminrechte für diese Gruppe und kannst sie editieren"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "Repositorygruppe bearbeiten"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "Gruppenname"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Repository"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Beschreibung"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Name"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Letzte Änderung"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Tipp"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Besitzer"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "Klicken um Aufsteigend zu Sortieren"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "Klicken um Absteigend zu Sortieren"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Keine Repositories gefunden."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Datenfehler."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Lade..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Log In"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Log In in %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "Benutzername"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Passwort"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Login Speichern"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "Passowrt Vergessen?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Kein Account?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Einloggen"
 
@@ -2499,34 +2021,33 @@
 msgid "Password Reset"
 msgstr "Passwort zurücksetzen"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr "Setze dein Passwort auf %s zurück"
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr "Setze dein Passwort zurück"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "E-Mailadresse"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
-msgstr "Captcha"
-
-#: kallithea/templates/password_reset.html:46
+msgstr "CAPTCHA"
+
+#: kallithea/templates/password_reset.html:47
 msgid "Send Password Reset Email"
 msgstr "E-Mail zum Zurücksetzen des Passworts anfordern"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
-#| msgid "" "
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
@@ -2534,13 +2055,13 @@
 "Der Link zum Zurücksetzen des Passworts wird an die zum Benutzernamen "
 "zugehörige E-Mailaddresse gesendet."
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 "Du bist dabei, ein neues Passwort für die E-Mail-Adresse %s festzulegen."
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
@@ -2548,110 +2069,91 @@
 "Beachten Sie, dass Sie dafür die gleiche Browsersitzung verwenden müssen, "
 "mit der Sie das Zurücksetzen des Passworts beantragt haben."
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr "Code, den du in der E-Mail erhalten hast"
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
-#| msgid "New password"
 msgid "New Password"
 msgstr "Neues Passwort"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
-#| msgid "Confirm new password"
 msgid "Confirm New Password"
 msgstr "Bestätige neues Passwort"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr "Bestätigen"
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Registrieren"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr "Registrieren für %s"
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "Passwort erneut eingeben"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Vorname"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Nachname"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-Mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr "Registrierte Konten können ohne weitere Aktion genutzt werden."
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 "Bitte warten Sie auf die Aktivierung Ihres Benutzerkontos durch einen "
 "Administrator."
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Es gibt bisher keine Branches"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Geschlossene Branches"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Es gibt bisher keine Tags"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Es gibt bisher keine Lesezeichen"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr "Admin-Logbuch"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "Logbuch filter..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr "Filter"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2659,30 +2161,16 @@
 msgstr[1] "%s Einträge"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Aktion"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Repository"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Datum"
 
@@ -2690,7 +2178,7 @@
 msgid "From IP"
 msgstr "Von IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "Es sind bisher keine Aktionen passiert"
 
@@ -2699,19 +2187,19 @@
 msgstr "Authentifizierungseinstellungen"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "Authentifizierung"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr "Plug-Ins zur Authentifizierung"
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr "Aktivierte Plugins"
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 #, fuzzy
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
@@ -2721,57 +2209,57 @@
 "der Reihenfolge, in der Kallithea die Plug-Ins zur Authentifizierung des "
 "Benutzers verwendet"
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr "Verfügbare mitgelieferte Plug-Ins"
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr "Plugin"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Speichern"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr "Repositorystandards"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Typ"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Privates Repository"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
@@ -2779,34 +2267,34 @@
 "Private Repositories sind nur für explizit hinzugefügte Mitarbeiter "
 "sichtbar."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Aktiviere Statistiken"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Statistik-Fenster in der Zusammenfassungsseite aktivieren."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Aktiviere Downloads"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Download-Menü auf der Zusammenfassungsseite aktivieren."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Locking aktivieren"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "Aktivieren Sie Lock-by-Pulling im Repository."
 
@@ -2815,7 +2303,7 @@
 msgid "Edit Gist"
 msgstr "Gist editieren"
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
@@ -2824,38 +2312,38 @@
 "Gist wurde während der Änderung aktualisiert. Änderungen kopieren und "
 "%(here)s klicken um die neue Version nachzuladen."
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Gist Beschreibung ..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr "Gist Lebenszeit"
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Verfällt"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "nie"
@@ -2865,7 +2353,8 @@
 msgstr "Gist aktualisieren"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Abbrechen"
 
@@ -2888,16 +2377,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr "Neuen Gist erstellen"
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "Erstellt"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Bisher sind keine Gists vorhanden"
 
@@ -2906,45 +2395,47 @@
 msgid "New Gist"
 msgstr "Neuer Gist"
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
+#: kallithea/templates/admin/gists/new.html:45
+#, fuzzy
+#| msgid "name this file..."
+msgid "Name this gist ..."
 msgstr "benenne diese Datei..."
 
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr "Privaten Gist erstellen"
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr "Öffentlichen Gist erstellen"
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "Zurücksetzen"
 
@@ -2954,190 +2445,208 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
-#: kallithea/templates/admin/gists/show.html:37
+#: kallithea/templates/admin/gists/show.html:35
 msgid "Public Gist"
 msgstr "Öffentlicher Gist"
 
-#: kallithea/templates/admin/gists/show.html:39
+#: kallithea/templates/admin/gists/show.html:37
 msgid "Private Gist"
 msgstr "Privater Gist"
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Löschen"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr "Löschen des Gists bestätigen"
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "erstellt"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Mein Account"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "Profil"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
-#, fuzzy
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
-msgstr "Neue E-Mailadresse"
-
-#: kallithea/templates/admin/my_account/my_account.html:38
+msgstr "E-Mail-Adressen"
+
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr "API Keys"
 
-#: kallithea/templates/admin/my_account/my_account.html:39
-#, fuzzy
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
-msgstr "Repositories"
-
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
-#, fuzzy
+msgstr "Eigene Repositories"
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
-msgstr "Repositories"
-
-#: kallithea/templates/admin/my_account/my_account.html:41
+msgstr "Beobachtete Repositories"
+
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
-#, fuzzy
 msgid "Show Permissions"
-msgstr "Berechtigungen kopieren"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+msgstr "Berechtigungen anzeigen"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr "Mitgeliefert"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
-#, fuzzy, python-format
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
+#, python-format
 msgid "Confirm to reset this API key: %s"
-msgstr "Zurücksetzen des API Keys \"%s\" bestätigen"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
-#, fuzzy
+msgstr "Zurücksetzen des API-Schlüssels \"%s\" bestätigen"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
-msgstr "verfallen"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
-#, fuzzy, python-format
+msgstr "Verfallen"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
+#, python-format
 msgid "Confirm to remove this API key: %s"
-msgstr "Entfernen des API Keys \"%s\" bestätigen"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
-#, fuzzy
+msgstr "Entfernen des API-Schlüssels \"%s\" bestätigen"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
-msgstr "entfernen"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
-#, fuzzy
+msgstr "Entfernen"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
-msgstr "Keine weiteren API Keys spezifiziert"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
-#, fuzzy
+msgstr "Keine weiteren API-Schlüssel angegeben"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
-msgstr "Neuer API Key"
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+msgstr "Neuer API-Schlüssel"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Hinzufügen"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+"\n"
+"API-Schlüssel werden verwendet, um Skripten oder Diensten denselben\n"
+"Zugang zu %s zu gewähren, den Sie mit Eingabe Ihres Passworts\n"
+"erlangen würden.\n"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+"\n"
+"Ebenso wie Passworte, sollten API-Schlüssel somit niemals mit anderen\n"
+"geteilt oder von nicht-vertrauenswürdigen Skripten oder Diensten\n"
+"verwendet werden. Falls ein solcher Zugriff zwischenzeitlich passiert "
+"sein\n"
+"sollte, dann können Sie hier den API-Schlüssel zurücksetzen, um weiteren\n"
+"Missbrauchen zu verhindern.\n"
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "Primär"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Löschen der E-Mail \"%s\" bestätigen"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr "Keine weiteren E-Mails spezifiziert."
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "Neue E-Mailadresse"
 
@@ -3145,108 +2654,71 @@
 msgid "Change Your Account Password"
 msgstr "Passwort des Benutzerkontos ändern"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr "Aktuelles Passwort"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Neues Passwort"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr "Bestätige neues Passwort"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-"Dieses Konto wird mit %s verwaltet und das Passwort kann hier nicht geändert "
-"werden"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
-msgstr "Benutzerbild ändern unter"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "Verwendet"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
-msgid "Avatars are disabled"
-msgstr "Avatare sind deaktiviert"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr "E-Mailadresse fehlt, bitte aktualisieren."
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+"Dieser Account wird mit %s verwaltet - daher kann das Passwort nicht "
+"geändert werden"
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
 #, fuzzy
 msgid "Current IP"
 msgstr "Aktuelle IP-Adresse"
 
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr "Gravatar"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr "Benutzerbild %s ändern unter"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:12
+#: kallithea/templates/admin/users/user_edit_profile.html:12
+msgid "Avatars are disabled"
+msgstr "Avatare sind deaktiviert"
+
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "Repositories in Ihrem Besitz"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Keine Datensätze gefunden."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Name"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr "Repositories, denen Sie folgen"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Meine Benachrichtigungen"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Alle"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Kommentare"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Pull Requests"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr "Markiere alle als gelesen"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Bisher gibt es keine Benachrichtigungen"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr "Zeige Benachrichtigung"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Benachrichtigungen"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr "Standardrechte"
 
@@ -3260,11 +2732,17 @@
 msgid "IP Whitelist"
 msgstr "IP Whitelist"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Anonymer Zugang"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "Anonymer Zugang"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
@@ -3273,7 +2751,7 @@
 "Unauthentifizierten Zugriff auf Kallithea erlauben. Anonyme Benutzer "
 "verwenden %s Benutzerrechte."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
@@ -3283,24 +2761,23 @@
 "gesetzt. Beachten Sie, dass alle spezifischen Standardrechte der "
 "Repositories verloren gehen"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
-#, fuzzy
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
-msgstr "Bestehendes Repository importieren?"
+msgstr "Auf alle bestehenden Repositories anwenden"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr "Berechtigungen für neue Repositories des Vorgabe-Benutzers."
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
-msgstr "Berechtigungen für den Standardbenutzer auf neuen Repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "Repository Gruppe"
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "Repository Gruppe"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
@@ -3310,52 +2787,49 @@
 "Rechte gesetzt. Beachten Sie, dass all spezifischen Standardrechte der "
 "Repositorygruppen verloren gehen"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
-#, fuzzy
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
-msgstr "Bestehendes Repository importieren?"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
-#, fuzzy
+msgstr "Auf alle bestehenden Repository-Gruppen anwenden"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
-msgstr "Rechte der übergeordneten Repositorygruppe kopieren."
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+msgstr "Berechtigungen des Vorgabe-Benutzers für neue Repository-Gruppen."
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "Benutzergruppe"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
-#, fuzzy
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
-"Alle Standardrechte jeder Benutzergruppe werden auf die gewählten Rechte "
-"gesetzt. Beachten Sie, dass alle spezifischen Standardrechte der "
-"Benutzergruppen verloren gehen"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+"Alle Vorgabe-Berechtigungen jeder Benutzergruppe werden auf die gewählten "
+"Berechtigungen zurückgesetzt. Beachten Sie, dass dabei alle Anpassungen "
+"von Vorgabe-Berechtigungen für Benutzergruppen verloren gehen"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr "Auf alle bestehenden Benutzergruppen anwenden"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
-msgstr "Berechtigungen für den Standardbenutzer für neue Benutzergruppen."
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
-#, fuzzy
+msgstr "Berechtigungen für neue Benutzergruppen des den Vorgabe-Benutzer."
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
-msgstr "Repository erstellung"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
+msgstr "Erstellung eines übergeordneten Repositories"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
 msgstr ""
 "Aktiviere dies, damit Nicht-Administratoren Repositories auf der obersten "
 "Ebene erstellen können."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
@@ -3363,91 +2837,93 @@
 "Hinweis: dadurch erhalten auch alle Benutzer API-Zugriff, um überall "
 "Repositories zu erstellen. Das kann sich in zukünftigen Versionen ändern."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
-msgstr "Repository-Erstellung mit Gruppenschreibzugriff"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
-#, fuzzy
+msgstr "Repository-Erstellung mit Gruppen-Schreibzugriff"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
-"Schreibrechte einer Repository Gruppe erlauben innerhalb der Gruppe neue "
-"Repositorys zu erstellen."
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+"Falls aktiv, gewährt dies das Recht zum Erzeugen von Repositories in "
+"einer Repository-Gruppe. Falls inaktiv, sind Gruppen-"
+"Schreibberechtigungen wirkungslos."
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "Benutzergruppen Erstellung"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
-"Aktivieren Sie dies, damit Nicht-Administratoren Benutzergruppen erstellen "
-"können."
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+"Aktivieren Sie dies, damit Nicht-Administratoren Benutzergruppen "
+"erstellen können."
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Repository-forking"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 "Aktivieren Sie dies, um Nichtadministratoren zu erlauben, Repositories zu "
 "forken."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Registrierung"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#, fuzzy
 msgid "External auth account activation"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+msgstr "Externe Authentifizierung für Accounts aktivieren"
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Bestätigen diese IP zu löschen: %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr "Alle IP-Adressen sind zulässig."
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Neue IP-Adresse"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "Repositorygruppen"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Gruppen name"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Übergeordnete Gruppe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr "Rechte der übergeordneten Gruppe kopieren"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr "Rechte der übergeordneten Repositorygruppe kopieren."
 
@@ -3456,30 +2932,29 @@
 msgid "%s Repository Group Settings"
 msgstr "%s Einstellungen für Repositorygruppen"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr "Untergruppe hinzufügen"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Einstellungen"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Erweitert"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Rechte"
@@ -3495,7 +2970,7 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
 msgid "Total repositories"
-msgstr ""
+msgstr "Alle Repositories"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
 msgid "Children groups"
@@ -3504,12 +2979,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Erstellt am"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3520,8 +2995,26 @@
 msgid "Delete this repository group"
 msgstr "Diese Repositorygruppe löschen"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr "Nicht sichtbar"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr "Sichtbar"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "Add Repository"
+msgid "Add repos"
+msgstr "Repository hinzufügen"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr "Benutzergruppen hinzufügen oder ändern"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 #, fuzzy
 msgid "User/User Group"
@@ -3529,119 +3022,120 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
-#, fuzzy
 msgid "Default"
-msgstr "standart"
+msgstr "Vorgabe"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
-#, fuzzy
 msgid "Revoke"
-msgstr "entziehen"
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+msgstr "Zurückziehen"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+msgstr "Neues hinzufügen"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+msgstr "Auf untergeordnete Elemente anwenden"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr "Beide"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+"Setzen oder zurückziehen von Berechtigungen bezüglich aller "
+"untergeordneten Elemente, einschließlich nicht-privater Repositories und "
+"anderer Gruppen, falls ausgewählt."
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+"Aktiviere die Sperre-bei-Pull für die Gruppe. Diese Option wird auf alle "
+"anderen enthaltenen Gruppen und Repositories angewandt"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr "Diese Gruppe löschen"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr "Löschen der Gruppe bestätigen"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "mit"
+msgid "Repository group %s"
+msgstr "Repository-Gruppe %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr "Repositorygruppenverwaltung"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr "Anzahl der Repositories oberster Ebene"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
-#, fuzzy
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
-msgstr "[erstellt] Repository"
-
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+msgstr "Entferntes Repository clonen"
+
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+"Optional: URL eines entfernten Repositories. Falls gesetzt, dann wird das "
+"Repository als Clon von dieser URL erstellt."
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 "Halten Sie es kurz und prägnant. Benutzen Sie eine README-Datei für "
 "längere Beschreibungen."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+"Wähle bei Bedarf eine Gruppe, der dieses Repository zugeordnet werden "
+"soll."
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Repository Typ der erstellt werden soll."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Start Revision"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
 msgstr ""
+"Vorgabe-Revision für Datei-Seiten, Downloads, Volltext-Indizierung und "
+"Doku-Erzeugung"
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
 #, python-format
@@ -3650,7 +3144,7 @@
 
 #: kallithea/templates/admin/repos/repo_creating.html:13
 msgid "Creating repository"
-msgstr ""
+msgstr "Repository erzeugen"
 
 #: kallithea/templates/admin/repos/repo_creating.html:27
 #, python-format
@@ -3658,34 +3152,39 @@
 "Repository \"%(repo_name)s\" is being created, you will be redirected "
 "when this process is finished.repo_name"
 msgstr ""
+"Repository \"%(repo_name)s\" wird erzeugt. Sie werden dorthin umgeleitet, "
+"sobald der Prozess abgeschlossen ist."
 
 #: kallithea/templates/admin/repos/repo_creating.html:39
 msgid ""
 "We're sorry but error occurred during this operation. Please check your "
 "Kallithea server logs, or contact administrator."
 msgstr ""
+"Bedauerlicherweise ist bei dieser Operation ein Fehler aufgetreten. Bitte "
+"prüfen Sie die Kallithea-Server-Logs or kontaktieren Sie die "
+"Administrierenden."
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
 #, python-format
 msgid "%s Repository Settings"
 msgstr "%s Repositoryeinstellungen"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit.html:52
+msgstr "Extra-Feld"
+
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit.html:55
+msgstr "Zwischenspeicher"
+
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Entfernt"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Statistiken"
 
@@ -3694,28 +3193,27 @@
 msgstr "Übergeordnet"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+msgstr "Setzen"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+"Manuell dieses Repository als Fork einem anderen aus der List zuordnen."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr "Sichtbarkeit des öffentlichen Logbuches"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "Entferne aus dem Öffentlichen Logbuch"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr "Zum öffentlichen Logbuch hinzufügen"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
@@ -3723,183 +3221,187 @@
 "Alle Aktionen, die in diesem Repository ausgeführt wurden, sind im "
 "öffentlichen Logbuch für jeden einsehbar."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+msgstr "Ändere die Sperrung"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 msgid "Confirm to unlock repository."
 msgstr "Entsperren des Repositorys bestätigen."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 msgid "Unlock Repository"
 msgstr "Repository entsperren"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+msgstr "Gesperrt durch %s auf %s"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 msgid "Confirm to lock repository."
 msgstr "Sperren des Repositorys bestätigen."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "Repository sperren"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Repository ist nicht gesperrt"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
 "pulling locks the repository; only the user who pulled and locked it can "
 "unlock it by doing a push."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+"Erzwinge die Sperre dieses Repositories. Dies funktioniert nur, falls "
+"anonymer Zugang abgeschaltet ist. Ein Pull führt zur Sperre des "
+"Repositories. Der pullende Nutzer sperrt das Repository; nur dieser "
+"Nutzer kann die Sperre durch eine Push-Operation wieder aufheben."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Löschen des Repositorys bestätigen: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Dieses Repository löschen"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] "Dieses Repository hat %s Fork"
 msgstr[1] "Dieses Repository hat %s Forks"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "Fork abtrennen"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "Forks löschen"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
 "it or restore it."
 msgstr ""
+"Das gelöschte Repository wird beiseitegelegt und versteckt, bis ein "
+"Administrierender es verfallen lässt. Der Administrierende kann es sowohl "
+"permanent löschen oder wiederherstellen."
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:4
 msgid "Invalidate Repository Cache"
 msgstr "Ungültiger Repositorycache"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
+"Manuell den Zwischenspeicher für dieses Repository verfallen lassen. Beim "
+"ersten Zugriff wird der Zwischenspeicher erneut aufgefüllt."
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr "Liste der zwischengespeicherten Werte"
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr "Präfix"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Schlüssel"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Aktiv"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+msgstr "Bezeichnung"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Löschen des Felds bestätigen: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+msgstr "Eindeutiges Kennzeichen des neuen Felds"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+msgstr "Neue Bezeichnung des Felds"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+msgstr "Eingabe einer kurzen Bezeichnung"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+msgstr "Beschreibung des neuen Felds"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+msgstr "Beschreibung eines Felds eingeben"
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr "Zusatzfelder sind deaktiviert."
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
-#, fuzzy
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
-msgstr "privates Repository"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
-#, fuzzy
+msgstr "Privates Repository"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr "Fork des Repository"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
-msgstr "Repositorium erzeugt %s"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
-#, fuzzy
+msgstr "URL des entfernten Repository"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
-msgstr "[Pulled von Remote] in Repository"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-#, fuzzy
-msgid "Confirm to pull changes from remote repository."
-msgstr "[Pulled von Remote] in Repository"
+msgstr "Hole Änderungen vom entfernten Repository"
 
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr "Bestätige die Abholung von Änderungen vom entfernten Repository."
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
-#, fuzzy
+msgstr "Für dieses Repository ist keine nicht-lokale URL angegeben."
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
-msgstr "privates Repository"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+msgstr "Dauerhafte Repository-Kennung"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+msgstr "Was ist das?"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+msgstr "URL nach Kennung"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3908,36 +3410,47 @@
 "                               This is useful for CI systems, or any "
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
-#, fuzzy
+"Falls dieses Repository umbenannt oder in eine andere Gruppe verschoben "
+"wird, ändert sich seine URL.\n"
+"Die Verwendung der permanenten URL garantiert, dass dieses Repository "
+"immer über diese URL erreichbar sein wird.\n"
+"Dies ist insbesondere für CI-Systeme oder in Fällen nützlich, in denen "
+"die URL des Repositories bei Dritten dauerhaft eingetragen wird."
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
-msgstr "[erstellt] Repository"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
-#, fuzzy
+msgstr "Entferntes Repository"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
-msgstr "Repository"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+msgstr "Repository URL"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+"Optional: URL eines entfernten Repositories. Falls gesetzt, dann kann das "
+"Repository von dieser URL bezogen werden."
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Besitzer des Repositorys ändern."
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
 msgid "Processed commits"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 msgid "Processed progress"
 msgstr ""
 
@@ -3953,7 +3466,7 @@
 msgid "Repositories Administration"
 msgstr "Repositoryverwaltung"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3974,7 +3487,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Hooks"
 
@@ -3986,279 +3499,271 @@
 msgid "System Info"
 msgstr "Systeminfo"
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Senden"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr "Einstellungen speichern"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Description"
+msgid "Rescan options"
+msgstr "Beschreibung"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Erneut Indizieren"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr "Prüfe auf Updates..."
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:7
 #, fuzzy
 msgid "Check for updates"
 msgstr "Auf Updates prüfen"
 
-#: kallithea/templates/admin/settings/settings_system.html:5
+#: kallithea/templates/admin/settings/settings_system.html:8
 msgid "Kallithea configuration file"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:6
+#: kallithea/templates/admin/settings/settings_system.html:9
 msgid "Python version"
 msgstr "Python-Version"
 
-#: kallithea/templates/admin/settings/settings_system.html:7
+#: kallithea/templates/admin/settings/settings_system.html:10
 msgid "Platform"
 msgstr "Plattform"
 
-#: kallithea/templates/admin/settings/settings_system.html:8
+#: kallithea/templates/admin/settings/settings_system.html:11
 msgid "Git version"
 msgstr "Git-Version"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 "Hinweis: Bitte stellen Sie sicher, dass der Server auf die URL zugreifen "
 "kann"
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr "Prüfe auf Updates..."
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Web"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr "Mercurial-Erweiterungen"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Erweiterung largefiles aktivieren"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Erweiterung hgsubversion aktivieren"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 msgid "Location of repositories"
 msgstr "Ort der Repositories"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Allgemein"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Zeige Kallithea-Version"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4275,93 +3780,101 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr "Clone-URL"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "Repository Größe"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Icons"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Einstellungen"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "Benutzergruppe hinzufügen"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr "Benutzergruppen"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr "Benutzergruppe hinzufügen"
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr "Kurze, optionale Beschreibung für diese Benutzergruppe."
 
@@ -4381,13 +3894,13 @@
 msgstr "Benutzergruppe: %s"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Mitglieder"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4396,15 +3909,15 @@
 msgid "Delete this user group"
 msgstr "Diese Benutzergruppe löschen"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Noch keine Mitglieder"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Ausgewählte Grppenmitglieder"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Verfügbare Mitglieder"
 
@@ -4418,17 +3931,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "Benutzer"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr "Benutzer hinzufügen"
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4447,12 +3960,12 @@
 msgstr "Benutzer: %s"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr "Letzter Login"
 
@@ -4461,7 +3974,7 @@
 msgstr "Mitglieder der Benutzergruppe"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4470,24 +3983,16 @@
 msgid "Delete this user"
 msgstr "Diesen Benutzer löschen"
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4495,223 +4000,224 @@
 msgid "Users Administration"
 msgstr "Benutzerverwaltung"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr "Authentifizierungsart"
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr "Support"
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Mercurial Repository"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Git Repository"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr "Fork erstellen"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Zusammenfassung"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Dateien"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Pull Requests"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "Optionen"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr "Fork vergleichen"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr "Keine Übereinstimmungen gefunden"
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "Öffentliches Logbuch"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr "Meine Pull Requests"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr "Nicht eingeloggt"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "Passwort vergessen?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "Kein Account?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr "Keine Übereinstimmungen gefunden"
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Repositorystandards"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4720,231 +4226,176 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Rechte"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Berechtigungen editieren"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-msgid "Add Another Comment"
-msgstr "Einen weiteren Kommentar hinzufügen"
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr "Gruppe"
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "mitglieder"
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
-msgstr ""
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "Aktiviere Downloads"
 
 #: kallithea/templates/base/root.html:31
-#, fuzzy
-#| msgid "on pull request"
-msgid "Open New Pull Request from {0}"
-msgstr "Kommentar von Pull Request"
+msgid "Add Another Comment"
+msgstr "Einen weiteren Kommentar hinzufügen"
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-#, fuzzy
-#| msgid "Show Selected Changeset __S"
-msgid "Show Selected Changesets {0} &rarr; {1}"
-msgstr "Ausgewähltes Changeset anzeigen __S"
+msgid "Start following this repository"
+msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
-msgstr ""
+msgid "Group"
+msgstr "Gruppe"
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
-msgstr "Widerruf der Rechte für {0}: {1} bestätigen?"
+msgid "No matching files"
+msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr "Aktiviert"
+#, fuzzy
+msgid "Open New Pull Request from {0}"
+msgstr "Kommentar von Pull Request"
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr "Deaktiviert"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+#, fuzzy
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr "Ausgewähltes Changeset anzeigen __S"
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "revisionen"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr "Widerruf der Rechte für {0}: {1} bestätigen?"
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Änderungssätze auswählen"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr "Changeset angeben"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Autor"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Revision"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "Klicken um Aufsteigend zu Sortieren"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "Klicken um Absteigend zu Sortieren"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Keine Datensätze gefunden."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Datenfehler."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Lade..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr "Gehe zum Tip des Repositorys"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Branch %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Bisher gibt es keine Änderungen"
 
@@ -4960,7 +4411,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4974,129 +4425,142 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Alter"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "privates Repository"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr ""
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Setze Changesetstatus"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d Kommentar"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Branch %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "Erstellt von"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "Erstellt von"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
+#: kallithea/templates/changeset/changeset_file_comment.html:20
 #, fuzzy
-msgid "No revisions"
-msgstr "revisionen"
+#| msgid "Comment"
+msgid "comment"
+msgstr "Kommentar"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -5116,172 +4580,177 @@
 msgid "Delete comment?"
 msgstr "Kommentar löschen?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Letzte Änderungen"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Kommentarvorschau"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "Setze Changesetstatus"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr "Keine Änderungen"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Kommentar von Pull Request"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr "Schließen"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Kommentar"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Vorschau"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d Kommentar"
 msgstr[1] "%d Kommentare"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "%d inline"
 msgstr[1] "%d inline"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] "%d generell"
 msgstr[1] "%d generell"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr "Setze Changesetstatus"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr "Keine Änderungen"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr "Schließen"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
-#: kallithea/templates/files/diff_2way.html:43
-msgid "Show full diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
-msgid "Show full side-by-side diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:38
-msgid "Show inline comments"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:86
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "Datei entfernt"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Ungeändert"
+
+#: kallithea/templates/changeset/diff_block.html:42
 msgid "Deleted"
 msgstr "Gelöscht"
 
-#: kallithea/templates/changeset/diff_block.html:89
+#: kallithea/templates/changeset/diff_block.html:44
 msgid "Renamed"
 msgstr "Umbenannt"
 
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Unbekannte Revision %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Kein Dateiname"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "Datei hinzugefügt"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "%s Changesets"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr "zurück"
 
@@ -5292,132 +4761,191 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Öffenentliches Repository"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Abonniere den %s RSS Feed"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Abonniere den %s ATOM Feed"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Comment on Changeset \"%s\""
+msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset"
+msgid "Changeset on"
+msgstr "Änderungssatz"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Zweig"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Letzte Änderungen"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr ""
+"Dieser Pull Request wurde geschlossen und kann daher nicht aktualisiert "
+"werden."
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Hallo %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 #, fuzzy
-#| msgid "We received a request to create a new password for your account."
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 "Wir haben eine Anforderung erhalten, für deinen Account ein neues "
 "Passwort zu erstellen."
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr "Pull Request [kommentiert] für"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "Pull Request %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
-#, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr ""
+#, fuzzy, python-format
+#| msgid "%(user)s commented on pull request %(age)s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Pull Request #%s von %s#%s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Pull Request [kommentiert] für"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "Gruppen name"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5435,7 +4963,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5445,72 +4973,74 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "oder"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr ""
+#, fuzzy
+msgid "New file type"
+msgstr "neue Datei"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr "Änderungen einchecken"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Größe"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr "Zuletzt geändert"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5520,7 +5050,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr "Datei löschen"
 
@@ -5533,24 +5063,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5558,51 +5084,61 @@
 msgstr[0] "%s Autor"
 msgstr[1] "%s Autoren"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "Branch %s gelöscht"
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "Die Datei ist zu groß, um sie anzuzeigen"
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Die Datei ist zu groß, um sie anzuzeigen"
+msgid "Show as raw."
+msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5622,12 +5158,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5636,35 +5172,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Privat"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Berechtigungen kopieren"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5674,40 +5210,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "ATOM Logbuch Feed"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "RSS Logbuch Feed"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "Meine Repositories"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "ATOM Feed für das Öffentliche Logbuch"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "RSS Feed für das Öffentliche Logbuch"
 
@@ -5716,31 +5252,36 @@
 msgid "New Pull Request"
 msgstr "Neuer Pull Request"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Titel"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Revision"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5753,6 +5294,10 @@
 msgid "Vote"
 msgstr "entziehen"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Alter"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5776,7 +5321,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5803,104 +5348,99 @@
 msgid "Pull request %s from %s#%s"
 msgstr "Pull Request #%s von %s#%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr "Zusammenfassung der Änderungen"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr "Es gibt keine Reviewers"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr "in"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr "Aktualisierung"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "Registrierung"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr "Pull Request Reviewers"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr "Änderungen verwerfen"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "Remove reviewer"
+msgid "Reviewers"
+msgstr "Reviewer entfernen"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "Reviewer entfernen"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "Potentielle Reviewer"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr "Als neuen Pull Request speichern"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr "Änderungen verwerfen"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr "Inhalt des Pull Requests"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5908,7 +5448,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "Pull Requests von '%s'"
 
@@ -5917,35 +5456,41 @@
 msgid "Pull Requests to '%s'"
 msgstr "Pull Requests für '%s'"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr "Einen neuen Pull Request eröffnen"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Zeige Pull Requests für '%s'"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Zeige Pull Requests von '%s'"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr "Von mir erstellte Pull Requests"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "Pull Request Reviewers"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5959,29 +5504,29 @@
 msgid "Search in All Repositories"
 msgstr "Suche in allen Repositories"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
-msgstr ""
-
-#: kallithea/templates/search/search.html:62
+msgstr "Suchbegriff"
+
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
-msgstr ""
-
-#: kallithea/templates/search/search.html:65
+msgstr "Suchen in"
+
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
-msgstr ""
-
-#: kallithea/templates/search/search.html:66
+msgstr "Dateiinhalt"
+
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
-msgstr ""
-
-#: kallithea/templates/search/search.html:67
+msgstr "Commit Nachrichten"
+
+#: kallithea/templates/search/search.html:58
 msgid "File names"
-msgstr ""
-
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+msgstr "Dateinamen"
+
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "Zugriff verweigert"
 
@@ -5991,80 +5536,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:39
+msgstr "Aktiviere"
+
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+msgstr "Dateien"
+
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:390
+msgstr "Mehr anzeigen"
+
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:391
+msgstr "Commits"
+
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:392
+msgstr "Dateien hinzugefügt"
+
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:393
+msgstr "Dateien geändert"
+
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:395
+msgstr "Dateien entfernt"
+
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:396
+msgstr "Commit"
+
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:397
+msgstr "Datei hinzugefügt"
+
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
-msgstr ""
-
-#: kallithea/templates/summary/statistics.html:398
+msgstr "Datei geändert"
+
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:4
+msgstr "Datei entfernt"
+
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -6072,85 +5617,218 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Clone von"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr "Clone-URL"
-
-#: kallithea/templates/summary/summary.html:78
-msgid "Show by Name"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:79
+#: kallithea/templates/summary/summary.html:68
 msgid "Show by ID"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:125
+msgstr "Herunterladen als zip"
+
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:186
+msgstr "Feed"
+
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Letzte Änderungen"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr "Schnelleinstieg"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "privates Repository"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:293
+msgstr "Liesmich-Datei von Revision %s:%s"
+
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s Tags"
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr "Tags vergleichen"
+msgstr "%s als %s herunterladen"
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr ""
+#~ "Es gibt keinen durchsuchbaren Index. Bitte den Whoosh Indizierer "
+#~ "ausführen"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP-Adresse %s ist nicht erlaubt"
+
+#, fuzzy
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
+
+#, fuzzy
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s hat am %(when)s eine Nachricht gesendet"
+
+#, fuzzy
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s hat Sie am %(when)s erwähnt"
+
+#, fuzzy
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s hat sich am %(when)s bei Kallithea registriert"
+
+#, fuzzy
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s hat am %(when)s einen neuen Pull Request eröffnet"
+
+#, fuzzy
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s hat am %(when)s ein Changeset kommentiert"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s hat am %(when)s eine Nachricht gesendet"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s hat Sie am %(when)s erwähnt"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s hat sich am %(when)s bei Kallithea registriert"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s hat am %(when)s einen neuen Pull Request eröffnet"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s hat am %(when)s einen Pull Request kommentiert"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "Repository Gruppe"
+
+#~ msgid "My Notifications"
+#~ msgstr "Meine Benachrichtigungen"
+
+#~ msgid "All"
+#~ msgstr "Alle"
+
+#~ msgid "Comments"
+#~ msgstr "Kommentare"
+
+#~ msgid "Mark All Read"
+#~ msgstr "Markiere alle als gelesen"
+
+#~ msgid "Mark as read"
+#~ msgstr "Markiere als gelesen"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Bisher gibt es keine Benachrichtigungen"
+
+#~ msgid "Show Notification"
+#~ msgstr "Zeige Benachrichtigung"
+
+#~ msgid "Notifications"
+#~ msgstr "Benachrichtigungen"
+
+#~ msgid "with"
+#~ msgstr "mit"
+
+#~ msgid "members"
+#~ msgstr "mitglieder"
+
+#~ msgid "Author"
+#~ msgstr "Autor"
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "Kommentar von Pull Request"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "Datum"
+
+#~ msgid "There are no reviewers"
+#~ msgstr "Es gibt keine Reviewers"
+
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Pull Request Reviewers"
+
+#~ msgid "Dashboard"
+#~ msgstr "Übersichtsseite"
+
+#~ msgid "Group Name"
+#~ msgstr "Gruppenname"
+
+#~ msgid "Remember me"
+#~ msgstr "Login Speichern"
+
+#~ msgid "name this file..."
+#~ msgstr "benenne diese Datei..."
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Benutzerbild ändern unter"
+
+#~ msgid "Using"
+#~ msgstr "Verwendet"
+
+#~ msgid "Missing email, please update your user email address."
+#~ msgstr "E-Mailadresse fehlt, bitte aktualisieren."
+
+#~ msgid "Web"
+#~ msgstr "Web"
+
+#~ msgid "quick filter..."
+#~ msgstr "Schnellfilter..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "Passwort vergessen?"
+
+#~ msgid "Repository Size"
+#~ msgstr "Repository Größe"
 
 #~ msgid "No comments."
 #~ msgstr "Keine Kommentare."
@@ -6194,12 +5872,6 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "ungültige Clone-URL"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
 #~ msgid "Defaults"
 #~ msgstr "Voreinstellungen"
 
@@ -6221,9 +5893,6 @@
 #~ msgid "delete"
 #~ msgstr "löschen"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Permissions Administration"
 #~ msgstr "Rechteverwaltung"
 
@@ -6251,14 +5920,9 @@
 #~ msgid "admin"
 #~ msgstr "admin"
 
-#~ msgid "delegated admin"
+#~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr "Optionale http[s] URL, von welcher das Repository geclont werden soll."
+#~ "Optionale http[s] URL, von welcher das Repository geclont werden soll."
 
 #~ msgid "Remote URL"
 #~ msgstr "Remote URL"
@@ -6266,36 +5930,12 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Änderungen von entferntem Ort übertragen"
 
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
 #~ msgid "edit"
 #~ msgstr "bearbeiten"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
 #~ msgid "Email prefix"
 #~ msgstr "E-Mail-Präfix"
 
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
 #~ msgid "SMTP server"
 #~ msgstr "SMTP-Server"
 
@@ -6308,60 +5948,15 @@
 #~ msgid "SMTP port"
 #~ msgstr "SMTP-Port"
 
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "Standart Rechte"
 
 #~ msgid "user groups"
 #~ msgstr "Benutzergruppen"
 
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "merge"
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "Statusänderung durch Pull Request"
 
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
 #~ msgid "Mimetype"
 #~ msgstr "MIME-Type"
 
@@ -6371,29 +5966,9 @@
 #~ msgid "Latest vote: %s"
 #~ msgstr "Letzte Stimmabgabe: %s"
 
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
+#~ msgid "This pull request can be updated with changes on %s:"
 #~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid ""
-#~ "Your password reset was successful, new"
-#~ " password has been sent to your "
-#~ "email"
-#~ msgstr ""
-#~ "Das Zurücksetzen des Passworted war "
-#~ "erfolgreich, ein neues Passwort wurde an"
-#~ " ihre EMail Addresse gesendet"
+#~ "Dieser Pull Request kann mit Änderungen in %s aktualisiert werden:"
 
 #~ msgid "Your new password"
 #~ msgstr "Dein neues Passwort"
@@ -6401,19 +5976,158 @@
 #~ msgid "Your new Kallithea password:%s"
 #~ msgstr "Ihr neues Kallithea-Passwort: %s"
 
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
+#~ msgid "Comment preview"
+#~ msgstr "Kommentarvorschau"
+
+#~ msgid "Preview"
+#~ msgstr "Vorschau"
 
 #~ msgid "You can generate it by clicking following URL"
 #~ msgstr "Du kannst es über die folgende URL erstellen"
 
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-#~ "Bitte ignoriere diese E-Mail, wenn du"
-#~ " kein neues Passwort angefordert hast."
-
 #~ msgid "Created by"
 #~ msgstr "Erstellt von"
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Geschlossen, ersetzt durch %s."
+
+#~ msgid "Closing."
+#~ msgstr "Schließen."
+
+#~ msgid "Repository no access"
+#~ msgstr "Kein Zugriff auf Repository"
+
+#~ msgid "Repository read access"
+#~ msgstr "Lesender Zugriff auf Repository"
+
+#~ msgid "Repository write access"
+#~ msgstr "Schreibdender Zugriff auf Repository"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Administrativer Zugang zum Repository"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Repository Gruppe hat Keinen Zugriff"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Repository Gruppe hat lesenden Zugriff"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Repository Gruppe hat schreibenden Zugriff"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Repository Gruppe hat Administrativen Zugriff"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Erstellung eines Repositorys deaktiviert"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Erstellung eines Repositorys aktiviert"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Forking eines Repositorys deaktiviert"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Forking eines Repositorys aktiviert"
+
+#~ msgid "Register disabled"
+#~ msgstr "Registrierung deaktiviert"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr ""
+#~ "Registrierung neuer Benutzer in Kallithea mit manueller Aktivierung"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr ""
+#~ "Registrierung neuer Benutzer in Kallithea mit Automatischer Aktivierung"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "Nicht Begutachtet"
+
+#~ msgid "Rejected"
+#~ msgstr "Abgelehnt"
+
+#~ msgid "Under Review"
+#~ msgstr "In Begutachtung"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Kein Zugriff für Repositorygruppe"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Lesezugriff für Repositorygruppe"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Schreibzugriff für Repositorygruppe"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Administrativer Zugriff für Repositorygruppe"
+
+#~ msgid "User group no access"
+#~ msgstr "Kein Zugriff für Benutzergruppe"
+
+#~ msgid "User group read access"
+#~ msgstr "Lesezugriff für Benutzergruppe"
+
+#~ msgid "User group write access"
+#~ msgstr "Nutzergruppe Schreibzugriff"
+
+#~ msgid "User group admin access"
+#~ msgstr "Administrativer Zugriff für Benutzergruppe"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Erstellung von Repositorygruppen deaktiviert"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Erstellung von Repositorygruppen aktiviert"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Erstellung von Benutzergruppen deaktiviert"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Erstellung von Benutzergruppen aktiviert"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "Benutzerregistrierung mit manueller Kontoaktivierung"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "Benutzerregistrierung mit automatische Kontoaktivierung"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "Review Request für %(repo_name)s Pull Request #%(pr_id)s von %(ref)s "
+#~ "erstellt von %(pr_username)s"
+
+#~ msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "Kommentar von %(comment_username)s für %(repo_name)s Pull Request #"
+#~ "%(pr_id)s von %(ref)s"
+
+#~ msgid "repositories"
+#~ msgstr "Repositories"
+
+#~ msgid "No repositories found."
+#~ msgstr "Keine Repositories gefunden."
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Es gibt bisher keine Branches"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Es gibt bisher keine Tags"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Es gibt bisher keine Lesezeichen"
+
+#~ msgid "enabled"
+#~ msgstr "Aktiviert"
+
+#~ msgid "Update"
+#~ msgstr "Aktualisierung"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Als neuen Pull Request speichern"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s Tags"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Tags vergleichen"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/i18n/el/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,5504 @@
+# Greek translations for Kallithea.
+# Copyright (C) 2015 Various authors, licensing as GPLv3
+# This file is distributed under the same license as the Kallithea project.
+# Automatically generated, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Kallithea 0.3\n"
+"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2017-06-14 18:51+0000\n"
+"Last-Translator: Asterios Dimitriou <steve@pci.gr>\n"
+"Language-Team: Greek <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/el/>\n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 2.15-dev\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
+msgid "There are no changesets yet"
+msgstr "Δεν υπάρχουν σετ αλλαγών ακόμα"
+
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
+#: kallithea/templates/base/perms_summary.html:14
+msgid "None"
+msgstr "Χωρίς"
+
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
+msgid "(closed)"
+msgstr "(κλειστό)"
+
+#: kallithea/controllers/changeset.py:83
+msgid "Show whitespace"
+msgstr "Εμφάνιση κενού"
+
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
+#: kallithea/templates/files/diff_2way.html:55
+msgid "Ignore whitespace"
+msgstr "Αγνόηση κενού"
+
+#: kallithea/controllers/changeset.py:163
+#, python-format
+msgid "Increase diff context to %(num)s lines"
+msgstr "Αύξηση του diff πλαισίου σε %(num)s γραμμές"
+
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "Χωρίς δικαιώματα αλλαγής της κατάστασης του αιτήματος έλξης"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Επιτυχής διαγραφή αιτήματος έλξης %s"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
+msgid "Such revision does not exist for this repository"
+msgstr "Δεν υπάρχει τέτοια αναθεώρηση για αυτό το αποθετήριο"
+
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Error creating repository %s"
+msgid "Could not find other repository %s"
+msgstr "Βλάβη κατά τη δημιουργία του αποθετηρίου %s"
+
+#: kallithea/controllers/compare.py:72
+#, fuzzy
+#| msgid "Cannot compare repositories without using common ancestor"
+msgid "Cannot compare repositories of different types"
+msgstr ""
+"Δεν μπορεί να γίνει σύγκριση αποθετηρίων χωρίς να χρησιμοποιηθεί κοινός "
+"πρόγονος"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
+msgid "Cannot compare repositories without using common ancestor"
+msgstr ""
+"Δεν μπορεί να γίνει σύγκριση αποθετηρίων χωρίς να χρησιμοποιηθεί κοινός "
+"πρόγονος"
+
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "Χωρίς απόκριση"
+
+#: kallithea/controllers/error.py:71
+msgid "Unknown error"
+msgstr "Άγνωστο λάθος"
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"Η αίτηση δεν  μπόρεσε να ερμηνευτεί από τον εξυπηρετητή λόγω κακής "
+"διατύπωσης."
+
+#: kallithea/controllers/error.py:87
+msgid "Unauthorized access to resource"
+msgstr "Ανεξουσιοδοτημένη πρόσβαση στον πόρο"
+
+#: kallithea/controllers/error.py:89
+msgid "You don't have permission to view this page"
+msgstr "Δεν έχετε άδεια για να εμφανίσετε αυτή τη σελίδα"
+
+#: kallithea/controllers/error.py:91
+msgid "The resource could not be found"
+msgstr "Ο πόρος δεν μπορεί να βρεθεί"
+
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+"Ο εξυπηρετητής συνάντησε μια απρόσμενη κατάσταση που τον απέτρεψαν να "
+"πραγματοποιήσει την αίτηση."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s συνέβαλε στο %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Το σετ αλλαγών ήταν πολύ μεγάλο και περικόπηκε..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr "%s %s τροφοδοσία"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Αλλαγές στο αποθετήριο %s"
+
+#: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr "Κλικ εδώ για προθήκη νέου αρχείου"
+
+#: kallithea/controllers/files.py:93
+#, python-format
+msgid "There are no files yet. %s"
+msgstr "Δεν υπάρχουν αρχεία ακόμα. %s"
+
+#: kallithea/controllers/files.py:194
+#, python-format
+msgid "%s at %s"
+msgstr "%s την %s"
+
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
+#, python-format
+msgid "This repository has been locked by %s on %s"
+msgstr "Το αποθετήριο κλειδώθηκε από %s την %s"
+
+#: kallithea/controllers/files.py:312
+#, fuzzy
+msgid "You can only delete files with revision being a valid branch"
+msgstr ""
+"Μπορείτε να διαγράψετε μόνο αρχεία σε αναθεώρηση που βρίσκονται σε έγκυρη "
+"διακλάδωση "
+
+#: kallithea/controllers/files.py:323
+#, python-format
+msgid "Deleted file %s via Kallithea"
+msgstr "Διαγραφή αρχείου %s μέσω του Kallithea"
+
+#: kallithea/controllers/files.py:345
+#, python-format
+msgid "Successfully deleted file %s"
+msgstr "Επιτυχής διαγραφή αρχείου %s"
+
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
+msgid "Error occurred during commit"
+msgstr "Συνέβη λάθος κατά το commit"
+
+#: kallithea/controllers/files.py:372
+msgid "You can only edit files with revision being a valid branch"
+msgstr ""
+"Μπορείτε να επεξεργαστείτε μόνο αρχεία σε αναθεώρηση που βρίσκονται σε "
+"έγκυρη διακλάδωση"
+
+#: kallithea/controllers/files.py:386
+#, python-format
+msgid "Edited file %s via Kallithea"
+msgstr "Επεξεργασία αρχείου %s μέσω του Kallithea"
+
+#: kallithea/controllers/files.py:402
+msgid "No changes"
+msgstr "Καμία αλλαγή"
+
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "Επιτυχής παράδοση σε %s"
+
+#: kallithea/controllers/files.py:438
+msgid "Added file via Kallithea"
+msgstr "Προσθήκη αρχείου μέσω Kallithea"
+
+#: kallithea/controllers/files.py:459
+msgid "No content"
+msgstr "Χωρίς περιεχόμενο"
+
+#: kallithea/controllers/files.py:463
+msgid "No filename"
+msgstr "Χωρίς όνομα αρχείου"
+
+#: kallithea/controllers/files.py:488
+msgid "Location must be relative path and must not contain .. in path"
+msgstr ""
+"Η τοποθεσία πρέπει να είναι σχετική διαδρομή και να μην περιέχει .. μέσα "
+"της"
+
+#: kallithea/controllers/files.py:520
+msgid "Downloads disabled"
+msgstr "Οι μεταφορτώσεις απενεργοποιήθηκαν"
+
+#: kallithea/controllers/files.py:531
+#, python-format
+msgid "Unknown revision %s"
+msgstr "Άγνωστη αναθεώρηση %s"
+
+#: kallithea/controllers/files.py:533
+msgid "Empty repository"
+msgstr "Άδειο αποθετήριο"
+
+#: kallithea/controllers/files.py:535
+msgid "Unknown archive type"
+msgstr "Άγνωστος τύπος αρχειοθέτησης"
+
+#: kallithea/controllers/files.py:756
+#: kallithea/templates/changeset/changeset_range.html:9
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
+msgid "Changesets"
+msgstr "Σετ αλλαγών"
+
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
+msgid "Branches"
+msgstr "Κλάδοι"
+
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
+msgid "Tags"
+msgstr "Ετικέτες"
+
+#: kallithea/controllers/forks.py:174
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "Συνέβει ένα λάθος κατά την διακλάδωση του αποθετηρίου %s"
+
+#: kallithea/controllers/home.py:78
+msgid "Groups"
+msgstr "Ομάδες"
+
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
+#: kallithea/templates/admin/repos/repo_add.html:12
+#: kallithea/templates/admin/repos/repo_add.html:16
+#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/users/user_edit_advanced.html:6
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
+msgid "Repositories"
+msgstr "Αποθετήρια"
+
+#: kallithea/controllers/home.py:121
+#: kallithea/templates/files/files_add.html:32
+#: kallithea/templates/files/files_delete.html:23
+#: kallithea/templates/files/files_edit.html:32
+msgid "Branch"
+msgstr "Κλάδος"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Κλειστοί Κλάδοι"
+
+#: kallithea/controllers/home.py:133
+msgid "Tag"
+msgstr "Ετικέτα"
+
+#: kallithea/controllers/home.py:139
+msgid "Bookmark"
+msgstr "Σελιδοδείκτης"
+
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
+#: kallithea/templates/journal/public_journal.html:4
+#: kallithea/templates/journal/public_journal.html:18
+msgid "Public Journal"
+msgstr "Δημόσιο Ημερολόγιο"
+
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
+msgid "Journal"
+msgstr "Ημερολόγιο"
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
+msgid "Bad captcha"
+msgstr "Λάθος captcha"
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Εγγραφήκατε επιτυχώς στο %s"
+
+#: kallithea/controllers/login.py:189
+msgid "A password reset confirmation code has been sent"
+msgstr "Στάλθηκε ένας κωδικός επιβεβαίωσης επαναφοράς του συνθηματικού"
+
+#: kallithea/controllers/login.py:238
+msgid "Invalid password reset token"
+msgstr "Άκυρο τεκμήριο (token) επαναφοράς του συνθηματικού"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
+msgid "Successfully updated password"
+msgstr "Το συνθηματικό ενημερώθηκε επιτυχώς"
+
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "Καθορίστηκε άκυρος σχολιαστής \"%s\""
+
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr "%s (κλειστό)"
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr "Σετ αλλαγών"
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr "Ειδικός"
+
+#: kallithea/controllers/pullrequests.py:182
+msgid "Peer branches"
+msgstr "Ομότιμοι κλάδοι"
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr "Σελιδοδείκτες"
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr "Λάθος κατά τη δημιουργία αιτήματος έλξης (pull request)"
+
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Ένα νέο αίτημα έλξης (pull request) δημιουργήθηκε επιτυχώς"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "Δημιουργήθηκε ενημέρωση αιτήματος έλξης"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Χωρίς περιγραφή"
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr "Ενημερώθηκε η αίτηση έλξης"
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr "Επιτυχής διαγραφή αιτήματος έλξης"
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr "Το αίτημα έλξης έχει ήδη συγχωνευτεί με το %s."
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr "Αυτό το αίτημα έλξης έχει κλείσει και δεν μπορεί να ενημερωθεί."
+
+#: kallithea/controllers/pullrequests.py:543
+#, fuzzy, python-format
+#| msgid "The following changes are available on %s:"
+msgid "The following additional changes are available on %s:"
+msgstr "Οι ακόλουθες αλλαγές είναι διαθέσιμες στο %s:"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
+msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr "Σημείωση: Ο κλάδος %s έχει άλλη κεφαλή (head): %s."
+
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
+msgstr "Αιτήματα έλξης του git δεν υποστηρίζουν ακόμα ενημερώσεις."
+
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "Δεν βρέθηκαν σετ αλλαγών για ενημέρωση αυτού του αιτήματος έλξης."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
+msgid "Invalid search query. Try quoting it."
+msgstr "Άκυρο αίτημα αναζήτησης. Δοκιμάστε με εισαγωγικά."
+
+#: kallithea/controllers/search.py:140
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
+msgid "An error occurred during search operation."
+msgstr "Ένα λάθος συνέβη κατά την διαδικασία αναζήτησης."
+
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
+msgid "No data ready yet"
+msgstr "Δεν υπάρχουν ακόμα έτοιμα δεδομένα"
+
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
+msgid "Statistics are disabled for this repository"
+msgstr "Τα στατιστικά είναι απενεργοποιημένα για αυτό το αποθετήριο"
+
+#: kallithea/controllers/admin/auth_settings.py:135
+msgid "Auth settings updated successfully"
+msgstr "Οι ρυθμίσεις εξουσιοδότησης ενημερώθηκαν επιτυχώς"
+
+#: kallithea/controllers/admin/auth_settings.py:146
+msgid "error occurred during update of auth settings"
+msgstr "παρουσιάστηκε βλάβη κατά την ενημέρωση των ρυθμίσεων εξουσιοδότησης"
+
+#: kallithea/controllers/admin/defaults.py:75
+msgid "Default settings updated successfully"
+msgstr "Οι προεπιλεγμένες ρυθμίσεις ενημερώθηκαν επιτυχώς"
+
+#: kallithea/controllers/admin/defaults.py:90
+msgid "Error occurred during update of defaults"
+msgstr "Συνέβη μία βλάβη κατά την ενημέρωση των προεπιλογών"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "Πάντα"
+
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 λεπτά"
+
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 ώρα"
+
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 ημέρα"
+
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
+msgid "1 month"
+msgstr "1 μήνας"
+
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
+msgid "Lifetime"
+msgstr "Διάρκεια ζωής"
+
+#: kallithea/controllers/admin/gists.py:140
+msgid "Error occurred during gist creation"
+msgstr "Συνέβη μία βλάβη κατά τη δημιουργία του gist"
+
+#: kallithea/controllers/admin/gists.py:156
+#, python-format
+msgid "Deleted gist %s"
+msgstr "Διαγράφηκε το gist %s"
+
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr "Mη τροποποιημένo"
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr "Το περιεχόμενο του gist ενημερώθηκε επιτυχώς"
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr "Τα δεδομένα του gist ενημερώθηκαν επιτυχώς"
+
+#: kallithea/controllers/admin/gists.py:233
+#, python-format
+msgid "Error occurred during update of gist %s"
+msgstr "Σφάλμα συνέβη κατά την ενημέρωση του gist %s"
+
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+"Δεν μπορείτε να επεξεργαστείτε αυτόν το χρήστη καθώς είναι κρίσιμος για "
+"όλη την εφαρμογή"
+
+#: kallithea/controllers/admin/my_account.py:117
+msgid "Your account was updated successfully"
+msgstr "Ο λογαριασμός σας ενημερώθηκε επιτυχώς"
+
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
+#, python-format
+msgid "Error occurred during update of user %s"
+msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του χρήστη %s"
+
+#: kallithea/controllers/admin/my_account.py:166
+msgid "Error occurred during update of user password"
+msgstr "Συνέβη ένα σφάλμα κατά την ενημέρωση του κωδικού του χρήστη"
+
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
+#, python-format
+msgid "Added email %s to user"
+msgstr "Προστέθηκε το email %s στον χρήστη"
+
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
+msgid "An error occurred during email saving"
+msgstr "Συνέβη ένα σφάλμα κατά την αποθήκευση του email"
+
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
+msgid "Removed email from user"
+msgstr "Αφαιρέθηκε το email από τον χρήστη"
+
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr "Το API κλειδί δημιουργήθηκε επιτυχώς"
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr "Το API κλειδί επαναφέρθηκε επιτυχώς"
+
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
+msgid "API key successfully deleted"
+msgstr "Το API κλειδί διαγράφηκε επιτυχώς"
+
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
+msgstr "Ανάγνωση"
+
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr "Εγγραφή"
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
+#: kallithea/templates/admin/auth/auth_settings.html:9
+#: kallithea/templates/admin/defaults/defaults.html:9
+#: kallithea/templates/admin/permissions/permissions.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/admin/repos/repo_add.html:10
+#: kallithea/templates/admin/repos/repo_add.html:14
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/settings/settings.html:9
+#: kallithea/templates/admin/user_groups/user_group_add.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/admin/users/user_add.html:8
+#: kallithea/templates/admin/users/user_edit.html:9
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
+#: kallithea/templates/base/perms_summary.html:17
+msgid "Admin"
+msgstr "Διαχειριστής"
+
+#: kallithea/controllers/admin/permissions.py:76
+#: kallithea/controllers/admin/permissions.py:87
+#: kallithea/controllers/admin/permissions.py:92
+#: kallithea/controllers/admin/permissions.py:95
+#: kallithea/controllers/admin/permissions.py:98
+#: kallithea/controllers/admin/permissions.py:101
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Απενεργοποιημένο"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Επιτρέπεται με χειροποίητη ενεργοποίηση του λογαριασμού"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Επιτρέπεται με αυτόματη ενεργοποίηση του λογαριασμού"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Χειροποίητη ενεργοποίηση εξωτερικού λογαριασμού"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Αυτόματη ενεργοποίηση εξωτερικού λογαριασμού"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
+msgid "Enabled"
+msgstr "Ενεργό"
+
+#: kallithea/controllers/admin/permissions.py:125
+msgid "Global permissions updated successfully"
+msgstr "Τα καθολικά δικαιώματα ενημερώθηκαν επιτυχώς"
+
+#: kallithea/controllers/admin/permissions.py:140
+msgid "Error occurred during update of permissions"
+msgstr "Συνέβει μια βλάβη κατά την ενημέρωση των δικαιωμάτων"
+
+#: kallithea/controllers/admin/repo_groups.py:174
+#, python-format
+msgid "Error occurred during creation of repository group %s"
+msgstr "Συνέβει μια βλάβη κατά την δημιουργία της ομάδας αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:179
+#, python-format
+msgid "Created repository group %s"
+msgstr "Δημιουργήθηκε η ομάδα αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:226
+#, python-format
+msgid "Updated repository group %s"
+msgstr "Ενημερώθηκε η ομάδα αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:242
+#, python-format
+msgid "Error occurred during update of repository group %s"
+msgstr "Συνέβει μια βλάβη κατά την ενημέρωση της ομάδας αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:252
+#, python-format
+msgid "This group contains %s repositories and cannot be deleted"
+msgstr "Αυτή η ομάδα περιέχει %s αποθετήρια και δε μπορεί να διαγραφεί"
+
+#: kallithea/controllers/admin/repo_groups.py:259
+#, python-format
+msgid "This group contains %s subgroups and cannot be deleted"
+msgstr "Αυτή η ομάδα περιέχει %s υποομάδες και δε μπορεί να διαγραφεί"
+
+#: kallithea/controllers/admin/repo_groups.py:265
+#, python-format
+msgid "Removed repository group %s"
+msgstr "Αφαιρέθηκε η ομάδα αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:270
+#, python-format
+msgid "Error occurred during deletion of repository group %s"
+msgstr "Συνέβει μια βλάβη κατά την διαγραφή της ομάδας αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
+msgid "Cannot revoke permission for yourself as admin"
+msgstr "Δεν μπορείτε να ανακαλέσετε την άδεια σας ως διαχειριστής"
+
+#: kallithea/controllers/admin/repo_groups.py:369
+msgid "Repository group permissions updated"
+msgstr "Τα δικαιώματα της ομάδας αποθετηρίου ενημερώθηκαν"
+
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
+msgid "An error occurred during revoking of permission"
+msgstr "Συνέβει μια βλάβη κατά την ανάκληση της άδειας"
+
+#: kallithea/controllers/admin/repos.py:137
+#, python-format
+msgid "Error creating repository %s"
+msgstr "Βλάβη κατά τη δημιουργία του αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repos.py:195
+#, python-format
+msgid "Created repository %s from %s"
+msgstr "Δημιουργήθηκε το αποθετήριο %s από το %s"
+
+#: kallithea/controllers/admin/repos.py:204
+#, python-format
+msgid "Forked repository %s as %s"
+msgstr "Κλωνοποιήθηκε το αποθετηρίο %s ως %s"
+
+#: kallithea/controllers/admin/repos.py:207
+#, python-format
+msgid "Created repository %s"
+msgstr "Δημιουργήθηκε το αποθετήριο %s"
+
+#: kallithea/controllers/admin/repos.py:236
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr "Το αποθετήριο %s ενημερώθηκε επιτυχώς"
+
+#: kallithea/controllers/admin/repos.py:256
+#, python-format
+msgid "Error occurred during update of repository %s"
+msgstr "Συνέβει μια βλάβη κατά την ενημέρωση του αποθετηρίου %s"
+
+#: kallithea/controllers/admin/repos.py:274
+#, python-format
+msgid "Detached %s forks"
+msgstr "Αποσυνδέθηκαν %s κλώνοι"
+
+#: kallithea/controllers/admin/repos.py:277
+#, python-format
+msgid "Deleted %s forks"
+msgstr "Διαγράφηκαν %s κλώνοι"
+
+#: kallithea/controllers/admin/repos.py:282
+#, python-format
+msgid "Deleted repository %s"
+msgstr "Διαγράφηκε το αποθετήριο %s"
+
+#: kallithea/controllers/admin/repos.py:285
+#, python-format
+msgid "Cannot delete repository %s which still has forks"
+msgstr "Δε μπορεί να διαγραφεί το αποθετήριο %s που ακόμα έχει κλώνους"
+
+#: kallithea/controllers/admin/repos.py:290
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr "Συνέβει μια βλάβη κατά την διαγραφή του %s"
+
+#: kallithea/controllers/admin/repos.py:330
+msgid "Repository permissions updated"
+msgstr "Τα δικαιώματα του αποθετηρίου ενημερώθηκαν"
+
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
+msgstr "Συνέβει μια βλάβη κατά τη δημιουργία του πεδίου"
+
+#: kallithea/controllers/admin/repos.py:401
+msgid "An error occurred during removal of field"
+msgstr "Συνέβει μια βλάβη κατά την απομάκρυνση του πεδίου"
+
+#: kallithea/controllers/admin/repos.py:415
+msgid "-- Not a fork --"
+msgstr "-- Όχι κλώνος --"
+
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr "Ενημερώθηκε η ορατότητα του αποθετηρίου στο δημόσιο ημερολόγιο"
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+"Συνέβει μια βλάβη κατά την τοποθέτηση αυτού το αποθετηρίου στο δημόσιο "
+"ημερολόγιο"
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr "Χωρίς"
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr "Σημειώθηκε το αποθετήριο %s σαν κλώνος του %s"
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr "Παρουσιάστηκε ένα σφάλμα κατά τη διάρκεια αυτής της λειτουργίας"
+
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
+msgid "Repository has been locked"
+msgstr "Το αποθετήριο έχει κλειδωθεί"
+
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
+msgid "Repository has been unlocked"
+msgstr "Το αποθετήριο έχει ξεκλειδωθεί"
+
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
+msgid "An error occurred during unlocking"
+msgstr "Παρουσιάστηκε ένα σφάλμα κατά το ξεκλείδωμα"
+
+#: kallithea/controllers/admin/repos.py:528
+msgid "Cache invalidation successful"
+msgstr "Η ακύρωση της cache ήταν επιτυχής"
+
+#: kallithea/controllers/admin/repos.py:532
+msgid "An error occurred during cache invalidation"
+msgstr "Παρουσιάστηκε ένα σφάλμα κατά τη διάρκεια ακύρωσης της cache"
+
+#: kallithea/controllers/admin/repos.py:545
+msgid "Pulled from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:548
+msgid "An error occurred during pull from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:579
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:135
+msgid "Updated VCS settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:174
+#, python-format
+msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:189
+#, fuzzy, python-format
+#| msgid "Deleted repository %s"
+msgid "Invalidated %s repositories"
+msgstr "Διαγράφηκε το αποθετήριο %s"
+
+#: kallithea/controllers/admin/settings.py:230
+msgid "Updated application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:283
+msgid "Updated visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:288
+msgid "Error occurred during updating visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:312
+msgid "Please enter email address"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:327
+msgid "Send email task created"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Δεν υπάρχουν ακόμα έτοιμα δεδομένα"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
+msgid "Added new hook"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:376
+msgid "Updated hooks"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:380
+msgid "Error occurred during hook creation"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:404
+msgid "Whoosh reindex task scheduled"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:143
+#, python-format
+msgid "Created user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:156
+#, python-format
+msgid "Error occurred during creation of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:184
+#, python-format
+msgid "Updated user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:206
+#, python-format
+msgid "Error occurred during update of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:217
+msgid "Successfully deleted user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:222
+msgid "An error occurred during deletion of user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:278
+msgid "Target group cannot be the same"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:284
+msgid "User group permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
+msgid "Updated permissions"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:123
+#, python-format
+msgid "Created user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:138
+#, python-format
+msgid "Error occurred during creation of user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:162
+msgid "User updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:190
+msgid "Successfully deleted user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:195
+msgid "An error occurred during deletion of user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:203
+msgid "The default user cannot be edited"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:412
+#, python-format
+msgid "Added IP address %s to user whitelist"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:418
+msgid "An error occurred while adding IP address"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:430
+msgid "Removed IP address from user whitelist"
+msgstr ""
+
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
+msgid "Repository not found in the filesystem"
+msgstr ""
+
+#: kallithea/lib/base.py:608
+#, python-format
+msgid "Changeset for %s %s not found in %s"
+msgstr ""
+
+#: kallithea/lib/diffs.py:193
+msgid "Binary file"
+msgstr ""
+
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
+msgid "No changes detected"
+msgstr ""
+
+#: kallithea/lib/helpers.py:612
+#, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
+msgid "Created tag: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:625
+#, python-format
+msgid "Changeset %s not found"
+msgstr ""
+
+#: kallithea/lib/helpers.py:674
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:680
+msgid "Compare view"
+msgstr ""
+
+#: kallithea/lib/helpers.py:699
+msgid "and"
+msgstr ""
+
+#: kallithea/lib/helpers.py:700
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
+msgid "revisions"
+msgstr ""
+
+#: kallithea/lib/helpers.py:725
+#, python-format
+msgid "Fork name %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:746
+#, python-format
+msgid "Pull request %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:756
+msgid "[deleted] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
+msgid "[created] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:760
+msgid "[created] repository as fork"
+msgstr ""
+
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
+msgid "[forked] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
+msgid "[updated] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:766
+msgid "[downloaded] archive from repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:768
+msgid "[delete] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:776
+msgid "[created] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:778
+msgid "[updated] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:780
+msgid "[created] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:782
+msgid "[updated] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:784
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:786
+msgid "[commented] on pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:788
+msgid "[closed] pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:790
+msgid "[pushed] into"
+msgstr ""
+
+#: kallithea/lib/helpers.py:792
+msgid "[committed via Kallithea] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:794
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:796
+msgid "[pulled] from"
+msgstr ""
+
+#: kallithea/lib/helpers.py:798
+msgid "[started following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:800
+msgid "[stopped following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:928
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
+msgid "No files"
+msgstr ""
+
+#: kallithea/lib/helpers.py:957
+msgid "new file"
+msgstr ""
+
+#: kallithea/lib/helpers.py:960
+msgid "mod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:963
+msgid "del"
+msgstr ""
+
+#: kallithea/lib/helpers.py:966
+msgid "rename"
+msgstr ""
+
+#: kallithea/lib/helpers.py:971
+msgid "chmod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:1264
+#, python-format
+msgid ""
+"%s repository is not mapped to db perhaps it was created or renamed from "
+"the filesystem please run the application again in order to rescan "
+"repositories"
+msgstr ""
+
+#: kallithea/lib/utils2.py:333
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:334
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:335
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:336
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:337
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:338
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:354
+#, python-format
+msgid "in %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:356
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:358
+#, python-format
+msgid "in %s and %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:361
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:364
+msgid "just now"
+msgstr ""
+
+#: kallithea/model/comment.py:68
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
+msgid "[Mention]"
+msgstr ""
+
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
+msgid "Default user has no access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1706
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1707
+msgid "Default user has write access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1708
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1710
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1711
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1712
+msgid "Default user has write access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1713
+msgid "Default user has admin access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1715
+msgid "Default user has no access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1716
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1717
+msgid "Default user has write access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1718
+msgid "Default user has admin access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1720
+msgid "Only admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1721
+msgid "Non-admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1723
+msgid "Only admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1724
+msgid "Non-admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1726
+msgid "Only admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1727
+msgid "Non-admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
+msgid "Only admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1733
+msgid "Non-admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
+msgid "User registration with manual account activation"
+msgstr ""
+
+#: kallithea/model/db.py:1737
+msgid "User registration with automatic account activation"
+msgstr ""
+
+#: kallithea/model/db.py:2263
+msgid "Not reviewed"
+msgstr ""
+
+#: kallithea/model/db.py:2264
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr ""
+
+#: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: kallithea/model/forms.py:170
+msgid "Name must not contain only digits"
+msgstr ""
+
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
+#, python-format
+msgid "New user %(new_username)s registered"
+msgstr ""
+
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
+msgid "Closing"
+msgstr ""
+
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "Ελλιπή σετ αλλαγών από την προηγούμενη αίτηση έλξης:"
+
+#: kallithea/model/pull_request.py:348
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Καινούρια σετ αλλαγών στα %s %s από την προηγούμενη αίτηση έλξης:"
+
+#: kallithea/model/pull_request.py:355
+#, fuzzy
+#| msgid "Ancestor didn't change - show diff since previous version:"
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+"Το γονικό δεν άλλαξε - εμφάνισε τις διαφορές από την προηγούμενη έκδοση:"
+
+#: kallithea/model/pull_request.py:362
+#, fuzzy, python-format
+#| msgid ""
+#| "This pull request is based on another %s revision and there is no "
+#| "simple diff."
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"Αυτή η αίτηση έλξης είναι βασισμένη σε μία άλλη %s αναθεώρηση και δεν "
+"υπάρχει ένα απλό diff."
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "No changes found on %s %s since previous version."
+msgid "No changes found on %s %s since previous iteration."
+msgstr "Δεν βρέθηκαν αλλαγές στο %s %s από την προηγούμενη έκδοση."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
+msgid "latest tip"
+msgstr ""
+
+#: kallithea/model/user.py:189
+msgid "New user registration"
+msgstr ""
+
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:263
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:364
+msgid "Password reset link"
+msgstr ""
+
+#: kallithea/model/user.py:413
+msgid "Password reset notification"
+msgstr ""
+
+#: kallithea/model/user.py:414
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: kallithea/model/validators.py:74
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:76
+#, python-format
+msgid "Username \"%(username)s\" cannot be used"
+msgstr ""
+
+#: kallithea/model/validators.py:78
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
+msgid "The input is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:133
+msgid "Invalid user group name"
+msgstr ""
+
+#: kallithea/model/validators.py:134
+#, python-format
+msgid "User group \"%(usergroup)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:136
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:176
+msgid "Cannot assign this group as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:177
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:179
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: kallithea/model/validators.py:250
+msgid "Invalid old password"
+msgstr ""
+
+#: kallithea/model/validators.py:266
+msgid "Passwords do not match"
+msgstr ""
+
+#: kallithea/model/validators.py:281
+msgid "Invalid username or password"
+msgstr ""
+
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:331
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: kallithea/model/validators.py:333
+#, python-format
+msgid "Repository group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:419
+msgid "Invalid repository URL"
+msgstr ""
+
+#: kallithea/model/validators.py:420
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:445
+msgid "Fork has to be the same type as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:460
+msgid "You don't have permissions to create repository in this group"
+msgstr ""
+
+#: kallithea/model/validators.py:462
+msgid "no permission to create repository in root location"
+msgstr ""
+
+#: kallithea/model/validators.py:512
+msgid "You don't have permissions to create a group in this location"
+msgstr ""
+
+#: kallithea/model/validators.py:552
+msgid "This username or user group name is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:645
+msgid "This is not a valid path"
+msgstr ""
+
+#: kallithea/model/validators.py:662
+msgid "This email address is already in use"
+msgstr ""
+
+#: kallithea/model/validators.py:682
+#, python-format
+msgid "Email address \"%(email)s\" not found"
+msgstr ""
+
+#: kallithea/model/validators.py:719
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:731
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr ""
+
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
+msgid "Key name can only consist of letters, underscore, dash or numbers"
+msgstr ""
+
+#: kallithea/model/validators.py:779
+msgid "Filename cannot be inside a directory"
+msgstr ""
+
+#: kallithea/model/validators.py:795
+#, python-format
+msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
+msgstr ""
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
+msgid "About"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add.html:5
+#: kallithea/templates/admin/repos/repo_add.html:19
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
+msgid "Add Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "You have admin right to this group, and can edit it"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "Edit Repository Group"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
+#: kallithea/templates/pullrequests/pullrequest_show.html:38
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
+msgid "Description"
+msgstr ""
+
+#: kallithea/templates/index_base.html:60
+msgid "Last Change"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
+msgid "Tip"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
+#: kallithea/templates/pullrequests/pullrequest_data.html:16
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
+msgid "Owner"
+msgstr ""
+
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
+msgid "Log In"
+msgstr ""
+
+#: kallithea/templates/login.html:17
+#, python-format
+msgid "Log In to %s"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:5
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
+msgid "Username"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
+msgid "Password"
+msgstr ""
+
+#: kallithea/templates/login.html:44
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
+msgid "Forgot your password ?"
+msgstr ""
+
+#: kallithea/templates/login.html:55
+msgid "Don't have an account ?"
+msgstr ""
+
+#: kallithea/templates/login.html:62
+msgid "Sign In"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:5
+msgid "Password Reset"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
+msgid "Reset Your Password to %s"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:23
+#: kallithea/templates/password_reset_confirmation.html:5
+#: kallithea/templates/password_reset_confirmation.html:18
+msgid "Reset Your Password"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:30
+msgid "Email Address"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
+msgid "Captcha"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
+#, python-format
+msgid "You are about to set a new password for the email address %s."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
+msgid "Code you received in the email"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:36
+msgid "New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:43
+msgid "Confirm New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:51
+msgid "Confirm"
+msgstr ""
+
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
+msgid "Sign Up"
+msgstr ""
+
+#: kallithea/templates/register.html:22
+#, python-format
+msgid "Sign Up to %s"
+msgstr ""
+
+#: kallithea/templates/register.html:45
+msgid "Re-enter password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
+msgid "First Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
+msgid "Last Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
+#: kallithea/templates/admin/settings/settings.html:31
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
+msgid "Email"
+msgstr ""
+
+#: kallithea/templates/register.html:85
+msgid "Registered accounts are ready to use and need no further action."
+msgstr ""
+
+#: kallithea/templates/register.html:87
+msgid "Please wait for an administrator to activate your account."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:5
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/base/base.html:55
+msgid "Admin Journal"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
+msgid "journal filter..."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:12
+#: kallithea/templates/journal/journal.html:12
+msgid "Filter"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/journal/journal.html:13
+#, python-format
+msgid "%s Entry"
+msgid_plural "%s Entries"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/admin_log.html:6
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
+msgid "Action"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:8
+msgid "Date"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:61
+msgid "No actions yet"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:5
+msgid "Authentication Settings"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:11
+#: kallithea/templates/base/base.html:61
+msgid "Authentication"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:27
+msgid "Authentication Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
+msgid "Enabled Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
+msgid "Available built-in plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
+msgid "Plugin"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:101
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
+msgid "Save"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:5
+#: kallithea/templates/admin/defaults/defaults.html:11
+#: kallithea/templates/base/base.html:62
+msgid "Repository Defaults"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
+msgid "Type"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
+msgid "Enable locking"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:5
+#: kallithea/templates/admin/gists/edit.html:18
+msgid "Edit Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was update since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
+msgid "Gist description ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
+msgid "Gist lifetime"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/edit.html:61
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/gists/show.html:47
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
+msgid "Expires"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+msgid "Never"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:145
+msgid "Update Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:146
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
+msgid "Cancel"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:6
+#: kallithea/templates/admin/gists/index.html:16
+#, python-format
+msgid "Private Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:8
+#: kallithea/templates/admin/gists/index.html:18
+#, python-format
+msgid "Public Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:10
+#: kallithea/templates/admin/gists/index.html:20
+msgid "Public Gists"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:37
+#: kallithea/templates/admin/gists/show.html:25
+#: kallithea/templates/base/base.html:312
+msgid "Create New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
+msgid "Created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:66
+msgid "There are no gists yet"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:5
+#: kallithea/templates/admin/gists/new.html:18
+msgid "New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
+msgid "Create Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:54
+msgid "Create Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
+msgid "Reset"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:5
+#: kallithea/templates/admin/gists/show.html:9
+msgid "Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:10
+msgid "URL"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:37
+msgid "Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/pullrequests/pullrequest_data.html:20
+msgid "Delete"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+msgid "Confirm to delete this Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
+msgid "Show as Raw"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:69
+msgid "created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:82
+msgid "Show as raw"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:5
+#: kallithea/templates/admin/my_account/my_account.html:9
+#: kallithea/templates/base/base.html:397
+msgid "My Account"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:25
+#: kallithea/templates/admin/users/user_edit.html:29
+msgid "Profile"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:26
+msgid "Email Addresses"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:28
+#: kallithea/templates/admin/users/user_edit.html:31
+msgid "API Keys"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:29
+msgid "Owned Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
+msgid "Watched Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:31
+#: kallithea/templates/admin/permissions/permissions.html:30
+#: kallithea/templates/admin/user_groups/user_group_edit.html:32
+#: kallithea/templates/admin/users/user_edit.html:34
+msgid "Show Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
+msgid "Built-in"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
+#, python-format
+msgid "Confirm to reset this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
+msgid "Expired"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
+#, python-format
+msgid "Confirm to remove this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
+msgid "Remove"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
+msgid "No additional API keys specified"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
+msgid "New API key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
+msgid "Add"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
+msgid "Primary"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
+msgid "No additional emails specified."
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
+msgid "New email address"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:1
+msgid "Change Your Account Password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:8
+msgid "Current password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
+msgid "New password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:22
+msgid "Confirm new password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:12
+#: kallithea/templates/admin/users/user_edit_profile.html:12
+msgid "Avatars are disabled"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:1
+msgid "Repositories You Own"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_watched.html:1
+msgid "Repositories You are Watching"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:5
+#: kallithea/templates/admin/permissions/permissions.html:11
+#: kallithea/templates/base/base.html:60
+msgid "Default Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:28
+#: kallithea/templates/admin/settings/settings.html:29
+msgid "Global"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:29
+#: kallithea/templates/admin/users/user_edit.html:32
+msgid "IP Whitelist"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
+msgid "Anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+msgid ""
+"All default permissions on each repository will be reset to chosen "
+"permission, note that all custom default permission on repositories will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
+msgid "Apply to all existing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:32
+msgid ""
+"All default permissions on each repository group will be reset to chosen "
+"permission, note that all custom default permission on repository groups "
+"will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid ""
+"All default permissions on each user group will be reset to chosen "
+"permission, note that all custom default permission on user groups will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
+msgid "Top level repository creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
+msgid ""
+"With this, write permission to a repository group allows creating "
+"repositories inside that group. Without this, group write permissions "
+"mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
+msgid "User group creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
+msgid "Enable this to allow non-admins to create user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
+msgid "External auth account activation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
+msgid "Confirm to delete this IP address: %s"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
+msgid "All IP addresses are allowed."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
+msgid "New IP address"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
+msgid "Repository Groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
+msgid "Group name"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
+msgid "Group parent"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
+msgid "Copy parent group permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
+msgid "Copy permission set from parent repository group."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
+#, python-format
+msgid "%s Repository Group Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
+msgid "Add Child Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
+#: kallithea/templates/admin/repos/repo_edit.html:12
+#: kallithea/templates/admin/repos/repo_edit.html:25
+#: kallithea/templates/admin/settings/settings.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit.html:29
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
+msgid "Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
+#: kallithea/templates/admin/user_groups/user_group_edit.html:30
+#: kallithea/templates/admin/users/user_edit.html:33
+msgid "Advanced"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit.html:31
+msgid "Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1
+#, python-format
+msgid "Repository Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6
+msgid "Top level repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
+msgid "Total repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
+msgid "Children groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_advanced.html:8
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
+msgid "Created on"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:121
+#, python-format
+msgid "Confirm to delete this group: %s with %s repository"
+msgid_plural "Confirm to delete this group: %s with %s repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
+msgid "Delete this repository group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "Disabled"
+msgid "Visible"
+msgstr "Απενεργοποιημένο"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "Χωρίς απόκριση"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
+msgid "User/User Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
+msgid "Default"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
+msgid "Revoke"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
+msgid "Add new"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
+msgid "Apply to children"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
+msgid "Both"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
+msgid ""
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Remove this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Confirm to delete this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_show.html:4
+#, fuzzy, python-format
+#| msgid "Updated repository group %s"
+msgid "Repository group %s"
+msgstr "Ενημερώθηκε η ομάδα αποθετηρίου %s"
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:5
+msgid "Repository Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
+msgid "Number of Top-level Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:12
+msgid "Clone remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+msgid ""
+"Optional: URL of a remote repository. If set, the repository will be "
+"created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
+msgid "Optionally select a group to put this repository into."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
+msgid "Type of repository to create."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
+msgid "Landing revision"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:9
+#, python-format
+msgid "%s Creating Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:13
+msgid "Creating repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:27
+#, python-format
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:39
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:8
+#, python-format
+msgid "%s Repository Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:34
+msgid "Extra Fields"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:37
+msgid "Caches"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:40
+msgid "Remote"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/summary/statistics.html:8
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
+msgid "Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:1
+msgid "Parent"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:5
+msgid "Set"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
+msgid "Manually set this repository as a fork of another from the list."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
+msgid "Public Journal Visibility"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
+msgid "Remove from public journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
+msgid "Add to Public Journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
+msgid "Change Locking"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#, python-format
+msgid "Locked by %s on %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+msgid "Repository is not locked"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
+msgid ""
+"Force locking on the repository. Works only when anonymous access is "
+"disabled. Triggering a pull locks the repository.  The user who is "
+"pulling locks the repository; only the user who pulled and locked it can "
+"unlock it by doing a push."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
+msgid "Delete this Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
+msgid "This repository has %s fork"
+msgid_plural "This repository has %s forks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
+msgid "Detach forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+msgid "Delete forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:4
+msgid "Invalidate Repository Cache"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgid "Prefix"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+msgid "Key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
+msgid "Active"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+msgid "Label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
+#, python-format
+msgid "Confirm to delete this field: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
+msgid "New field key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
+msgid "New field label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
+msgid "Enter short label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
+msgid "New field description"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
+msgid "Enter description of a field"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
+msgid "Extra fields are disabled."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
+msgid "Private Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Empty repository"
+msgid "Fork of repository"
+msgstr "Άδειο αποθετήριο"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
+msgid "Remote repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
+msgid "Pull Changes from Remote Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
+msgid "This repository does not have a remote repository URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "Permanent Repository ID"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "What is that?"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
+msgid "URL by id"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
+msgid ""
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
+msgid "Remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
+msgid "Repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
+msgid ""
+"Optional: URL of a remote repository. If set, the repository can be "
+"pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
+msgid "Change owner of this repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+msgid "Processed progress"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Reset Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Confirm to remove current statistics."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:5
+msgid "Repositories Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:43
+msgid "State"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:5
+msgid "Settings Administration"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:27
+msgid "VCS"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:28
+msgid "Remap and Rescan"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:30
+msgid "Visual"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:4
+msgid "Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:33
+msgid "Full Text Search"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:34
+msgid "System Info"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:4
+msgid "Send test email to"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:12
+msgid "Send"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:4
+msgid "Site branding"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:12
+msgid "HTTP authentication realm"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
+msgid "ReCaptcha public key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:35
+msgid "Public key for reCaptcha system."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:40
+msgid "ReCaptcha private key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
+msgid "Save Settings"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:3
+msgid "Built-in Mercurial Hooks (Read-Only)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
+msgid "Failed to remove hook"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
+msgid "Delete records of missing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:17
+msgid "Invalidate cache for all repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:20
+msgid "Check this to reload data and clear cache keys for all repositories."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:25
+msgid "Install Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
+msgid "Overwrite existing Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
+msgid "Rescan Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:4
+msgid "Index build option"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:12
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
+msgid "Reindex"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Check for updates"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Upgrade info endpoint"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Note: please make sure this server can access this URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:23
+msgid "Python Packages"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:9
+msgid "Show repository size after push"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:15
+msgid "Log user push commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:21
+msgid "Log user pull commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:27
+msgid "Update repository after push (hg update)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:33
+msgid "Mercurial extensions"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:47
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:64
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
+msgid "General"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:9
+msgid "Use repository extra fields"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:12
+msgid "Allows storing additional customized fields per repository."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:17
+msgid "Show Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:20
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
+msgid ""
+"Gravatar URL allows you to use another avatar server application.\n"
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
+msgid ""
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repositories"
+msgid "Repository page size"
+msgstr "Αποθετήρια"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:65
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:75
+msgid "Show public repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:84
+msgid "Show public/private icons next to repository names."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:89
+msgid "Meta Tagging"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:5
+msgid "Add user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit.html:11
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
+msgid "User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:12
+#: kallithea/templates/admin/user_groups/user_groups.html:24
+msgid "Add User Group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
+msgid "Short, optional description for this user group."
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:5
+#, python-format
+msgid "%s user group settings"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:33
+msgid "Show Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
+#, python-format
+msgid "User Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
+msgid "Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
+#: kallithea/templates/data_table/_dt_elements.html:102
+#, python-format
+msgid "Confirm to delete this user group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
+msgid "Delete this user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
+msgid "No members yet"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
+msgid "Chosen group members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
+msgid "Available members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_groups.html:5
+msgid "User Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:10
+#: kallithea/templates/admin/users/user_edit.html:11
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
+msgid "Users"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:12
+#: kallithea/templates/admin/users/users.html:23
+msgid "Add User"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:41
+msgid "Password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:5
+#, python-format
+msgid "%s user settings"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:30
+msgid "Emails"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:1
+#, python-format
+msgid "User: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_profile.html:32
+msgid "Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:9
+#: kallithea/templates/admin/users/users.html:41
+msgid "Last Login"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:10
+msgid "Member of User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:90
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:23
+msgid "Delete this user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_ips.html:7
+#, python-format
+msgid "Inherited from %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:39
+msgid "Name in Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:53
+msgid "New password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:5
+msgid "Users Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:44
+msgid "Auth Type"
+msgstr ""
+
+#: kallithea/templates/base/base.html:16
+#, python-format
+msgid "Server instance: %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:28
+msgid "Support"
+msgstr ""
+
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
+msgid "Mercurial repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
+msgid "Git repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:115
+msgid "Create Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
+msgid "Summary"
+msgstr ""
+
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
+msgid "Changelog"
+msgstr ""
+
+#: kallithea/templates/base/base.html:133
+#: kallithea/templates/files/files.html:11
+msgid "Files"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
+msgid "Options"
+msgstr ""
+
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
+msgid "Compare Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:158
+msgid "Compare"
+msgstr ""
+
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
+#: kallithea/templates/search/search.html:14
+#: kallithea/templates/search/search.html:67
+msgid "Search"
+msgstr ""
+
+#: kallithea/templates/base/base.html:164
+msgid "Unlock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:166
+msgid "Lock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
+msgid "Unfollow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:178
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
+msgid "Create Pull Request"
+msgstr ""
+
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:313
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:315
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:316
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:321
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:360
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:369
+msgid "Login to Your Account"
+msgstr ""
+
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
+msgid "Log Out"
+msgstr ""
+
+#: kallithea/templates/base/base.html:524
+msgid "Parent rev."
+msgstr ""
+
+#: kallithea/templates/base/base.html:533
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
+msgid "Inherit defaults"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:15
+#, python-format
+msgid ""
+"Select to inherit global settings, IP whitelist and permissions from the "
+"%s."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:23
+msgid "Create repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:33
+msgid "Create user groups"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:37
+msgid "Select this option to allow user group creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:43
+msgid "Fork repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:47
+msgid "Select this option to allow repository forking for this user"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:13
+#: kallithea/templates/changelog/changelog.html:41
+msgid "Show"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:22
+msgid "No permissions defined yet"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:30
+#: kallithea/templates/base/perms_summary.html:55
+msgid "Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:32
+#: kallithea/templates/base/perms_summary.html:57
+msgid "Edit Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:92
+msgid "No permission defined"
+msgstr ""
+
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:30
+msgid "Unable to post"
+msgstr ""
+
+#: kallithea/templates/base/root.html:31
+msgid "Add Another Comment"
+msgstr ""
+
+#: kallithea/templates/base/root.html:32
+msgid "Stop following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:33
+msgid "Start following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:34
+msgid "Group"
+msgstr ""
+
+#: kallithea/templates/base/root.html:35
+msgid "Loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:36
+msgid "loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:37
+msgid "Search truncated"
+msgstr ""
+
+#: kallithea/templates/base/root.html:38
+msgid "No matching files"
+msgstr ""
+
+#: kallithea/templates/base/root.html:39
+msgid "Open New Pull Request from {0}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:40
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Επιλογή σετ αλλαγών"
+
+#: kallithea/templates/base/root.html:52
+msgid "Specify changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:23
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changelog/changelog.html:47
+msgid "Clear selection"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:54
+msgid "Go to tip of repository"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
+#, python-format
+msgid "Compare fork with %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:61
+#, python-format
+msgid "Compare fork with parent repository (%s)"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:65
+#: kallithea/templates/files/files.html:29
+msgid "Branch filter:"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:221
+msgid "There are no changes yet"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:4
+#: kallithea/templates/changeset/changeset.html:77
+msgid "Removed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:5
+#: kallithea/templates/changeset/changeset.html:78
+msgid "Changed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:6
+#: kallithea/templates/changeset/changeset.html:79
+#: kallithea/templates/changeset/diff_block.html:38
+msgid "Added"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:8
+#: kallithea/templates/changelog/changelog_details.html:9
+#: kallithea/templates/changelog/changelog_details.html:10
+#: kallithea/templates/changeset/changeset.html:81
+#: kallithea/templates/changeset/changeset.html:82
+#: kallithea/templates/changeset/changeset.html:83
+#, python-format
+msgid "Affected %s files"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%s committed on %s"
+msgid "%s comments"
+msgstr "%s συνέβαλε στο %s"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:8
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:34
+msgid "Changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
+msgid "Raw diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:46
+msgid "Patch diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
+msgid "Download diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
+msgid "Merge"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:96
+msgid "Grafted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:102
+msgid "Transplanted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:108
+msgid "Replaced by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:122
+msgid "Preceded by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
+#, python-format
+msgid "%s file changed"
+msgid_plural "%s files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
+#, python-format
+msgid "%s file changed with %s insertions and %s deletions"
+msgid_plural "%s files changed with %s insertions and %s deletions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Show full diff anyway"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "%s committed on %s"
+msgid "comment"
+msgstr "%s συνέβαλε στο %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:21
+msgid "on pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:22
+msgid "No title"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:24
+msgid "on this changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+msgid "Delete comment?"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
+msgid "Status change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
+msgid "Comment"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "Login now"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:141
+msgid "Hide"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:153
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
+msgid "%d inline"
+msgid_plural "%d inline"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
+msgid "%d general"
+msgid_plural "%d general"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
+msgid "Files affected"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Mη τροποποιημένo"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Άγνωστη αναθεώρηση %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Χωρίς όνομα αρχείου"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "New file type"
+msgid "File after"
+msgstr "Άγνωστος τύπος αρχειοθέτησης"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
+msgid "Show merge diff"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:54
+msgid "is"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
+msgid "%s changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:56
+msgid "behind"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:6
+#: kallithea/templates/compare/compare_diff.html:8
+#, python-format
+msgid "%s Compare"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:13
+#: kallithea/templates/compare/compare_diff.html:41
+msgid "Compare Revisions"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:39
+msgid "Swap"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:48
+msgid "Compare revisions, branches, bookmarks, or tags."
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
+#, python-format
+msgid "Showing %s commit"
+msgid_plural "Showing %s commits"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/compare/compare_diff.html:95
+msgid "Show full diff"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:23
+msgid "Public repository"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:29
+msgid "Repository creation in progress..."
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:42
+msgid "No changesets yet"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:76
+msgid "Creating"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset"
+msgid "Changeset on"
+msgstr "Σετ αλλαγών"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Κλάδος"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr "Αυτό το αίτημα έλξης έχει κλείσει και δεν μπορεί να ενημερωθεί."
+
+#: kallithea/templates/email_templates/password_reset.html:9
+#, python-format
+msgid "Hello %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:16
+msgid "We have received a request to reset the password for your account."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
+msgid "To set a new password, click the following link"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Finish pull request"
+msgid "Pull request"
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Error creating pull request: %s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Error creating pull request: %s"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Λάθος στη δημιουργία αιτήματος έλξης - pull request: %s"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:15
+#, python-format
+msgid "%s File side-by-side diff"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:19
+#: kallithea/templates/files/file_diff.html:8
+msgid "File diff"
+msgstr ""
+
+#: kallithea/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File Diff"
+msgstr ""
+
+#: kallithea/templates/files/files.html:4
+#: kallithea/templates/files/files.html:77
+#, python-format
+msgid "%s Files"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:4
+#, python-format
+msgid "%s Files Add"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
+#: kallithea/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:41
+msgid "Enter filename..."
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
+msgid "or"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+msgid "Upload File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:47
+msgid "Create New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:53
+#, fuzzy
+msgid "New file type"
+msgstr "Άγνωστος τύπος αρχειοθέτησης"
+
+#: kallithea/templates/files/files_add.html:64
+#: kallithea/templates/files/files_delete.html:34
+#: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
+msgid "Commit Changes"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:37
+msgid "Search File List"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:42
+msgid "Loading file list..."
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
+msgid "Size"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:53
+msgid "Last Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:54
+msgid "Last Modified"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:55
+msgid "Last Committer"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:4
+#, python-format
+msgid "%s Files Delete"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:12
+#: kallithea/templates/files/files_delete.html:30
+msgid "Delete file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:4
+#, python-format
+msgid "%s File Edit"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:21
+msgid "Edit file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
+msgid "Show Annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
+msgid "Source"
+msgstr ""
+
+#: kallithea/templates/files/files_history_box.html:2
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/files/files_source.html:6
+msgid "Diff to Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:7
+msgid "Show at Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:10
+msgid "Show Authors"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:26
+msgid "Show Source"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:41
+msgid "Deleting files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:58
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:73
+msgid "Show as raw."
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:23
+msgid "Go Back"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:24
+msgid "No files at given path"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:9
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
+msgid "Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers_data.html:9
+msgid "Started following -"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:5
+#, python-format
+msgid "Fork repository %s"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:25
+msgid "Fork name"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:53
+msgid "Default revision for files page, downloads, whoosh, and readme."
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:58
+msgid "Private"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:66
+msgid "Copy permissions"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:69
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:75
+msgid "Update after clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:78
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:85
+msgid "Fork this Repository"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:9
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
+msgid "Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:14
+msgid "Forked"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:24
+msgid "There are no forks yet"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
+msgid "RSS journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:34
+msgid "My Repositories"
+msgstr ""
+
+#: kallithea/templates/journal/journal_data.html:42
+msgid "No entries yet"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:10
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:11
+msgid "RSS public journal feed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:4
+#: kallithea/templates/pullrequests/pullrequest.html:8
+msgid "New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:26
+#: kallithea/templates/pullrequests/pullrequest_data.html:15
+#: kallithea/templates/pullrequests/pullrequest_show.html:29
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
+msgid "Title"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:28
+msgid "Summarize the changes - or leave empty"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
+msgid "Write a short description on this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:40
+msgid "Changeset flow"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:46
+msgid "Origin repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
+msgid "Destination repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:6
+msgid "No entries"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:14
+msgid "Vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:18
+msgid "From"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:19
+msgid "To"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:28
+#, python-format
+msgid "You voted: %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:30
+msgid "You didn't vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:35
+msgid "(no title)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:37
+#: kallithea/templates/pullrequests/pullrequest_show.html:31
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
+msgid "Closed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:67
+msgid "Delete Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:68
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:70
+#, python-format
+msgid "Confirm again to delete this pull request with %s comments"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:6
+#, python-format
+msgid "%s Pull Request %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:10
+#, python-format
+msgid "Pull request %s from %s#%s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
+msgid "Summarize the changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
+msgid "Pull request status calculated from votes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
+msgid "Origin"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
+msgid "on"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
+msgid "Target"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
+msgid "Pull changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
+msgid "Current revision - no change"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
+msgid "Pull Request Content"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:6
+#, python-format
+msgid "%s Pull Requests"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:11
+#, python-format
+msgid "Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:13
+#, python-format
+msgid "Pull Requests to '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
+msgid "Open New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
+#, python-format
+msgid "Show Pull Requests to %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
+#, python-format
+msgid "Show Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:28
+msgid "Hide closed pull requests (only show open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:30
+msgid "Show closed pull requests (in addition to open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
+msgid "Pull Requests Created by Me"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
+msgid "Pull Requests I Participate In"
+msgstr ""
+
+#: kallithea/templates/search/search.html:6
+#, python-format
+msgid "%s Search"
+msgstr ""
+
+#: kallithea/templates/search/search.html:8
+#: kallithea/templates/search/search.html:16
+msgid "Search in All Repositories"
+msgstr ""
+
+#: kallithea/templates/search/search.html:47
+msgid "Search term"
+msgstr ""
+
+#: kallithea/templates/search/search.html:54
+msgid "Search in"
+msgstr ""
+
+#: kallithea/templates/search/search.html:56
+msgid "File contents"
+msgstr ""
+
+#: kallithea/templates/search/search.html:57
+msgid "Commit messages"
+msgstr ""
+
+#: kallithea/templates/search/search.html:58
+msgid "File names"
+msgstr ""
+
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
+msgid "Permission denied"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:4
+#, python-format
+msgid "%s Statistics"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:16
+#: kallithea/templates/summary/summary.html:36
+#, python-format
+msgid "%s ATOM feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:17
+#: kallithea/templates/summary/summary.html:37
+#, python-format
+msgid "%s RSS feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
+msgid "Enable"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:38
+msgid "Stats gathered: "
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
+msgid "files"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
+msgid "Show more"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:405
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:406
+msgid "files added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:407
+msgid "files changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:408
+msgid "files removed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:410
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:411
+msgid "file added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:412
+msgid "file changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:413
+msgid "file removed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:5
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:14
+#, python-format
+msgid "Repository locked by %s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:16
+msgid "Repository unlocked"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:22
+msgid "Fork of"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:27
+msgid "Clone from"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:84
+msgid "Trending files"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:98
+msgid "Download"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:101
+msgid "There are no downloads yet"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:103
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:109
+msgid "Download as zip"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:113
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:115
+msgid "With subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
+msgid "Feed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:175
+msgid "Latest Changes"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
+#, python-format
+msgid "Readme file from revision %s:%s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:298
+#, python-format
+msgid "Download %s as %s"
+msgstr ""
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr ""
+#~ "Δεν υπάρχει ευρετήριο για την αναζήτηση. Παρακαλώ τρέξτε τον whoosh "
+#~ "για την δημιουργία του"
+
+#, fuzzy
+#~| msgid "Updated repository group %s"
+#~ msgid "Repository Group"
+#~ msgstr "Ενημερώθηκε η ομάδα αποθετηρίου %s"
+
+#~ msgid "This pull request can be updated with changes on %s:"
+#~ msgstr "Αυτό το αίτημα έλξης μπορεί να ενημερωθεί με αλλαγές στο %s:"
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Κλειστό, αντικαταστάθηκε από %s."
+
+#~ msgid "Closing."
+#~ msgstr "Κλείνει."
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Ένα νέο αίτημα έλξης (pull request) δημιουργήθηκε επιτυχώς"
Binary file kallithea/i18n/en/LC_MESSAGES/kallithea.mo has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/i18n/es/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,5473 @@
+# Spanish translations for Kallithea.
+# Copyright (C) 2016 Various authors, licensing as GPLv3
+# This file is distributed under the same license as the Kallithea project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Kallithea 0.3\n"
+"Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2018-04-18 11:43+0000\n"
+"Last-Translator: Jesús Sánchez <jsanchezfdz95@gmail.com>\n"
+"Language-Team: Spanish <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/es/>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 3.0-dev\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
+msgid "There are no changesets yet"
+msgstr "Aún no hay cambios"
+
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
+#: kallithea/templates/base/perms_summary.html:14
+msgid "None"
+msgstr "Ninguno"
+
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
+msgid "(closed)"
+msgstr "(cerrado)"
+
+#: kallithea/controllers/changeset.py:83
+msgid "Show whitespace"
+msgstr "Mostrar espacios en blanco"
+
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
+#: kallithea/templates/files/diff_2way.html:55
+msgid "Ignore whitespace"
+msgstr "Ignorar espacios en blanco"
+
+#: kallithea/controllers/changeset.py:163
+#, python-format
+msgid "Increase diff context to %(num)s lines"
+msgstr "Aumentar el contexto del diff a %(num)s lineas"
+
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "No tene permiso para cambiar el estado de la petición pull"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Petición de pull %s eliminada correctamente"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
+msgid "Such revision does not exist for this repository"
+msgstr "La revisión no existe en este repositorio"
+
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "No se puede encontrar otro repositorio %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr "No se pueden comparar repositorios de diferentes tipos"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr "No se puede mostrar diff vacio"
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
+msgid "Cannot compare repositories without using common ancestor"
+msgstr "No se pueden comparar repositorios sin usar un ancestro común"
+
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "No hay respuesta"
+
+#: kallithea/controllers/error.py:71
+msgid "Unknown error"
+msgstr "Error desconocido"
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"La petición no ha podido ser atendida por el servidor debido un error de "
+"sintaxis."
+
+#: kallithea/controllers/error.py:87
+msgid "Unauthorized access to resource"
+msgstr "Acceso no autorizado al recurso"
+
+#: kallithea/controllers/error.py:89
+msgid "You don't have permission to view this page"
+msgstr "No tiene permiso para ver esta página"
+
+#: kallithea/controllers/error.py:91
+msgid "The resource could not be found"
+msgstr "No se ha encontrado el recurso"
+
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+"La petición no se ha podido completar debido a que el servidor encontró "
+"un problema inesperado."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s anotó en %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "El cambio era demasiado grande y se redució..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr "%s%s canal"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, fuzzy, python-format
+msgid "Changes on %s repository"
+msgstr "Cambios en %s repositorio"
+
+#: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr "Haga clic aquí para añadir un archivo nuevo"
+
+#: kallithea/controllers/files.py:93
+#, python-format
+msgid "There are no files yet. %s"
+msgstr "Aún no hay archivos. %s"
+
+#: kallithea/controllers/files.py:194
+#, python-format
+msgid "%s at %s"
+msgstr "%s en %s"
+
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
+#, python-format
+msgid "This repository has been locked by %s on %s"
+msgstr "Este repositorio ha sido bloqueado por %s en %s"
+
+#: kallithea/controllers/files.py:312
+msgid "You can only delete files with revision being a valid branch"
+msgstr ""
+"Sólo puede borrar archivos si la revisión pertenece a una rama válida"
+
+#: kallithea/controllers/files.py:323
+#, python-format
+msgid "Deleted file %s via Kallithea"
+msgstr "Archivo %s eliminado mediante Kallithea"
+
+#: kallithea/controllers/files.py:345
+#, python-format
+msgid "Successfully deleted file %s"
+msgstr "El archivo %s se eliminó correctamente"
+
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
+msgid "Error occurred during commit"
+msgstr "Ocurrió un error al anotar los cambios"
+
+#: kallithea/controllers/files.py:372
+msgid "You can only edit files with revision being a valid branch"
+msgstr ""
+"Sólo puede editar archivos si la revisión pertenece a una rama válida"
+
+#: kallithea/controllers/files.py:386
+#, python-format
+msgid "Edited file %s via Kallithea"
+msgstr "Archivo %s editado mediante Kallithea"
+
+#: kallithea/controllers/files.py:402
+msgid "No changes"
+msgstr "No hay cambios"
+
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "Anotado correctamente a %s"
+
+#: kallithea/controllers/files.py:438
+msgid "Added file via Kallithea"
+msgstr "Archivo añadido mediante Kallithea"
+
+#: kallithea/controllers/files.py:459
+msgid "No content"
+msgstr "Sin contenido"
+
+#: kallithea/controllers/files.py:463
+msgid "No filename"
+msgstr "Sin nombre de archivo"
+
+#: kallithea/controllers/files.py:488
+#, fuzzy
+msgid "Location must be relative path and must not contain .. in path"
+msgstr "La ruta debe ser relativa y no debe contener .. en la ruta"
+
+#: kallithea/controllers/files.py:520
+msgid "Downloads disabled"
+msgstr "Descargas deshabilitadas"
+
+#: kallithea/controllers/files.py:531
+#, python-format
+msgid "Unknown revision %s"
+msgstr "Revisión desconocida %s"
+
+#: kallithea/controllers/files.py:533
+msgid "Empty repository"
+msgstr "Repositorio vacío"
+
+#: kallithea/controllers/files.py:535
+msgid "Unknown archive type"
+msgstr "Tipo de archivo desconocido"
+
+#: kallithea/controllers/files.py:756
+#: kallithea/templates/changeset/changeset_range.html:9
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
+msgid "Changesets"
+msgstr "Cambios"
+
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
+msgid "Branches"
+msgstr "Ramas"
+
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
+msgid "Tags"
+msgstr "Etiquetas"
+
+#: kallithea/controllers/forks.py:174
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "Ocurrió un error mientras se bifurcaba el repositorio %s"
+
+#: kallithea/controllers/home.py:78
+msgid "Groups"
+msgstr "Grupos"
+
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
+#: kallithea/templates/admin/repos/repo_add.html:12
+#: kallithea/templates/admin/repos/repo_add.html:16
+#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/users/user_edit_advanced.html:6
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
+msgid "Repositories"
+msgstr "Repositorios"
+
+#: kallithea/controllers/home.py:121
+#: kallithea/templates/files/files_add.html:32
+#: kallithea/templates/files/files_delete.html:23
+#: kallithea/templates/files/files_edit.html:32
+msgid "Branch"
+msgstr "Rama"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Ramas cerradas"
+
+#: kallithea/controllers/home.py:133
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: kallithea/controllers/home.py:139
+msgid "Bookmark"
+msgstr "Marcador"
+
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
+#: kallithea/templates/journal/public_journal.html:4
+#: kallithea/templates/journal/public_journal.html:18
+msgid "Public Journal"
+msgstr "Registro público"
+
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
+msgid "Journal"
+msgstr "Registro"
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
+msgid "Bad captcha"
+msgstr "CAPTCHA erróneo"
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "El registro en %s se ha efectuado correctamente"
+
+#: kallithea/controllers/login.py:189
+msgid "A password reset confirmation code has been sent"
+msgstr "Se ha enviado una confirmación de restauración de contraseña"
+
+#: kallithea/controllers/login.py:238
+msgid "Invalid password reset token"
+msgstr "Señal de restauración de contraseña inválida"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
+msgid "Successfully updated password"
+msgstr "Contraseña actualizada correctamente"
+
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "El validador \"%s\" no es correcto"
+
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr "%s (cerrado)"
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr "Cambio"
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr "Especial"
+
+#: kallithea/controllers/pullrequests.py:182
+#, fuzzy
+msgid "Peer branches"
+msgstr "Ramas de los pares"
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr "Marcadores"
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr "Error al crear la petición de pull: %s"
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr "Ocurrió un error al crear la petición de pull"
+
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "La petición de pull se ha creado correctamente"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "Actualización de la petición pull creada"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "No hay descripción"
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr "Petición pull actualizada"
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr "Petición pull eliminada correctamente"
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "No se encontraron cambios para actualizar la petición pull."
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr "La petición pull ya ha sido incluida a %s."
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr "La petición pull esta cerrada y no se puede actualizar."
+
+#: kallithea/controllers/pullrequests.py:543
+#, fuzzy, python-format
+#| msgid "The following changes are available on %s:"
+msgid "The following additional changes are available on %s:"
+msgstr "Los siguientes cambios están disponibles en %s:"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
+msgstr "No se encontraron cambios para actualizar la petición pull."
+
+#: kallithea/controllers/pullrequests.py:557
+#, fuzzy, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr "Nota: la rama %s tiene otro head: %s."
+
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
+msgstr "La peticiones pull de Git aún no soportan actualizaciones."
+
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "No se encontraron cambios para actualizar la petición pull."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
+msgid "Invalid search query. Try quoting it."
+msgstr "Consulta de búsqueda inválida. Inténtelo entre comillas."
+
+#: kallithea/controllers/search.py:140
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
+msgid "An error occurred during search operation."
+msgstr "Ocurrió un error mientras se ejecutaba la búsqueda."
+
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
+msgid "No data ready yet"
+msgstr "Todavía no hay datos disponibles"
+
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
+msgid "Statistics are disabled for this repository"
+msgstr "Las estadísticas están deshabilitadas en este repositorio"
+
+#: kallithea/controllers/admin/auth_settings.py:135
+msgid "Auth settings updated successfully"
+msgstr "Los ajustes de autentificación se han actualizado correctamente"
+
+#: kallithea/controllers/admin/auth_settings.py:146
+msgid "error occurred during update of auth settings"
+msgstr "ocurrió un error al actualizar los ajustes de autentificación"
+
+#: kallithea/controllers/admin/defaults.py:75
+msgid "Default settings updated successfully"
+msgstr "Los ajustes predeterminados se han actualizado correctamente"
+
+#: kallithea/controllers/admin/defaults.py:90
+msgid "Error occurred during update of defaults"
+msgstr "Ocurrió un error al actualizar los ajustes predeterminados"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "Para siempre"
+
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 minutos"
+
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 hora"
+
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 día"
+
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
+msgid "1 month"
+msgstr "1 mes"
+
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
+msgid "Lifetime"
+msgstr "Tiempo de vida"
+
+#: kallithea/controllers/admin/gists.py:140
+msgid "Error occurred during gist creation"
+msgstr "Ocurrió un error mientras se creaba el gist"
+
+#: kallithea/controllers/admin/gists.py:156
+#, python-format
+msgid "Deleted gist %s"
+msgstr "Gist %s eliminado"
+
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr "Sin modificar"
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr "Gist actualizado correctamente"
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:233
+#, python-format
+msgid "Error occurred during update of gist %s"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:117
+msgid "Your account was updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
+#, python-format
+msgid "Error occurred during update of user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:166
+msgid "Error occurred during update of user password"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
+msgid "An error occurred during email saving"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
+msgid "Removed email from user"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
+msgid "API key successfully deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
+#: kallithea/templates/admin/auth/auth_settings.html:9
+#: kallithea/templates/admin/defaults/defaults.html:9
+#: kallithea/templates/admin/permissions/permissions.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/admin/repos/repo_add.html:10
+#: kallithea/templates/admin/repos/repo_add.html:14
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/settings/settings.html:9
+#: kallithea/templates/admin/user_groups/user_group_add.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/admin/users/user_add.html:8
+#: kallithea/templates/admin/users/user_edit.html:9
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
+#: kallithea/templates/base/perms_summary.html:17
+msgid "Admin"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:76
+#: kallithea/controllers/admin/permissions.py:87
+#: kallithea/controllers/admin/permissions.py:92
+#: kallithea/controllers/admin/permissions.py:95
+#: kallithea/controllers/admin/permissions.py:98
+#: kallithea/controllers/admin/permissions.py:101
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
+msgid "Enabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:125
+msgid "Global permissions updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:140
+msgid "Error occurred during update of permissions"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:174
+#, python-format
+msgid "Error occurred during creation of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:179
+#, python-format
+msgid "Created repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:226
+#, python-format
+msgid "Updated repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:242
+#, python-format
+msgid "Error occurred during update of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:252
+#, python-format
+msgid "This group contains %s repositories and cannot be deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:259
+#, python-format
+msgid "This group contains %s subgroups and cannot be deleted"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:265
+#, python-format
+msgid "Removed repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:270
+#, python-format
+msgid "Error occurred during deletion of repository group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
+msgid "Cannot revoke permission for yourself as admin"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:369
+msgid "Repository group permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
+msgid "An error occurred during revoking of permission"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:137
+#, python-format
+msgid "Error creating repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:195
+#, python-format
+msgid "Created repository %s from %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:204
+#, python-format
+msgid "Forked repository %s as %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:207
+#, python-format
+msgid "Created repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:236
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:256
+#, python-format
+msgid "Error occurred during update of repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:274
+#, python-format
+msgid "Detached %s forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:277
+#, python-format
+msgid "Deleted %s forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:282
+#, python-format
+msgid "Deleted repository %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:285
+#, python-format
+msgid "Cannot delete repository %s which still has forks"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:290
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:330
+msgid "Repository permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during repository forking %s"
+msgid "An error occurred during creation of field: %r"
+msgstr "Ocurrió un error mientras se bifurcaba el repositorio %s"
+
+#: kallithea/controllers/admin/repos.py:401
+msgid "An error occurred during removal of field"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:415
+msgid "-- Not a fork --"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
+msgid "Repository has been locked"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
+msgid "Repository has been unlocked"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
+msgid "An error occurred during unlocking"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:528
+msgid "Cache invalidation successful"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:532
+msgid "An error occurred during cache invalidation"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:545
+msgid "Pulled from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:548
+msgid "An error occurred during pull from remote location"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:579
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:135
+msgid "Updated VCS settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:174
+#, python-format
+msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:189
+#, python-format
+msgid "Invalidated %s repositories"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:230
+msgid "Updated application settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:283
+msgid "Updated visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:288
+msgid "Error occurred during updating visualisation settings"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:312
+msgid "Please enter email address"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:327
+msgid "Send email task created"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Todavía no hay datos disponibles"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
+msgid "Added new hook"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:376
+msgid "Updated hooks"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:380
+msgid "Error occurred during hook creation"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:404
+msgid "Whoosh reindex task scheduled"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:143
+#, python-format
+msgid "Created user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:156
+#, python-format
+msgid "Error occurred during creation of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:184
+#, python-format
+msgid "Updated user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:206
+#, python-format
+msgid "Error occurred during update of user group %s"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:217
+msgid "Successfully deleted user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:222
+msgid "An error occurred during deletion of user group"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:278
+msgid "Target group cannot be the same"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:284
+msgid "User group permissions updated"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
+msgid "Updated permissions"
+msgstr ""
+
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:123
+#, python-format
+msgid "Created user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:138
+#, python-format
+msgid "Error occurred during creation of user %s"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:162
+msgid "User updated successfully"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:190
+msgid "Successfully deleted user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:195
+msgid "An error occurred during deletion of user"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:203
+msgid "The default user cannot be edited"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:412
+#, python-format
+msgid "Added IP address %s to user whitelist"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:418
+msgid "An error occurred while adding IP address"
+msgstr ""
+
+#: kallithea/controllers/admin/users.py:430
+msgid "Removed IP address from user whitelist"
+msgstr ""
+
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
+msgid "Repository not found in the filesystem"
+msgstr ""
+
+#: kallithea/lib/base.py:608
+#, python-format
+msgid "Changeset for %s %s not found in %s"
+msgstr ""
+
+#: kallithea/lib/diffs.py:193
+msgid "Binary file"
+msgstr ""
+
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
+msgid "No changes detected"
+msgstr ""
+
+#: kallithea/lib/helpers.py:612
+#, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
+msgid "Created tag: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:625
+#, python-format
+msgid "Changeset %s not found"
+msgstr ""
+
+#: kallithea/lib/helpers.py:674
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:680
+msgid "Compare view"
+msgstr ""
+
+#: kallithea/lib/helpers.py:699
+msgid "and"
+msgstr ""
+
+#: kallithea/lib/helpers.py:700
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
+msgid "revisions"
+msgstr ""
+
+#: kallithea/lib/helpers.py:725
+#, python-format
+msgid "Fork name %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:746
+#, python-format
+msgid "Pull request %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:756
+msgid "[deleted] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
+msgid "[created] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:760
+msgid "[created] repository as fork"
+msgstr ""
+
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
+msgid "[forked] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
+msgid "[updated] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:766
+msgid "[downloaded] archive from repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:768
+msgid "[delete] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:776
+msgid "[created] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:778
+msgid "[updated] user"
+msgstr ""
+
+#: kallithea/lib/helpers.py:780
+msgid "[created] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:782
+msgid "[updated] user group"
+msgstr ""
+
+#: kallithea/lib/helpers.py:784
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:786
+msgid "[commented] on pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:788
+msgid "[closed] pull request for"
+msgstr ""
+
+#: kallithea/lib/helpers.py:790
+msgid "[pushed] into"
+msgstr ""
+
+#: kallithea/lib/helpers.py:792
+msgid "[committed via Kallithea] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:794
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:796
+msgid "[pulled] from"
+msgstr ""
+
+#: kallithea/lib/helpers.py:798
+msgid "[started following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:800
+msgid "[stopped following] repository"
+msgstr ""
+
+#: kallithea/lib/helpers.py:928
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
+msgid "No files"
+msgstr ""
+
+#: kallithea/lib/helpers.py:957
+msgid "new file"
+msgstr ""
+
+#: kallithea/lib/helpers.py:960
+msgid "mod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:963
+msgid "del"
+msgstr ""
+
+#: kallithea/lib/helpers.py:966
+msgid "rename"
+msgstr ""
+
+#: kallithea/lib/helpers.py:971
+msgid "chmod"
+msgstr ""
+
+#: kallithea/lib/helpers.py:1264
+#, python-format
+msgid ""
+"%s repository is not mapped to db perhaps it was created or renamed from "
+"the filesystem please run the application again in order to rescan "
+"repositories"
+msgstr ""
+
+#: kallithea/lib/utils2.py:333
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:334
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:335
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:336
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:337
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:338
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/lib/utils2.py:354
+#, python-format
+msgid "in %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:356
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:358
+#, python-format
+msgid "in %s and %s"
+msgstr ""
+
+#: kallithea/lib/utils2.py:361
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: kallithea/lib/utils2.py:364
+msgid "just now"
+msgstr ""
+
+#: kallithea/model/comment.py:68
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
+msgid "[Mention]"
+msgstr ""
+
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
+msgid "Default user has no access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1706
+msgid "Default user has read access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1707
+msgid "Default user has write access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1708
+msgid "Default user has admin access to new repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1710
+msgid "Default user has no access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1711
+msgid "Default user has read access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1712
+msgid "Default user has write access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1713
+msgid "Default user has admin access to new repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1715
+msgid "Default user has no access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1716
+msgid "Default user has read access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1717
+msgid "Default user has write access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1718
+msgid "Default user has admin access to new user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1720
+msgid "Only admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1721
+msgid "Non-admins can create repository groups"
+msgstr ""
+
+#: kallithea/model/db.py:1723
+msgid "Only admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1724
+msgid "Non-admins can create user groups"
+msgstr ""
+
+#: kallithea/model/db.py:1726
+msgid "Only admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1727
+msgid "Non-admins can create top level repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
+msgid "Only admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1733
+msgid "Non-admins can fork repositories"
+msgstr ""
+
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
+msgid "User registration with manual account activation"
+msgstr ""
+
+#: kallithea/model/db.py:1737
+msgid "User registration with automatic account activation"
+msgstr ""
+
+#: kallithea/model/db.py:2263
+msgid "Not reviewed"
+msgstr ""
+
+#: kallithea/model/db.py:2264
+msgid "Under review"
+msgstr ""
+
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr ""
+
+#: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: kallithea/model/forms.py:170
+msgid "Name must not contain only digits"
+msgstr ""
+
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
+#, python-format
+msgid "New user %(new_username)s registered"
+msgstr ""
+
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
+msgid "Closing"
+msgstr ""
+
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "Error al crear la petición de pull: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "Cambios que faltan desde la ultima petición de pull:"
+
+#: kallithea/model/pull_request.py:348
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Cambios nuevos en %s %s desde la ultima petición pull:"
+
+#: kallithea/model/pull_request.py:355
+#, fuzzy
+#| msgid "Ancestor didn't change - show diff since previous version:"
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+"El ascendente no ha cambiado - ver diferencias desde la versión anterior:"
+
+#: kallithea/model/pull_request.py:362
+#, fuzzy, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"La petición de pull está basada en otra %s revisión y no hay un diff "
+"simple."
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "No changes found on %s %s since previous version."
+msgid "No changes found on %s %s since previous iteration."
+msgstr "No se encontró ningún cambio en %s %s desde la versión anterior."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
+msgid "latest tip"
+msgstr ""
+
+#: kallithea/model/user.py:189
+msgid "New user registration"
+msgstr ""
+
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:263
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:364
+msgid "Password reset link"
+msgstr ""
+
+#: kallithea/model/user.py:413
+msgid "Password reset notification"
+msgstr ""
+
+#: kallithea/model/user.py:414
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: kallithea/model/validators.py:74
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:76
+#, python-format
+msgid "Username \"%(username)s\" cannot be used"
+msgstr ""
+
+#: kallithea/model/validators.py:78
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
+msgid "The input is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:133
+msgid "Invalid user group name"
+msgstr ""
+
+#: kallithea/model/validators.py:134
+#, python-format
+msgid "User group \"%(usergroup)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:136
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:176
+msgid "Cannot assign this group as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:177
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:179
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: kallithea/model/validators.py:250
+msgid "Invalid old password"
+msgstr ""
+
+#: kallithea/model/validators.py:266
+msgid "Passwords do not match"
+msgstr ""
+
+#: kallithea/model/validators.py:281
+msgid "Invalid username or password"
+msgstr ""
+
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:331
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: kallithea/model/validators.py:333
+#, python-format
+msgid "Repository group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: kallithea/model/validators.py:419
+msgid "Invalid repository URL"
+msgstr ""
+
+#: kallithea/model/validators.py:420
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:445
+msgid "Fork has to be the same type as parent"
+msgstr ""
+
+#: kallithea/model/validators.py:460
+msgid "You don't have permissions to create repository in this group"
+msgstr ""
+
+#: kallithea/model/validators.py:462
+msgid "no permission to create repository in root location"
+msgstr ""
+
+#: kallithea/model/validators.py:512
+msgid "You don't have permissions to create a group in this location"
+msgstr ""
+
+#: kallithea/model/validators.py:552
+msgid "This username or user group name is not valid"
+msgstr ""
+
+#: kallithea/model/validators.py:645
+msgid "This is not a valid path"
+msgstr ""
+
+#: kallithea/model/validators.py:662
+msgid "This email address is already in use"
+msgstr ""
+
+#: kallithea/model/validators.py:682
+#, python-format
+msgid "Email address \"%(email)s\" not found"
+msgstr ""
+
+#: kallithea/model/validators.py:719
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:731
+msgid "Please enter a valid IPv4 or IPv6 address"
+msgstr ""
+
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
+msgid "Key name can only consist of letters, underscore, dash or numbers"
+msgstr ""
+
+#: kallithea/model/validators.py:779
+msgid "Filename cannot be inside a directory"
+msgstr ""
+
+#: kallithea/model/validators.py:795
+#, python-format
+msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
+msgstr ""
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
+msgid "About"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add.html:5
+#: kallithea/templates/admin/repos/repo_add.html:19
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
+msgid "Add Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:5
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "You have admin right to this group, and can edit it"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
+msgid "Edit Repository Group"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
+#: kallithea/templates/pullrequests/pullrequest_show.html:38
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
+msgid "Description"
+msgstr ""
+
+#: kallithea/templates/index_base.html:60
+msgid "Last Change"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
+msgid "Tip"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
+#: kallithea/templates/pullrequests/pullrequest_data.html:16
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
+msgid "Owner"
+msgstr ""
+
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
+msgid "Log In"
+msgstr ""
+
+#: kallithea/templates/login.html:17
+#, python-format
+msgid "Log In to %s"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:5
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
+msgid "Username"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
+msgid "Password"
+msgstr ""
+
+#: kallithea/templates/login.html:44
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
+msgid "Forgot your password ?"
+msgstr ""
+
+#: kallithea/templates/login.html:55
+msgid "Don't have an account ?"
+msgstr ""
+
+#: kallithea/templates/login.html:62
+msgid "Sign In"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:5
+msgid "Password Reset"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
+msgid "Reset Your Password to %s"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:23
+#: kallithea/templates/password_reset_confirmation.html:5
+#: kallithea/templates/password_reset_confirmation.html:18
+msgid "Reset Your Password"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:30
+msgid "Email Address"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
+msgid "Captcha"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
+#, python-format
+msgid "You are about to set a new password for the email address %s."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
+msgid "Code you received in the email"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:36
+msgid "New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:43
+msgid "Confirm New Password"
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:51
+msgid "Confirm"
+msgstr ""
+
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
+msgid "Sign Up"
+msgstr ""
+
+#: kallithea/templates/register.html:22
+#, python-format
+msgid "Sign Up to %s"
+msgstr ""
+
+#: kallithea/templates/register.html:45
+msgid "Re-enter password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
+msgid "First Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
+msgid "Last Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
+#: kallithea/templates/admin/settings/settings.html:31
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
+msgid "Email"
+msgstr ""
+
+#: kallithea/templates/register.html:85
+msgid "Registered accounts are ready to use and need no further action."
+msgstr ""
+
+#: kallithea/templates/register.html:87
+msgid "Please wait for an administrator to activate your account."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:5
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/base/base.html:55
+msgid "Admin Journal"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
+msgid "journal filter..."
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:12
+#: kallithea/templates/journal/journal.html:12
+msgid "Filter"
+msgstr ""
+
+#: kallithea/templates/admin/admin.html:13
+#: kallithea/templates/journal/journal.html:13
+#, python-format
+msgid "%s Entry"
+msgid_plural "%s Entries"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/admin_log.html:6
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
+msgid "Action"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:8
+msgid "Date"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr ""
+
+#: kallithea/templates/admin/admin_log.html:61
+msgid "No actions yet"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:5
+msgid "Authentication Settings"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:11
+#: kallithea/templates/base/base.html:61
+msgid "Authentication"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:27
+msgid "Authentication Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
+msgid "Enabled Plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
+msgid "Available built-in plugins"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
+msgid "Plugin"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:101
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
+msgid "Save"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:5
+#: kallithea/templates/admin/defaults/defaults.html:11
+#: kallithea/templates/base/base.html:62
+msgid "Repository Defaults"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
+msgid "Type"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
+msgid "Enable locking"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:5
+#: kallithea/templates/admin/gists/edit.html:18
+msgid "Edit Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was update since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
+msgid "Gist description ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
+msgid "Gist lifetime"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/edit.html:61
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/gists/show.html:47
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
+msgid "Expires"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+msgid "Never"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:145
+msgid "Update Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:146
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
+msgid "Cancel"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:6
+#: kallithea/templates/admin/gists/index.html:16
+#, python-format
+msgid "Private Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:8
+#: kallithea/templates/admin/gists/index.html:18
+#, python-format
+msgid "Public Gists for User %s"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:10
+#: kallithea/templates/admin/gists/index.html:20
+msgid "Public Gists"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:37
+#: kallithea/templates/admin/gists/show.html:25
+#: kallithea/templates/base/base.html:312
+msgid "Create New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
+msgid "Created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/index.html:66
+msgid "There are no gists yet"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:5
+#: kallithea/templates/admin/gists/new.html:18
+msgid "New Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
+msgid "Create Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:54
+msgid "Create Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
+msgid "Reset"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:5
+#: kallithea/templates/admin/gists/show.html:9
+msgid "Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:10
+msgid "URL"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:37
+msgid "Private Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/pullrequests/pullrequest_data.html:20
+msgid "Delete"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:54
+msgid "Confirm to delete this Gist"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
+msgid "Show as Raw"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:69
+msgid "created"
+msgstr ""
+
+#: kallithea/templates/admin/gists/show.html:82
+msgid "Show as raw"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:5
+#: kallithea/templates/admin/my_account/my_account.html:9
+#: kallithea/templates/base/base.html:397
+msgid "My Account"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:25
+#: kallithea/templates/admin/users/user_edit.html:29
+msgid "Profile"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:26
+msgid "Email Addresses"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:28
+#: kallithea/templates/admin/users/user_edit.html:31
+msgid "API Keys"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:29
+msgid "Owned Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
+msgid "Watched Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account.html:31
+#: kallithea/templates/admin/permissions/permissions.html:30
+#: kallithea/templates/admin/user_groups/user_group_edit.html:32
+#: kallithea/templates/admin/users/user_edit.html:34
+msgid "Show Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
+msgid "Built-in"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
+#, python-format
+msgid "Confirm to reset this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
+msgid "Expired"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
+#, python-format
+msgid "Confirm to remove this API key: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
+msgid "Remove"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
+msgid "No additional API keys specified"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
+msgid "New API key"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
+msgid "Add"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
+msgid "Primary"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
+msgid "No additional emails specified."
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
+msgid "New email address"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:1
+msgid "Change Your Account Password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:8
+msgid "Current password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
+msgid "New password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:22
+msgid "Confirm new password"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:12
+#: kallithea/templates/admin/users/user_edit_profile.html:12
+msgid "Avatars are disabled"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:1
+msgid "Repositories You Own"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_watched.html:1
+msgid "Repositories You are Watching"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:5
+#: kallithea/templates/admin/permissions/permissions.html:11
+#: kallithea/templates/base/base.html:60
+msgid "Default Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:28
+#: kallithea/templates/admin/settings/settings.html:29
+msgid "Global"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions.html:29
+#: kallithea/templates/admin/users/user_edit.html:32
+msgid "IP Whitelist"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
+msgid "Anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+msgid ""
+"All default permissions on each repository will be reset to chosen "
+"permission, note that all custom default permission on repositories will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
+msgid "Apply to all existing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:32
+msgid ""
+"All default permissions on each repository group will be reset to chosen "
+"permission, note that all custom default permission on repository groups "
+"will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid ""
+"All default permissions on each user group will be reset to chosen "
+"permission, note that all custom default permission on user groups will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
+msgid "Top level repository creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
+msgid ""
+"With this, write permission to a repository group allows creating "
+"repositories inside that group. Without this, group write permissions "
+"mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
+msgid "User group creation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
+msgid "Enable this to allow non-admins to create user groups."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
+msgid "External auth account activation"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
+msgid "Confirm to delete this IP address: %s"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
+msgid "All IP addresses are allowed."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
+msgid "New IP address"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
+msgid "Repository Groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
+msgid "Group name"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
+msgid "Group parent"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
+msgid "Copy parent group permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
+msgid "Copy permission set from parent repository group."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
+#, python-format
+msgid "%s Repository Group Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
+msgid "Add Child Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
+#: kallithea/templates/admin/repos/repo_edit.html:12
+#: kallithea/templates/admin/repos/repo_edit.html:25
+#: kallithea/templates/admin/settings/settings.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit.html:29
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
+msgid "Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
+#: kallithea/templates/admin/user_groups/user_group_edit.html:30
+#: kallithea/templates/admin/users/user_edit.html:33
+msgid "Advanced"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit.html:31
+msgid "Permissions"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:1
+#, python-format
+msgid "Repository Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:6
+msgid "Top level repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:7
+msgid "Total repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:8
+msgid "Children groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_advanced.html:8
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
+msgid "Created on"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:121
+#, python-format
+msgid "Confirm to delete this group: %s with %s repository"
+msgid_plural "Confirm to delete this group: %s with %s repositories"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:25
+msgid "Delete this repository group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "No hay respuesta"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
+msgid "User/User Group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
+msgid "Default"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
+msgid "Revoke"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
+msgid "Add new"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
+msgid "Apply to children"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
+msgid "Both"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
+msgid ""
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Remove this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
+msgid "Confirm to delete this group"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_show.html:4
+#, python-format
+msgid "Repository group %s"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:5
+msgid "Repository Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
+msgid "Number of Top-level Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:12
+msgid "Clone remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+msgid ""
+"Optional: URL of a remote repository. If set, the repository will be "
+"created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
+msgid "Optionally select a group to put this repository into."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
+msgid "Type of repository to create."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
+msgid "Landing revision"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:9
+#, python-format
+msgid "%s Creating Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:13
+msgid "Creating repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:27
+#, python-format
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_creating.html:39
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:8
+#, python-format
+msgid "%s Repository Settings"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:34
+msgid "Extra Fields"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:37
+msgid "Caches"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:40
+msgid "Remote"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/summary/statistics.html:8
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
+msgid "Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:1
+msgid "Parent"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:5
+msgid "Set"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
+msgid "Manually set this repository as a fork of another from the list."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
+msgid "Public Journal Visibility"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
+msgid "Remove from public journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
+msgid "Add to Public Journal"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
+msgid "Change Locking"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#, python-format
+msgid "Locked by %s on %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+msgid "Repository is not locked"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
+msgid ""
+"Force locking on the repository. Works only when anonymous access is "
+"disabled. Triggering a pull locks the repository.  The user who is "
+"pulling locks the repository; only the user who pulled and locked it can "
+"unlock it by doing a push."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
+msgid "Delete this Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
+msgid "This repository has %s fork"
+msgid_plural "This repository has %s forks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
+msgid "Detach forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+msgid "Delete forks"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:4
+msgid "Invalidate Repository Cache"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgid "Prefix"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+msgid "Key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
+msgid "Active"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+msgid "Label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
+#, python-format
+msgid "Confirm to delete this field: %s"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
+msgid "New field key"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
+msgid "New field label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
+msgid "Enter short label"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
+msgid "New field description"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
+msgid "Enter description of a field"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
+msgid "Extra fields are disabled."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
+msgid "Private Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Empty repository"
+msgid "Fork of repository"
+msgstr "Repositorio vacío"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
+msgid "Remote repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
+msgid "Pull Changes from Remote Repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
+msgid "This repository does not have a remote repository URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "Permanent Repository ID"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
+msgid "What is that?"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
+msgid "URL by id"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
+msgid ""
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
+msgid "Remote repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
+msgid "Repository URL"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
+msgid ""
+"Optional: URL of a remote repository. If set, the repository can be "
+"pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
+msgid "Change owner of this repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+msgid "Processed progress"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Reset Statistics"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:10
+msgid "Confirm to remove current statistics."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:5
+msgid "Repositories Administration"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repos.html:43
+msgid "State"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:5
+msgid "Settings Administration"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:27
+msgid "VCS"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:28
+msgid "Remap and Rescan"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:30
+msgid "Visual"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:4
+msgid "Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:33
+msgid "Full Text Search"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings.html:34
+msgid "System Info"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:4
+msgid "Send test email to"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_email.html:12
+msgid "Send"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:4
+msgid "Site branding"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:12
+msgid "HTTP authentication realm"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
+msgid "ReCaptcha public key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:35
+msgid "Public key for reCaptcha system."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:40
+msgid "ReCaptcha private key"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
+msgid "Save Settings"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:3
+msgid "Built-in Mercurial Hooks (Read-Only)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
+msgid "Failed to remove hook"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
+msgid "Delete records of missing repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:17
+msgid "Invalidate cache for all repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:20
+msgid "Check this to reload data and clear cache keys for all repositories."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:25
+msgid "Install Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
+msgid "Overwrite existing Git hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
+msgid "Rescan Repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:4
+msgid "Index build option"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:12
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
+msgid "Reindex"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
+msgid "Check for updates"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Upgrade info endpoint"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
+msgid "Note: please make sure this server can access this URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:23
+msgid "Python Packages"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:9
+msgid "Show repository size after push"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:15
+msgid "Log user push commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:21
+msgid "Log user pull commands"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:27
+msgid "Update repository after push (hg update)"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:33
+msgid "Mercurial extensions"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:47
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:64
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
+msgid "General"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:9
+msgid "Use repository extra fields"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:12
+msgid "Allows storing additional customized fields per repository."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:17
+msgid "Show Kallithea version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:20
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
+msgid ""
+"Gravatar URL allows you to use another avatar server application.\n"
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
+msgid ""
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repositories"
+msgid "Repository page size"
+msgstr "Repositorios"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:65
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:75
+msgid "Show public repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:84
+msgid "Show public/private icons next to repository names."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:89
+msgid "Meta Tagging"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:5
+msgid "Add user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:10
+#: kallithea/templates/admin/user_groups/user_group_edit.html:11
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
+msgid "User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:12
+#: kallithea/templates/admin/user_groups/user_groups.html:24
+msgid "Add User Group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
+msgid "Short, optional description for this user group."
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:5
+#, python-format
+msgid "%s user group settings"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit.html:33
+msgid "Show Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:1
+#, python-format
+msgid "User Group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
+msgid "Members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
+#: kallithea/templates/data_table/_dt_elements.html:102
+#, python-format
+msgid "Confirm to delete this user group: %s"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:21
+msgid "Delete this user group"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
+msgid "No members yet"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
+msgid "Chosen group members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
+msgid "Available members"
+msgstr ""
+
+#: kallithea/templates/admin/user_groups/user_groups.html:5
+msgid "User Groups Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:10
+#: kallithea/templates/admin/users/user_edit.html:11
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
+msgid "Users"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:12
+#: kallithea/templates/admin/users/users.html:23
+msgid "Add User"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_add.html:41
+msgid "Password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:5
+#, python-format
+msgid "%s user settings"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit.html:30
+msgid "Emails"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:1
+#, python-format
+msgid "User: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:7
+#: kallithea/templates/admin/users/user_edit_profile.html:32
+msgid "Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:9
+#: kallithea/templates/admin/users/users.html:41
+msgid "Last Login"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:10
+msgid "Member of User Groups"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:21
+#: kallithea/templates/data_table/_dt_elements.html:90
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_advanced.html:23
+msgid "Delete this user"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_ips.html:7
+#, python-format
+msgid "Inherited from %s"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:39
+msgid "Name in Source of Record"
+msgstr ""
+
+#: kallithea/templates/admin/users/user_edit_profile.html:53
+msgid "New password confirmation"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:5
+msgid "Users Administration"
+msgstr ""
+
+#: kallithea/templates/admin/users/users.html:44
+msgid "Auth Type"
+msgstr ""
+
+#: kallithea/templates/base/base.html:16
+#, python-format
+msgid "Server instance: %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:28
+msgid "Support"
+msgstr ""
+
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
+msgid "Mercurial repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
+msgid "Git repository"
+msgstr ""
+
+#: kallithea/templates/base/base.html:115
+msgid "Create Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
+msgid "Summary"
+msgstr ""
+
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
+msgid "Changelog"
+msgstr ""
+
+#: kallithea/templates/base/base.html:133
+#: kallithea/templates/files/files.html:11
+msgid "Files"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
+msgid "Options"
+msgstr ""
+
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
+msgid "Compare Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:158
+msgid "Compare"
+msgstr ""
+
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
+#: kallithea/templates/search/search.html:14
+#: kallithea/templates/search/search.html:67
+msgid "Search"
+msgstr ""
+
+#: kallithea/templates/base/base.html:164
+msgid "Unlock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:166
+msgid "Lock"
+msgstr ""
+
+#: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
+msgid "Unfollow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:178
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
+msgid "Create Pull Request"
+msgstr ""
+
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:313
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:315
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:316
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:321
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:360
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:369
+msgid "Login to Your Account"
+msgstr ""
+
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
+msgid "Log Out"
+msgstr ""
+
+#: kallithea/templates/base/base.html:524
+msgid "Parent rev."
+msgstr ""
+
+#: kallithea/templates/base/base.html:533
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
+msgid "Inherit defaults"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:15
+#, python-format
+msgid ""
+"Select to inherit global settings, IP whitelist and permissions from the "
+"%s."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:23
+msgid "Create repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:33
+msgid "Create user groups"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:37
+msgid "Select this option to allow user group creation for this user"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:43
+msgid "Fork repositories"
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:47
+msgid "Select this option to allow repository forking for this user"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:13
+#: kallithea/templates/changelog/changelog.html:41
+msgid "Show"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:22
+msgid "No permissions defined yet"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:30
+#: kallithea/templates/base/perms_summary.html:55
+msgid "Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:32
+#: kallithea/templates/base/perms_summary.html:57
+msgid "Edit Permission"
+msgstr ""
+
+#: kallithea/templates/base/perms_summary.html:92
+msgid "No permission defined"
+msgstr ""
+
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:30
+msgid "Unable to post"
+msgstr ""
+
+#: kallithea/templates/base/root.html:31
+msgid "Add Another Comment"
+msgstr ""
+
+#: kallithea/templates/base/root.html:32
+msgid "Stop following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:33
+msgid "Start following this repository"
+msgstr ""
+
+#: kallithea/templates/base/root.html:34
+msgid "Group"
+msgstr ""
+
+#: kallithea/templates/base/root.html:35
+msgid "Loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:36
+msgid "loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:37
+msgid "Search truncated"
+msgstr ""
+
+#: kallithea/templates/base/root.html:38
+msgid "No matching files"
+msgstr ""
+
+#: kallithea/templates/base/root.html:39
+msgid "Open New Pull Request from {0}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:40
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Seleccionar cambios"
+
+#: kallithea/templates/base/root.html:52
+msgid "Specify changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:23
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changelog/changelog.html:47
+msgid "Clear selection"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:54
+msgid "Go to tip of repository"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
+#, python-format
+msgid "Compare fork with %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:61
+#, python-format
+msgid "Compare fork with parent repository (%s)"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:65
+#: kallithea/templates/files/files.html:29
+msgid "Branch filter:"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:221
+msgid "There are no changes yet"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:4
+#: kallithea/templates/changeset/changeset.html:77
+msgid "Removed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:5
+#: kallithea/templates/changeset/changeset.html:78
+msgid "Changed"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:6
+#: kallithea/templates/changeset/changeset.html:79
+#: kallithea/templates/changeset/diff_block.html:38
+msgid "Added"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_details.html:8
+#: kallithea/templates/changelog/changelog_details.html:9
+#: kallithea/templates/changelog/changelog_details.html:10
+#: kallithea/templates/changeset/changeset.html:81
+#: kallithea/templates/changeset/changeset.html:82
+#: kallithea/templates/changeset/changeset.html:83
+#, python-format
+msgid "Affected %s files"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%s committed on %s"
+msgid "%s comments"
+msgstr "%s anotó en %s"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:8
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:34
+msgid "Changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
+msgid "Raw diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:46
+msgid "Patch diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
+msgid "Download diff"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
+msgid "Merge"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:96
+msgid "Grafted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:102
+msgid "Transplanted from:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:108
+msgid "Replaced by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:122
+msgid "Preceded by:"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
+#, python-format
+msgid "%s file changed"
+msgid_plural "%s files changed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
+#, python-format
+msgid "%s file changed with %s insertions and %s deletions"
+msgid_plural "%s files changed with %s insertions and %s deletions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Show full diff anyway"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "%s committed on %s"
+msgid "comment"
+msgstr "%s anotó en %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:21
+msgid "on pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:22
+msgid "No title"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:24
+msgid "on this changeset"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:30
+msgid "Delete comment?"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
+msgid "Status change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
+msgid "Comment"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
+msgid "Login now"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:141
+msgid "Hide"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:153
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
+msgid "%d inline"
+msgid_plural "%d inline"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
+msgid "%d general"
+msgid_plural "%d general"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
+msgid "Files affected"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Sin modificar"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Revisión desconocida %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Sin nombre de archivo"
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
+msgid "Show merge diff"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:54
+msgid "is"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
+msgid "%s changesets"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:56
+msgid "behind"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:6
+#: kallithea/templates/compare/compare_diff.html:8
+#, python-format
+msgid "%s Compare"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:13
+#: kallithea/templates/compare/compare_diff.html:41
+msgid "Compare Revisions"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:39
+msgid "Swap"
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:48
+msgid "Compare revisions, branches, bookmarks, or tags."
+msgstr ""
+
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
+#, python-format
+msgid "Showing %s commit"
+msgid_plural "Showing %s commits"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/compare/compare_diff.html:95
+msgid "Show full diff"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:23
+msgid "Public repository"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:29
+msgid "Repository creation in progress..."
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:42
+msgid "No changesets yet"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr ""
+
+#: kallithea/templates/data_table/_dt_elements.html:76
+msgid "Creating"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset"
+msgid "Changeset on"
+msgstr "Cambio"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Rama"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr "La petición pull esta cerrada y no se puede actualizar."
+
+#: kallithea/templates/email_templates/password_reset.html:9
+#, python-format
+msgid "Hello %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:16
+msgid "We have received a request to reset the password for your account."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
+msgid "To set a new password, click the following link"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request updated"
+msgid "Pull request"
+msgstr "Petición pull actualizada"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Error creating pull request: %s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "Error al crear la petición de pull: %s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Error creating pull request: %s"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Error al crear la petición de pull: %s"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:15
+#, python-format
+msgid "%s File side-by-side diff"
+msgstr ""
+
+#: kallithea/templates/files/diff_2way.html:19
+#: kallithea/templates/files/file_diff.html:8
+msgid "File diff"
+msgstr ""
+
+#: kallithea/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File Diff"
+msgstr ""
+
+#: kallithea/templates/files/files.html:4
+#: kallithea/templates/files/files.html:77
+#, python-format
+msgid "%s Files"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:4
+#, python-format
+msgid "%s Files Add"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
+#: kallithea/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:41
+msgid "Enter filename..."
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
+msgid "or"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:43
+msgid "Upload File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:47
+msgid "Create New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:53
+msgid "New file type"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:64
+#: kallithea/templates/files/files_delete.html:34
+#: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
+msgid "Commit Changes"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:37
+msgid "Search File List"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:42
+msgid "Loading file list..."
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
+msgid "Size"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:53
+msgid "Last Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:54
+msgid "Last Modified"
+msgstr ""
+
+#: kallithea/templates/files/files_browser.html:55
+msgid "Last Committer"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:4
+#, python-format
+msgid "%s Files Delete"
+msgstr ""
+
+#: kallithea/templates/files/files_delete.html:12
+#: kallithea/templates/files/files_delete.html:30
+msgid "Delete file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:4
+#, python-format
+msgid "%s File Edit"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:21
+msgid "Edit file"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
+msgid "Show Annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
+msgid "Source"
+msgstr ""
+
+#: kallithea/templates/files/files_history_box.html:2
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: kallithea/templates/files/files_source.html:6
+msgid "Diff to Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:7
+msgid "Show at Revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:10
+msgid "Show Authors"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:26
+msgid "Show Source"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:41
+msgid "Deleting files allowed only when on branch head revision"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:58
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:73
+msgid "Show as raw."
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:23
+msgid "Go Back"
+msgstr ""
+
+#: kallithea/templates/files/files_ypjax.html:24
+msgid "No files at given path"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers.html:9
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
+msgid "Followers"
+msgstr ""
+
+#: kallithea/templates/followers/followers_data.html:9
+msgid "Started following -"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:5
+#, python-format
+msgid "Fork repository %s"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:25
+msgid "Fork name"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:53
+msgid "Default revision for files page, downloads, whoosh, and readme."
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:58
+msgid "Private"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:66
+msgid "Copy permissions"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:69
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:75
+msgid "Update after clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:78
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: kallithea/templates/forks/fork.html:85
+msgid "Fork this Repository"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks.html:9
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
+msgid "Forks"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:14
+msgid "Forked"
+msgstr ""
+
+#: kallithea/templates/forks/forks_data.html:24
+msgid "There are no forks yet"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
+msgid "RSS journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:34
+msgid "My Repositories"
+msgstr ""
+
+#: kallithea/templates/journal/journal_data.html:42
+msgid "No entries yet"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:10
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/public_journal.html:11
+msgid "RSS public journal feed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:4
+#: kallithea/templates/pullrequests/pullrequest.html:8
+msgid "New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:26
+#: kallithea/templates/pullrequests/pullrequest_data.html:15
+#: kallithea/templates/pullrequests/pullrequest_show.html:29
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
+msgid "Title"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:28
+msgid "Summarize the changes - or leave empty"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
+msgid "Write a short description on this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:40
+msgid "Changeset flow"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:46
+msgid "Origin repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
+msgid "Destination repository"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:6
+msgid "No entries"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:14
+msgid "Vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:18
+msgid "From"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:19
+msgid "To"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:28
+#, python-format
+msgid "You voted: %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:30
+msgid "You didn't vote"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:35
+msgid "(no title)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:37
+#: kallithea/templates/pullrequests/pullrequest_show.html:31
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
+msgid "Closed"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:67
+msgid "Delete Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:68
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_data.html:70
+#, python-format
+msgid "Confirm again to delete this pull request with %s comments"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:6
+#, python-format
+msgid "%s Pull Request %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:10
+#, python-format
+msgid "Pull request %s from %s#%s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
+msgid "Summarize the changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
+msgid "Pull request status calculated from votes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
+msgid "Origin"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
+msgid "on"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
+msgid "Target"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
+msgid "Pull changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
+msgid "Current revision - no change"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
+msgid "Pull Request Content"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:6
+#, python-format
+msgid "%s Pull Requests"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:11
+#, python-format
+msgid "Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:13
+#, python-format
+msgid "Pull Requests to '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
+msgid "Open New Pull Request"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
+#, python-format
+msgid "Show Pull Requests to %s"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
+#, python-format
+msgid "Show Pull Requests from '%s'"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:28
+msgid "Hide closed pull requests (only show open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:30
+msgid "Show closed pull requests (in addition to open pull requests)"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
+msgid "Pull Requests Created by Me"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
+msgid "Pull Requests I Participate In"
+msgstr ""
+
+#: kallithea/templates/search/search.html:6
+#, python-format
+msgid "%s Search"
+msgstr ""
+
+#: kallithea/templates/search/search.html:8
+#: kallithea/templates/search/search.html:16
+msgid "Search in All Repositories"
+msgstr ""
+
+#: kallithea/templates/search/search.html:47
+msgid "Search term"
+msgstr ""
+
+#: kallithea/templates/search/search.html:54
+msgid "Search in"
+msgstr ""
+
+#: kallithea/templates/search/search.html:56
+msgid "File contents"
+msgstr ""
+
+#: kallithea/templates/search/search.html:57
+msgid "Commit messages"
+msgstr ""
+
+#: kallithea/templates/search/search.html:58
+msgid "File names"
+msgstr ""
+
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
+msgid "Permission denied"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:4
+#, python-format
+msgid "%s Statistics"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:16
+#: kallithea/templates/summary/summary.html:36
+#, python-format
+msgid "%s ATOM feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:17
+#: kallithea/templates/summary/summary.html:37
+#, python-format
+msgid "%s RSS feed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
+msgid "Enable"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:38
+msgid "Stats gathered: "
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
+msgid "files"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
+msgid "Show more"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:405
+msgid "commits"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:406
+msgid "files added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:407
+msgid "files changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:408
+msgid "files removed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:410
+msgid "commit"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:411
+msgid "file added"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:412
+msgid "file changed"
+msgstr ""
+
+#: kallithea/templates/summary/statistics.html:413
+msgid "file removed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:5
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:14
+#, python-format
+msgid "Repository locked by %s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:16
+msgid "Repository unlocked"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:22
+msgid "Fork of"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:27
+msgid "Clone from"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:84
+msgid "Trending files"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:98
+msgid "Download"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:101
+msgid "There are no downloads yet"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:103
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:109
+msgid "Download as zip"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:113
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:115
+msgid "With subrepos"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
+msgid "Feed"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:175
+msgid "Latest Changes"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
+#, python-format
+msgid "Readme file from revision %s:%s"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:298
+#, python-format
+msgid "Download %s as %s"
+msgstr ""
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr ""
+#~ "No hay ningún indice para buscar. Por favor, ejecute el indexador "
+#~ "whoosh"
+
+#, fuzzy
+#~| msgid "Empty repository"
+#~ msgid "Repository Group"
+#~ msgstr "Repositorio vacío"
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Cerrado, reemplazado por %s."
+
+#~ msgid "Closing."
+#~ msgstr "Cerrado."
--- a/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/fr/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,101 +7,121 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2017-07-26 06:43+0000\n"
-"Last-Translator: Étienne Gilli <etienne.gilli@gmail.com>\n"
-"Language-Team: French "
-"<https://hosted.weblate.org/projects/kallithea/stable/fr/>\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2019-03-26 21:15+0000\n"
+"Last-Translator: Thomas De Schampheleire <patrickdepinguin@gmail.com>\n"
+"Language-Team: French <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/fr/>\n"
 "Language: fr\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n > 1;\n"
-"X-Generator: Weblate 2.16-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"X-Generator: Weblate 3.6-dev\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Il n’y a aucun changement pour le moment"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Aucun"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(fermé)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Afficher les espaces et tabulations"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorer les espaces et tabulations"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Augmenter le contexte du diff à %(num)s lignes"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+msgid "No permission to change status"
+msgstr "Permission manquante pour changer le statut"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "La requête de pull %s a été supprimée avec succès"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Une telle révision n'existe pas pour ce dépôt"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-"La modification de l'état sur un ensemble de modifications associé à une "
-"demande de tirage fermé n'est pas autorisé"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Sélectionner le changeset"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "Impossible de trouver l'autre dépôt %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr "Impossible de comparer des dépôts de types différents"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr "Impossible d'afficher un diff vide"
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr "Aucun ancêtre trouvé pour le diff de fusion"
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr "Plusieurs ancêtres de fusion trouvés pour la comparaison de fusion"
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Impossible de comparer des dépôts sans utiliser un ancêtre commun"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Pas de réponse"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Erreur inconnue"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Le serveur n’a pas pu interpréter la requête à cause d’une erreur de "
 "syntaxe."
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Accès interdit à cette ressource"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Vous n’avez pas la permission de voir cette page"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ressource introuvable"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -109,370 +129,363 @@
 "La requête n’a pu être traitée en raison d’une erreur survenue sur le "
 "serveur."
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "Changements sur le dépôt %s"
-
-#: kallithea/controllers/feed.py:56
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s a commité, le %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Cet ensemble de changements était trop important et a été découpé…"
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "Flux %s de %s"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Cet ensemble de changements était trop important et a été découpé…"
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s a commité, le %s"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Changements sur le dépôt %s"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Ajouter un nouveau fichier"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Il n'y a actuellement pas de fichiers. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr "%s à %s"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Ce dépôt a été verrouillé par %s sur %s"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 "Vous ne pouvez supprimer les fichiers que si la révision est une branche "
 "valide"
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Le fichier %s a été supprimé via Kallithea"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Suppression du fichier %s effectuée avec succès"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Une erreur est survenue durant le commit"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Vous ne pouvez modifier les fichiers que si la révision est une branche "
 "valide"
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "%s édité via Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Aucun changement"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Commit réalisé avec succès sur %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "%s ajouté par Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Aucun contenu"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Aucun nom de fichier"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 "Le chemin doit être un chemin relatif et ne doit pas contenir .. dans le "
 "chemin"
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Les téléchargements sont désactivés"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Révision %s inconnue"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Dépôt vide"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Type d’archive inconnu"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Changesets"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Branches"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Tags"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "Une erreur est survenue durant le fork du dépôt %s"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Groupes"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Dépôts"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Branche"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Branches fermées"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Étiquette"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Signet"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Journal public"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Historique"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Mauvais captcha"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Vous vous êtes inscrits avec succès à Kallithea"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Vous vous êtes inscrit avec succès avec %s"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr "Un lien de confirmation de réinitialisation de mot de passe a été envoyé"
-
-#: kallithea/controllers/login.py:246
+msgstr ""
+"Un lien de confirmation de réinitialisation de mot de passe a été envoyé"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr "Clé de réinitialisation de mot de passe invalide"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Mot de passe mis à jour avec succès"
 
-#: kallithea/controllers/pullrequests.py:124
-#, python-format
-msgid "%s (closed)"
-msgstr "%s (fermé)"
-
-#: kallithea/controllers/pullrequests.py:152
-#: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
-msgid "Changeset"
-msgstr "Changements"
-
-#: kallithea/controllers/pullrequests.py:173
-msgid "Special"
-msgstr "Spécial"
-
-#: kallithea/controllers/pullrequests.py:174
-msgid "Peer branches"
-msgstr "Branches appairées"
-
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
-msgid "Bookmarks"
-msgstr "Signets"
-
-#: kallithea/controllers/pullrequests.py:310
-#, python-format
-msgid "Error creating pull request: %s"
-msgstr "Erreur de création de la demande de pull : %s"
-
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Aucune description"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "La requête de pull a été ouverte avec succès"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
+#: kallithea/controllers/pullrequests.py:71
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr "Reviewer spécifié \"%s\" non valide"
 
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr "%s (fermé)"
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr "Changements"
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr "Spécial"
+
+#: kallithea/controllers/pullrequests.py:182
+msgid "Peer branches"
+msgstr "Branches appairées"
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr "Signets"
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr "Erreur de création de la demande de pull : %s"
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "Une erreur est survenue durant la création de la pull request"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr "Changeset manquant depuis la précédente pull request :"
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr "Nouveau changeset sur %s %s depuis la précédente pull request :"
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-"L'ancêtre n'a pas changé - montrer les différences avec la version "
-"précédente :"
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-"Cette demande de pull est basée sur une autre révision %s et il n'y a pas"
-" de diff simple."
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr "Aucun changement constaté sur %s %s depuis la version précédente."
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "Fermé, remplacé par %s."
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr "Mise à jour de la pull request créée"
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "La requête de pull a été ouverte avec succès"
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr "Nouvelle itération de requête de pull créée"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr "Entretemps, les relecteurs suivants on été ajoutés : %s"
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr "Entretemps, les relecteurs suivants ont été supprimés : %s"
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Aucune description"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "Pull request mise à jour"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "La requête de pull a été supprimée avec succès"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr "Révision %s non trouvée dans %s"
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+"Erreur : Pas de changeset trouvé lors de l'affichage la requête de pull "
+"de %s."
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Cette pull request a déjà été fusionnée à %s."
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr "Cette pull request a été fermée et ne peut pas être mise à jour."
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr "Cette demande de pull peut être mise à jour avec les modifications de %s :"
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr "Pas de changeset trouvé pour ce pull request."
-
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+"Les modifications additionnelles suivantes sont disponibles sur %s :"
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr "Pas de changeset additionnel trouvé pour cette requête de pull."
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
-msgstr "Note: La branche %s a une autre tête: %s."
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr "Le smises à jour des Git pull requests ne sont pas encore supportées."
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr "Permission manquante pour changer le statut du pull request"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Fermeture."
-
-#: kallithea/controllers/search.py:135
+msgstr "Note: La branche %s a une autre tête : %s."
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+"Les itérations des requêtes de pull Git ne sont pas encore supportées."
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+"Erreur : certains changesets n'ont pas été trouvés lors de l'affichage la "
+"requête de pull depuis %s."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+"Le diff ne peut pas être affiché : révisions des requêtes de pull "
+"introuvables."
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
-msgstr "Requête invalide. Essayer de la mettre entre guillemets."
+msgstr "Requête invalide. Essayez de la mettre entre guillemets."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-"L’index de recherche n’est pas présent. Veuillez exécuter l’indexeur de "
-"code Whoosh"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr "Le serveur n'a pas d'index de recherche."
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Une erreur est survenue pendant la recherche."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "Aucune donnée actuellement disponible"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "La mise à jour des statistiques est désactivée pour ce dépôt"
 
@@ -486,153 +499,156 @@
 "une erreur est survenue pendant la mise à jour des réglages "
 "d'authentification"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Mise à jour des réglages par défaut effectuée avec succès"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
-msgstr "Une erreur est survenue durant la mise à jour des réglages par défaut"
+msgstr ""
+"Une erreur est survenue durant la mise à jour des réglages par défaut"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "Pour toujours"
 
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr "Pour toujours"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 minute"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 minute"
-
-#: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
 msgid "1 hour"
 msgstr "1 heure"
 
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 jour"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 mois"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Toujours"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Une erreur est survenue lors de la création du gist"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist %s supprimé"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
 msgstr "Non modifié"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr "Le contenu du gist a été mis à jour avec succès"
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr "Les données du gist on été mises à jour avec succès"
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Une erreur est survenue durant la mise à jour du gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
-"Vous ne pouvez pas éditer cet utilisateur ; il est nécessaire pour le bon"
-" fonctionnement de l’application"
-
-#: kallithea/controllers/admin/my_account.py:129
+"Vous ne pouvez pas éditer cet utilisateur ; il est nécessaire pour le bon "
+"fonctionnement de l’application"
+
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Votre compte a été mis à jour avec succès"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Une erreur est survenue durant la mise à jour de l'utilisateur %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 "Une erreur est survenue durant la mise à jour du mot de passe de "
 "l'utilisateur"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "L’e-mail « %s » a été ajouté à l’utilisateur"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Une erreur est survenue durant l’enregistrement de l’e-mail"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "L’e-mail a été enlevé de l’utilisateur"
 
-#: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
 msgid "API key successfully created"
 msgstr "Clé d'API créée avec succès"
 
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
 msgid "API key successfully reset"
 msgstr "Clé d'API remise à zéro avec succès"
 
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "Clé d'API supprimée avec succès"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Lire"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Écrire"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -640,279 +656,273 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Administration"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Interdite"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Autorisé avec activation de compte manuelle"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Autorisé avec activation de compte automatique"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Activation manuelle du compte externe"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Activation automatique du compte externe"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Interdite"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Autorisé avec activation de compte manuelle"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Autorisé avec activation de compte automatique"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Activation manuelle du compte externe"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Activation automatique du compte externe"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Autorisée"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr "Permissions globales mises à jour avec succès"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Une erreur est survenue durant la mise à jour des permissions"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Une erreur est survenue durant la création du groupe de dépôts %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Groupe de dépôts %s créé"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Groupe de dépôts %s mis à jour"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
-msgstr "Une erreur est survenue durant la mise à jour du groupe de dépôts %s"
-
-#: kallithea/controllers/admin/repo_groups.py:284
+msgstr ""
+"Une erreur est survenue durant la mise à jour du groupe de dépôts %s"
+
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Ce groupe contient %s dépôts et ne peut être supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Ce groupe contient %s sous-groupes et ne peut pas être supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Groupe de dépôts %s supprimé"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
-msgstr "Une erreur est survenue durant la suppression du groupe de dépôts %s"
-
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+msgstr ""
+"Une erreur est survenue durant la suppression du groupe de dépôts %s"
+
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Impossible de révoquer votre permission d'administrateur"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Permissions du groupe de dépôts mises à jour"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "Une erreur est survenue durant la révocation de la permission"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Erreur de création du dépôt %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Dépôt %s créé depuis %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "dépôt %s forké en tant que %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Dépôt %s créé"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Dépôt %s mis à jour avec succès"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Une erreur est survenue durant la mise à jour du dépôt %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "%s forks détachés"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "%s forks supprimés"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Dépôt %s supprimé"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "Impossible de supprimer le dépôt %s : des forks y sont attachés"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Erreur pendant la suppression de %s"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Permissions du dépôt mises à jour"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr "Une erreur est survenue durant la création du champ"
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr "Erreur de validation du champ : %s"
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr "Une erreur est survenue durant la création du champ : %r"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Une erreur est survenue durant la suppression du champ"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Pas un fork --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "La visibilité du dépôt dans le journal public a été mise à jour"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
-"Une erreur est survenue durant la configuration du journal public pour ce"
-" dépôt"
-
-#: kallithea/controllers/admin/repos.py:512
+"Une erreur est survenue durant la configuration du journal public pour ce "
+"dépôt"
+
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "[Aucun dépôt]"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Le dépôt %s a été marké comme fork de %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "Une erreur est survenue durant cette opération"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "Ce dépôt a été verrouillé"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "Ce dépôt a été déverrouillé"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "Une erreur est survenue durant le déverrouillage"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "Invalidation du cache réalisée avec succès"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "Une erreur est survenue durant l’invalidation du cache"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "Les changements distants ont été récupérés"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "Une erreur est survenue durant le pull depuis la source distante"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
-msgstr "Une erreur est survenue durant la suppression des statistiques du dépôt"
-
-#: kallithea/controllers/admin/settings.py:170
+msgstr ""
+"Une erreur est survenue durant la suppression des statistiques du dépôt"
+
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "Réglages des gestionnaires de versions mis à jour"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -920,351 +930,363 @@
 "Impossible d'activer la prise en charge de hgsubversion. La bibliothèque "
 "« hgsubversion » est manquante"
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr ""
 "Une erreur est survenue durant la mise à jour des réglages de "
 "l'application"
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr "Dépôts ré-analysés avec succès. Ajouté : %s. Supprimé : %s."
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr "%s dépôts invalidés"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "Réglages mis à jour"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Réglages d’affichage mis à jour"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 "Une erreur est survenue durant la mise à jour des réglages de "
 "visualisation"
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr "Veuillez entrer votre adresse e-mail"
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr "Tâche d'envoi d'e-mail créée"
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr "Le hook existe déjà"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+"Les hooks intégrés sont en lecture seule. Merci de choisir un autre nom "
+"pour le hook."
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Le nouveau hook a été ajouté"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Hooks mis à jour"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "Une erreur est survenue durant la création du hook"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "La tâche de réindexation Whoosh a été planifiée"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "Groupe d'utilisateurs %s créé"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
-msgstr "Une erreur est survenue durant la création du groupe d'utilisateurs %s"
-
-#: kallithea/controllers/admin/user_groups.py:201
+msgstr ""
+"Une erreur est survenue durant la création du groupe d'utilisateurs %s"
+
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "Groupe d'utilisateurs %s mis à jour"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
-msgstr "Une erreur est survenue durant la mise à jour du groupe d'utilisateurs %s"
-
-#: kallithea/controllers/admin/user_groups.py:242
+msgstr ""
+"Une erreur est survenue durant la mise à jour du groupe d'utilisateurs %s"
+
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "Groupe d'utilisateurs supprimé avec succès"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
-msgstr "Une erreur est survenue durant la suppression du groupe d'utilisateurs"
-
-#: kallithea/controllers/admin/user_groups.py:314
+msgstr ""
+"Une erreur est survenue durant la suppression du groupe d'utilisateurs"
+
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "Le groupe cible ne peut pas être le même"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "Permissions du groupe d'utilisateurs mises à jour"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "Permissions mises à jour"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "Une erreur est survenue durant l’enregistrement des permissions"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "Utilisateur %s créé"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Une erreur est survenue durant la création de l'utilisateur %s"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "L’utilisateur a été mis à jour avec succès"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "Utilisateur supprimé avec succès"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "Une erreur est survenue durant la suppression de l’utilisateur"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr "L'utilisateur par défaut ne peut pas être modifié"
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "L'adresse IP %s a été ajoutée à la liste blanche"
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Une erreur est survenue durant la sauvegarde d'IP"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr "L'adresse IP a été supprimée de la liste blanche"
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IP %s non autorisée"
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+"Vous devez être un utilisateur enregistré pour effectuer cette action"
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr "Vous devez être connecté pour visualiser cette page"
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr "Clé d'API invalide"
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
 msgstr ""
-"Une fuite de jeton CSRF a été détectée - tous les jetons de formulaire sont "
-"considérés comme expirés"
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr "Vous devez être un utilisateur enregistré pour effectuer cette action"
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr "Vous devez être connecté pour visualiser cette page"
-
-#: kallithea/lib/base.py:490
+"Une fuite de jeton CSRF a été détectée - tous les jetons de formulaire "
+"sont considérés comme expirés"
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr "Dépôt non trouvé sur le système de fichiers"
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr "Ensemble de changements pour %s %s non trouvé dans %s"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Fichier binaire"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Cet ensemble de changements était trop gros pour être affiché et a été "
 "découpé, utilisez le menu « diff » pour afficher les différences"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Aucun changement détecté"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Branche supprimée : %s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Étiquette créée : %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr "Ensemble de changements %s non trouvé"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Afficher les changements combinés %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr "Vue de comparaison"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "et"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s de plus"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "révisions"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr "Nom du fork %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr "Requête de pull %s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[a supprimé] le dépôt"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[a créé] le dépôt"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[a créé] le dépôt en tant que fork"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[a forké] le dépôt"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[a mis à jour] le dépôt"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "[téléchargée] archive depuis le dépôt"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[a supprimé] le dépôt"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "[a créé] l’utilisateur"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "[a mis à jour] l’utilisateur"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "[créé] groupe d'utilisateurs"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "[mis à jour] groupe d'utilisateurs"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[a commenté] une révision du dépôt"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "[a commenté] la requête de pull pour"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "[a fermé] la requête de pull de"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[a pushé] dans"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[a commité via Kallithea] dans le dépôt"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[a pullé depuis un site distant] dans le dépôt"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[a pullé] depuis"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[suit maintenant] le dépôt"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[ne suit plus] le dépôt"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " et %s de plus"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Aucun fichier"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "nouveau fichier"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "suppr."
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "renommer"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1275,760 +1297,363 @@
 "probablement été créé ou renommé manuellement. Veuillez relancer "
 "l’application pour rescanner les dépôts"
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d an"
 msgstr[1] "%d ans"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d mois"
 msgstr[1] "%d mois"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d jour"
 msgstr[1] "%d jours"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d heure"
 msgstr[1] "%d heures"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d minute"
 msgstr[1] "%d minutes"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d seconde"
 msgstr[1] "%d secondes"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "dans %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "Il y a %s"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "dans %s et %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "Il y a %s et %s"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "à l’instant"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Aucun accès au dépôt"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Accès en lecture au dépôt"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Accès en écriture au dépôt"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Accès administrateur au dépôt"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Aucun accès au groupe de dépôts"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Accès en lecture au groupe de dépôts"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Accès en écriture au groupe de dépôts"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Accès administrateur au groupe de dépôts"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Administrateur Kallithea"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Création de dépôt désactivée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Création de dépôt activée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Fork de dépôt désactivé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Fork de dépôt activé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Enregistrement désactivé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Enregistrer un nouvel utilisateur Kallithea manuellement activé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Enregistrer un nouvel utilisateur Kallithea auto-activé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Pas encore relue"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Approuvée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Rejetée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "En cours de relecture"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "niveau supérieur"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Groupe de dépôts, pas d'accès"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Groupe de dépôts, accès en lecture"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Groupe de dépôts, accès en écriture"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Groupe de dépôts, accès d'administration"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Groupe d'utilisateurs, pas d'accès"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Groupe d'utilisateurs, accès en lecture"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Groupe d'utilisateurs, accès en écriture"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Groupe d'utilisateurs, accès d'administration"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Création de groupes de dépôts désactivée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Création de groupes de dépôts activée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Création de groupes d'utilisateurs désactivée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Création de groupes d'utilisateurs activée"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Enregistrement désactivé"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Enregistrement des utilisateurs avec activation de compte manuelle"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Enregistrement des utilisateurs avec activation de compte automatique"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-"Création de dépôts activée avec l'accès en écriture vers un groupe de "
-"dépôts"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-"Création de dépôts désactivée avec l'accès en écriture vers un groupe de "
-"dépôts"
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "à la ligne %s"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Mention]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "niveau supérieur"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Administrateur Kallithea"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux dépôts"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr "L'utilisateur par défaut a un accès en lecture aux nouveaux dépôts"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr "L'utilisateur par défaut a un accès en écriture aux nouveaux dépôts"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
-msgstr "L'utilisateur par défaut a un accès administrateur aux nouveaux dépôts"
-
-#: kallithea/model/db.py:1673
+msgstr ""
+"L'utilisateur par défaut a un accès administrateur aux nouveaux dépôts"
+
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
-msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux groupes de dépôts"
-
-#: kallithea/model/db.py:1674
+msgstr ""
+"L'utilisateur par défaut n'a pas accès aux nouveaux groupes de dépôts"
+
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
-"L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes de"
-" dépôts"
-
-#: kallithea/model/db.py:1675
+"L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes de "
+"dépôts"
+
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut a accès en écriture aux nouveaux groupes de "
 "dépôts"
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 "L'utilisateur par défaut a accès administrateur aux nouveaux groupes de "
 "dépôts"
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
-msgstr "L'utilisateur par défaut n'a pas accès aux nouveaux groupes d'utilisateurs"
-
-#: kallithea/model/db.py:1679
+msgstr ""
+"L'utilisateur par défaut n'a pas accès aux nouveaux groupes d'utilisateurs"
+
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a accès en lecture seule aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a accès en écriture aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 "L'utilisateur par défaut a un accès administrateur aux nouveaux groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr "Seul un administrateur peut créer un groupe de dépôts"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
-msgstr "Les utilisateurs non-administrateurs peuvent créer des groupes de dépôts"
-
-#: kallithea/model/db.py:1686
+msgstr ""
+"Les utilisateurs non-administrateurs peuvent créer des groupes de dépôts"
+
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr "Seul un administrateur peut créer des groupes d'utilisateurs"
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 "Les utilisateurs non-administrateurs peuvent créer des groupes "
 "d'utilisateurs"
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr "Seul un administrateur peut créer des dépôts de niveau supérieur"
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 "Les utilisateurs non-administrateurs peuvent créer des dépôts de niveau "
 "supérieur"
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+"Création de dépôts activée avec l'accès en écriture vers un groupe de "
+"dépôts"
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+"Création de dépôts désactivée avec l'accès en écriture vers un groupe de "
+"dépôts"
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr "Seul un administrateur peut faire un fork de dépôt"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr "Les utilisateurs non-administrateurs peuvent faire un fork de dépôt"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Enregistrement désactivé"
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr "Enregistrement des utilisateurs avec activation de compte manuelle"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
-msgstr "Enregistrement des utilisateurs avec activation de compte automatique"
-
-#: kallithea/model/db.py:2238
+msgstr ""
+"Enregistrement des utilisateurs avec activation de compte automatique"
+
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr "Pas encore relue"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr "En cours de relecture"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr "Non approuvée"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Approuvée"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Veuillez entrer un identifiant"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Entrez une valeur d’au moins %(min)i caractères de long"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Veuillez entrer un mot de passe"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Entrez au moins %(min)i caractères"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr "Le nom ne doit pas contenir seulement des chiffres"
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s a commenté sur le changeset %(age)s"
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s a envoyé un message %(age)s"
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s vous a mentionné %(age)s"
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s s'est enregistré sur Kallithea %(age)s"
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s a ouvert une nouvelle demande de pull %(age)s"
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s a commenté la demande de pull %(age)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s a commenté sur le changeset à %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s a envoyé un message à %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s vous a mentionné à %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s s'est enregistré sur Kallithea à %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s a ouvert une nouvelle demande de pull à %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s a commenté la demande de pull à %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr "[Commentaire] Changeset %(short_id)s de %(repo_name)s dans %(branch)s"
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+"[Commentaire] Changeset %(short_id)s « %(message_short)s » de "
+"%(repo_name)s dans %(branch)s"
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Nouvel utilisateur %(new_username)s enregistré"
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
-"[Ajouté] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour "
-"%(repo_name)s"
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+"[Revue] %(repo_name)s PR %(pr_nice_id)s « %(pr_title_short)s » depuis "
+"%(pr_source_branch)s par %(pr_owner_username)s"
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
 msgstr ""
-"[Commentaire] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour "
-"%(repo_name)s"
-
-#: kallithea/model/notification.py:322
+"[Commentaire] %(repo_name)s PR %(pr_nice_id)s « %(pr_title_short)s » "
+"depuis %(pr_source_branch)s par %(pr_owner_username)s"
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "Fermeture"
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 "%(user)s veut que vous regardiez la demande de pull %(pr_nice_id)s : "
 "%(pr_title)s"
 
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr "Impossible de créer une requête de pull vide"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+"Impossible de créer la requête de pull : fusion croisée détectée, merci "
+"de fusionner une révision plus vieille de %s vers %s"
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr "Vous n'êtes pas autorisé à créer cette requête de pull"
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr "Changeset manquant depuis la précédente itération :"
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Nouveau changeset sur %s %s depuis la précédente itération :"
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr "L'ancêtre n'a pas changé - diff depuis l'itération précédente :"
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"Cette itération est basée sur une autre révision %s et il n'y a pas de "
+"diff simple."
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr "Aucun changement constaté sur %s %s depuis l'itération précédente."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr "Fermé, itération suivante : %s."
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "Dernier sommet"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "Nouveau enregistrement d'utilisateur"
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Vous ne pouvez pas supprimer cet utilisateur ; il est nécessaire pour le "
 "bon fonctionnement de l’application"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -2037,34 +1662,34 @@
 "L’utilisateur \"%s\" possède %s dépôts et ne peut être supprimé. Changez "
 "les propriétaires ou supprimez ces dépôts : %s"
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
 "L’utilisateur \"%s\" possède %s groupes de dépôt et ne peut être "
 "supprimé. Changez les propriétaires ou supprimez ces dépôts : %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 "L’utilisateur « %s » possède %s groupes d'utilisateurs et ne peut pas "
-"être supprimé. Changez les propriétaires de ces groupes d'utilisateurs ou"
-" supprimez-les : %s"
-
-#: kallithea/model/user.py:360
+"être supprimé. Changez les propriétaires de ces groupes d'utilisateurs ou "
+"supprimez-les : %s"
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "Lien de remise à zéro du mot de passe"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr "Notification de réinitialisation du mot de passe"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
@@ -2073,48 +1698,48 @@
 "Le mot de passe de votre compte %s a été changé via le formulaire de "
 "réinitialisation du mot de passe."
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "Cette valeur ne peut être une liste vide"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "Le nom d’utilisateur « %(username)s » existe déjà"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "Le nom d’utilisateur « %(username)s » n’est pas valide"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
-"Le nom d'utilisateur ne peut contenir que des caractères alphanumériques,"
-" des underscores (_), points, traits d'union et doit commencer avec un "
+"Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, "
+"des underscores (_), points, traits d'union et doit commencer avec un "
 "caractère alphanumérique ou un underscore"
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr "L'entrée n'est pas valide"
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "Le nom d’utilisateur « %(username)s » n’est pas valide"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "Nom de groupe d'utilisateurs invalide"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "Le groupe d'utilisateurs « %(usergroup)s » existe déjà"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
@@ -2123,65 +1748,65 @@
 "alphanumériques, des tirets, des points, des traits d'union et doit "
 "commencer avec un caractère alphanumérique"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Impossible d’assigner ce groupe en tant que parent"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "Le groupe « %(group_name)s » existe déjà"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Un dépôt portant le nom « %(group_name)s » existe déjà"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Caractères incorrects (non-ASCII) dans le mot de passe"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr "Ancien mot de passe invalide"
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Les mots de passe ne correspondent pas"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr "Nom d'utilisateur ou mot de passe invalide"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "Jeton d’authentification incorrect"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "Le nom de dépôt « %(repo)s » n’est pas autorisé"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Un dépôt portant le nom « %(repo)s » existe déjà"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "Le dépôt « %(repo)s » existe déjà dans le groupe « %(group)s »"
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Un groupe de dépôts avec le nom « %(repo)s » existe déjà"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr "URL de dépôt invalide"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
@@ -2189,293 +1814,201 @@
 "URL de dépôt invalide. Ce doit être une URL valide de type http, https, "
 "ssh, svn+http ou svn+https"
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "Le fork doit être du même type que le parent"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "Vous n’avez pas la permission de créer un dépôt dans ce"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "pas de permission de créer un dépôt dans la racine"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
-msgstr "Vous n'avez pas les permissions pour créer un groupe dans cet endroit"
-
-#: kallithea/model/validators.py:597
+msgstr ""
+"Vous n'avez pas les permissions pour créer un groupe dans cet endroit"
+
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
-msgstr "Ce nom d'utilisateur ou nom de groupe d'utilisateurs n'est pas valide"
-
-#: kallithea/model/validators.py:690
+msgstr ""
+"Ce nom d'utilisateur ou nom de groupe d'utilisateurs n'est pas valide"
+
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "Ceci n’est pas un chemin valide"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr "Cette adresse e-mail est déjà enregistrée"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "L’adresse e-mail « %(email)s » n’existe pas"
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
-"L’attribut Login du CN doit être spécifié. Cet attribut correspond au nom"
-" d’utilisateur"
-
-#: kallithea/model/validators.py:774
+"L’attribut Login du CN doit être spécifié. Cet attribut correspond au nom "
+"d’utilisateur"
+
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Veuillez entrer une adresse IPv4 ou IPv6 valide"
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr "La taille du réseau (bits) doit être entre 0 et 32 (et non %(bits)r)"
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+"La taille du réseau (bits) doit être entre 0 et 32 (et non %(bits)r)"
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 "Le nom de la clé ne peut consister que de letters, de traits d'union, de "
 "tirets ou de nombres"
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "Le nom du fichier ne peut être à l'intérieur d'un répertoire"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 "Les greffons %(loaded)s et %(next_to_load)s exportent tous les deux vers "
 "le même nom"
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr "À propos"
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Tableau de bord"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "Filtre rapide…"
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "Dépôts"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "Ajouter un dépôt"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "Ajouter un groupe de dépôts"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
-msgstr "Vous avez les droits d'administration sur ce groupe, et pouvez l'éditer"
-
-#: kallithea/templates/index_base.html:32
+msgstr ""
+"Vous avez les droits d'administration sur ce groupe, et pouvez l'éditer"
+
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "Modifier le groupe de dépôts"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "Nom du groupe"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Dépôt"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Description"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Nom"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Dernière modification"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Sommet"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Propriétaire"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "Tri ascendant"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "Tri descendant"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Pas de dépôts trouvés."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Erreur d’intégrité des données."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Chargement…"
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Connexion"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Se connecter à %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "Nom d’utilisateur"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Mot de passe"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Se souvenir de moi"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr "Rester connecté après un redémarrage du navigateur"
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "Mot de passe oublié ?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Vous n’avez pas de compte ?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Connexion"
 
@@ -2483,47 +2016,47 @@
 msgid "Password Reset"
 msgstr "Remettre le mot de passe à zéro"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr "Réinitialiser votre mot de passe à %s"
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr "Réinitialiser votre mot de passe"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "Adresse e-mail"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr "Captcha"
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 msgid "Send Password Reset Email"
 msgstr "Envoyer l'E-mail de réinitialisation du mot de passe"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
-"Un lien de réinitialisation du mot de passe sera envoyé à l'adresse "
-"e-mail indiquée si elle est enregistrée dans le système."
-
-#: kallithea/templates/password_reset_confirmation.html:19
+"Un lien de réinitialisation du mot de passe sera envoyé à l'adresse e-"
+"mail indiquée si elle est enregistrée dans le système."
+
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 "Vous êtes sur le point de changer le mot de passe pour l'adresse e-mail "
 "%s."
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
@@ -2531,106 +2064,89 @@
 "Vous devez utiliser la même session de navigateur pour cette opération "
 "que celle utilisée pour la demande de réinitialisation de mot de passe."
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr "Le code que vous avez reçu dans l'e-mail"
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr "Nouveau mot de passe"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr "Confirmer le nouveau mot de passe"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr "Confirmation"
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Inscription"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr "S'inscrire sur %s"
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "Confirmation"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Prénom"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Nom"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 "Les comptes enregistrés sont prêts à être utilisés, et ne nécessitent "
 "aucune autre action."
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr "Merci d'attendre qu'un administrateur active votre compte."
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Aucune branche n’a été créée pour le moment"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Branches fermées"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Aucun tag n’a été créé pour le moment"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Aucun signet n’a été créé"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr "Historique d’administration"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "filtre du journal…"
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr "Filtre"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2638,30 +2154,16 @@
 msgstr[1] "%s entrées"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Action"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Dépôt"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Date"
 
@@ -2669,7 +2171,7 @@
 msgid "From IP"
 msgstr "Depuis l’adresse IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "Aucune action n’a été enregistrée pour le moment"
 
@@ -2678,19 +2180,19 @@
 msgstr "Réglages d'authentification"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "Authentification"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr "Greffons d'authentification"
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr "Greffons activés"
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
@@ -2699,57 +2201,57 @@
 "est aussi celui dans lequel Kallithea va essayer d'authentifier un "
 "utilisateur"
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr "Greffons inclus disponibles"
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr "Greffon"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Enregistrer"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr "Réglages par défaut du dépôt"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Type"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Dépôt privé"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
@@ -2757,34 +2259,34 @@
 "Les dépôts privés sont visibles seulement par les utilisateurs ajoutés "
 "comme collaborateurs."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Activer les statistiques"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Afficher les statistiques sur la page du dépôt."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Activer les téléchargements"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Afficher le menu de téléchargements sur la page du dépôt."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Activer le verrouillage"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "Activer le verrouillage lors d’un pull sur le dépôt."
 
@@ -2793,7 +2295,7 @@
 msgid "Edit Gist"
 msgstr "Modifier le gist"
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
@@ -2802,38 +2304,38 @@
 "Le gist a été mis à jour depuis que vous avez commencé à éditer. Copier "
 "vos changements et cliquez %(here)s pour charger la nouvelle version."
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Description du gist…"
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr "Durée de vie du gist"
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Expire le"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr "Jamais"
 
@@ -2842,7 +2344,8 @@
 msgstr "Mettre à jour le gist"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -2865,16 +2368,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr "Créer un nouveau gist"
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "Créé"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Il n'y a actuellement pas de gists"
 
@@ -2883,45 +2386,45 @@
 msgid "New Gist"
 msgstr "Nouveau gist"
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr "nommez ce fichier…"
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr "Nommer ce gist…"
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr "Créer un gist privé"
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr "Créer un gist public"
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "Réinitialiser"
 
@@ -2931,182 +2434,207 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
-#: kallithea/templates/admin/gists/show.html:37
+#: kallithea/templates/admin/gists/show.html:35
 msgid "Public Gist"
 msgstr "Gist public"
 
-#: kallithea/templates/admin/gists/show.html:39
+#: kallithea/templates/admin/gists/show.html:37
 msgid "Private Gist"
 msgstr "Gist privé"
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Supprimer"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr "Confirmer la supprisson de ce gist"
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Modifier"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr "Montrer en brut"
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "créé"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "Montrer en brut"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Mon compte"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "Profil"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr "Adresses e-mail"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr "Clés de l'API"
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr "Dépôts possédés"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr "Dépôts surveillés"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr "Afficher les permissions"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr "Inclus"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "Confirmer la remise à zéro de cette clé d'API : %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr "a expiré"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "Confirmer la suppression de cette clé d'API : %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr "Supprimer"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr "Pas de clés d'API supplémentaires spécifiées"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr "Nouvelle clé d'API"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Ajouter"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+"\n"
+"Les clés API sont utilisées pour permettre à des scripts et des services "
+"d'accéder à %s en utilisant votre compte, comme si vous aviez fourni "
+"votre mot de passe à ces scripts ou services.\n"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+"\n"
+"Comme les mots de passe, les clés API ne devraient donc jamais être "
+"diffusées à des tiers, ni passés à des scripts ou services auxquels vous "
+"ne faites pas confiance. Si cette diffusion a tout de même lieu, vous "
+"pouvez réinitialiser la clé API sur cette page pour qu'elle ne puisse "
+"plus être utilisée.\n"
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "Primaire"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Veuillez confirmer la suppression de l’e-mail : %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr "Pas d'adresse email supplémentaires spécifiées."
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "Nouvelle adrese"
 
@@ -3114,105 +2642,69 @@
 msgid "Change Your Account Password"
 msgstr "Changer le mot de passe de votre compte"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr "Mot de passe actuel"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Nouveau mot de passe"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr "Confirmer le nouveau mot de passe"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr "Ce compte est géré avec %s et le mot de passe ne peut pas être changé ici"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
-msgstr "Vous pouvez changer votre avatar sur"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+"Ce compte est géré avec %s et le mot de passe ne peut pas être changé ici"
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr "Adresse IP actuelle"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr "Gravatar"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr "Changer l'avatar de %s sur"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "en utilisant l’adresse"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr "Les avatars sont désactivés"
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr "Adresse courriel manquante, veuillez mettre à jour votre adresse courriel."
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr "Adresse IP actuelle"
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "Dépôts dont vous êtes le propriétaire"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Aucun élément n’a été trouvé."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Nom"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr "Dépôts que vous surveillez"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Mes notifications"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Tous"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Commentaires"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Demandes de pull"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr "Tout marquer comme lu"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Aucune notification pour le moment"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr "Montrer Notification"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Notifications"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr "Permissions par défaut"
 
@@ -3226,11 +2718,15 @@
 msgid "IP Whitelist"
 msgstr "Liste blanche d'adresses IP"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Accès anonyme"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr "Permettre l'accès anonyme"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
@@ -3239,58 +2735,58 @@
 "Autoriser l'accès à Kallithea sans le besoin de se connecter. Les "
 "utilisateurs anonymes ont les permissions de l'utilisateur %s."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 "Toutes les permissions par défaut de chaque dépôt vont être "
-"réinitialisées aux valeurs choisies. Notez que toutes les permissions par"
-" défaut personnalisées sur les dépôts seront perdues"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+"réinitialisées aux valeurs choisies. Notez que toutes les permissions par "
+"défaut personnalisées sur les dépôts seront perdues"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr "Appliquer à tous les dépôts existants"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr "Permissions pour l'utilisateur par défaut sur les nouveaux dépôts."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
 msgid "Repository group"
 msgstr "Groupe de dépôt"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
+#: kallithea/templates/admin/permissions/permissions_globals.html:32
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 "Toutes les permissions par défaut de chaque groupe de dépôts vont être "
-"réinitialisées aux valeurs choisies. Notez que toutes les permissions par"
-" défaut personnalisées sur les groupes de dépôts seront perdues"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+"réinitialisées aux valeurs choisies. Notez que toutes les permissions par "
+"défaut personnalisées sur les groupes de dépôts seront perdues"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr "Appliquer à tous les groupes de dépôts existants"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 "Permissions pour l'utilisateur par défaut sur les nouveaux groupes de "
 "dépôts."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "Groupe d'utilisateurs"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
@@ -3301,27 +2797,28 @@
 "permissions par défaut personnalisées sur les groupes d'utilisateurs "
 "seront perdues"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr "Appliquer à tous les groupes d'utilisateurs existants"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 "Permissions pour l'utilisateur par défaut sur les nouveaux groupes "
 "d'utilisateurs."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr "Création de dépôt de niveau supérieur"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
 msgstr ""
 "Activer pour autoriser les non-administrateurs à créer des dépôts au "
 "niveau supérieur."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
@@ -3330,11 +2827,11 @@
 "pour créer des dépôts partout. Ce comportement peut changer dans des "
 "versions futures."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr "Création de dépôts avec l'accès en écriture du groupe"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
@@ -3344,75 +2841,77 @@
 "créer des dépôts dans ce groupe. Sans ceci, le droit d'écriture pour les "
 "groupes n'a pas d'impact."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "Création de groupes d'utilisateurs"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 "Activer pour autoriser les non-administrateurs à créer des groupes "
 "d'utilisateurs."
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Fork de dépôt"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
-msgstr "Activer pour autoriser les non-administrateurs à faire des fork de dépôt."
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+msgstr ""
+"Activer pour autoriser les non-administrateurs à faire des fork de dépôt."
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Enregistrement"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "Activation de l'authentification externe"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Confirmer la suppression de cette adresse IP : %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr "Toutes les adresses IP sont autorisées."
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Nouvelle adresse IP"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "Groupes de dépôts"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Nom de groupe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Parent du groupe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr "Copier les permissions du groupe parent"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr "Copier les permissions à partir du groupe de dépôts parent."
 
@@ -3421,30 +2920,29 @@
 msgid "%s Repository Group Settings"
 msgstr "Options du groupe de dépôts %s"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr "Ajouter un groupe enfant"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Options"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Avancé"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Permissions"
@@ -3469,12 +2967,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Créé le"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3485,16 +2983,32 @@
 msgid "Delete this repository group"
 msgstr "Supprimer ce groupe de dépôts"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr "Non visible"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr "Visible"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr "Ajouter un dépôt"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr "Ajouter/Modifier les groupes d'utilisateurs"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr "Utilisateur/groupe d'utilisateurs"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3502,28 +3016,28 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr "Révoquer"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "Ajouter un nouveau"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr "Appliquer aux enfants"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr "Les deux"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
@@ -3531,48 +3045,40 @@
 "Ajouter ou révoquer la permission pour tous les enfants de ce groupe, y "
 "compris les dépôts non-privés et les autres groupes si sélectionné."
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
 msgstr ""
 "Activer le verrou lors d’un pull sur le groupe. Cette option sera "
 "appliquée à tous les sous-groupes et dépôts de ce groupe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr "Supprimer ce groupe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr "Confirmer la suppression de ce groupe"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr "Tableau de bord du groupe de dépôts %s"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "Accueil"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "comprenant"
+msgid "Repository group %s"
+msgstr "Groupe de dépôts %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr "Administration des groupes de dépôts"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr "Nombre de dépôts de niveau supérieur"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr "Cloner le dépôt distant"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
@@ -3580,31 +3086,32 @@
 "Optionnel : URL d'un dépôt distant. Si renseigné, le dépôt sera créé "
 "comme un clone à partir de cette URL."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 "Gardez cette description précise et concise. Utilisez un fichier README "
 "pour des descriptions plus détaillées."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "Sélectionnez un groupe (optionel) dans lequel sera placé le dépôt."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Type de dépôt à créer."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Révision d’arrivée"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3644,22 +3151,22 @@
 msgid "%s Repository Settings"
 msgstr "Réglages du dépôt %s"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr "Champs supplémentaires"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr "Caches"
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Dépôt distant"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Statistiques"
 
@@ -3668,28 +3175,27 @@
 msgstr "Parent"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr "Appliquer"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
-msgstr "Marquer manuellement ce dépôt comme fork d’un autre dépôt de la liste."
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+msgstr ""
+"Marquer manuellement ce dépôt comme fork d’un autre dépôt de la liste."
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr "Visibilité du journal public"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "Supprimer du journal public"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr "Ajouter au journal public"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
@@ -3697,36 +3203,36 @@
 "Les actions réalisées sur ce dépôt seront visibles à tous depuis le "
 "journal public."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr "Changer le verrouillage"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 msgid "Confirm to unlock repository."
 msgstr "Veuillez confirmer le déverrouillage de ce dépôt."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 msgid "Unlock Repository"
 msgstr "Déverrouiller le dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr "Verrouillé par %s sur %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 msgid "Confirm to lock repository."
 msgstr "Veuillez confirmer le verrouillage de ce dépôt."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "Verrouiller le dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Ce dépôt n’est pas verrouillé"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3738,32 +3244,32 @@
 "L'utilisateur qui fait le pull verrouille le dépôt ; seul l'utilisateur "
 "qui a fait le pull et a verrouillé peut déverrouiller en faisant un push."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Voulez-vous vraiment supprimer le dépôt %s ?"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Supprimer ce dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] "Ce dépôt a %s fork"
 msgstr[1] "Ce dépôt a %s forks"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "Détacher les forks"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "Supprimer les forks"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3777,11 +3283,7 @@
 msgid "Invalidate Repository Cache"
 msgstr "Invalider le cache du dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr "Voulez-vous vraiment invalider le cache du dépôt ?"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
@@ -3789,95 +3291,100 @@
 "Invalider manuellement le cache de ce dépôt. Au prochain accès sur ce "
 "dépôt, il sera à nouveau mis en cache."
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 msgid "List of Cached Values"
 msgstr "Liste des valeurs en cache"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "Préfixe"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Clé"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Actif"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr "Libellé"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Voulez-vous vraiment supprimer ce champ : %s ?"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "Clé du nouveau champ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "Libellé du nouveau champ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "Saisir un libellé court"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "Description du nouveau champ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "Saisir la description du champ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr "Les champs supplémentaires sont désactivés."
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr "Dépôt privé"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr "Forker du dépôt"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr "URL du dépôt distant"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr "Récupérer les modifications depuis le dépôt distant"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 msgid "Confirm to pull changes from remote repository."
-msgstr "Voulez-vous vraiment récupérer les changements depuis le dépôt distant ?"
-
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgstr ""
+"Voulez-vous vraiment récupérer les changements depuis le dépôt distant ?"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr "Ce dépôt n'a pas d'URL de dépôt distant."
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr "ID permanent du dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr "Qu'est-ce que c'est ?"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr "URL par id"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3894,15 +3401,15 @@
 "d'intégration continue, ou dans tous les cas où vous devez saisir l'URL "
 "« en dur » dans un service tiers."
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr "Dépôt distant"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr "URL du dépôt"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
@@ -3910,21 +3417,26 @@
 "Optionel : URL d'un dépôt distant. Si renseigné, le dépôt sera pullé à "
 "partir de cette URL."
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 "Révision par défaut pour les pages de fichiers, de téléchargements, de "
 "recherche et de documentation"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr "Saisir le nom de l'utilisateur"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Changer le propriétaire de ce dépôt."
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr "Commits traités"
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr "Commits traités"
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr "Avancement"
 
@@ -3934,13 +3446,14 @@
 
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:10
 msgid "Confirm to remove current statistics."
-msgstr "Souhaitez-vous vraiment réinitialiser les statistiques de ce dépôt ?"
+msgstr ""
+"Souhaitez-vous vraiment réinitialiser les statistiques de ce dépôt ?"
 
 #: kallithea/templates/admin/repos/repos.html:5
 msgid "Repositories Administration"
 msgstr "Administration des dépôts"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr "État"
 
@@ -3961,7 +3474,7 @@
 msgstr "Visuel"
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Hooks"
 
@@ -3973,51 +3486,56 @@
 msgid "System Info"
 msgstr "Informations sytème"
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr "Envoyer un courriel de test à"
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Envoyer"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "Nom du site"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr "Mettez un title personnalisé pour votre service Kallithea."
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr "Mettez un title personnalisé pour votre service Kallithea."
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "Domaine d'authentification HTTP (realm)"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr "Bloc HTML pour l'analytique"
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr "Bloc de personnalisation HTML/JavaScript/CSS"
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
 msgstr ""
-"HTML avec du JavaScript pour les systèmes d'analyse Web comme Google "
-"Analytics ou Piwik. Ceci sera ajouté en bas de chaque page."
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (potentiellement avec du JavaScript et/ou du CSS) qui sera ajouté en "
+"bas de chaque page. Cela peut être utilisé pour les systèmes d'analyse de "
+"trafic Web, mais aussi pour effectuer des personnalisations spécifiques à "
+"une instance, comme ajouter une bannière de projet en haut de chaque page."
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr "Clé publique ReCaptcha"
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr "Clé publique pour le système reCaptcha."
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr "Clé privée ReCaptcha"
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
@@ -4025,17 +3543,21 @@
 "Clé privée pour le système reCaptcha. Définir cette valeur activera le "
 "captcha à l'enregistrement."
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr "Enregistrer les options"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr "Hooks Mercurial intégrés (lecture seule)"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr "Hooks personnalisés"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
@@ -4044,44 +3566,41 @@
 "certains évènements comme le push et le pull. Ils peuvent déclencher des "
 "fonctions Python ou des applications externes."
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr "Hooks personnalisés"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "Erreur lors de la suppression du hook"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr "Option de scan"
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr "Options de scan"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr "Supprimer les enregistrements de dépôts manquants"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
-"Cocher cette option pour supprimer tous les commentaires, les requêtes de"
-" pull et d'autres informations liées aux dépôts qui n'existent plus sur "
-"le système de fichiers."
+"Cocher cette option pour supprimer tous les commentaires, les requêtes de "
+"pull et d'autres informations liées aux dépôts qui n'existent plus sur le "
+"système de fichiers."
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "Invalider le cache pour tous les dépôts"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
-msgstr "Cocher pour recharger les données et vider le cache pour tous les dépôts."
-
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+msgstr ""
+"Cocher pour recharger les données et vider le cache pour tous les dépôts."
+
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr "Installer des hooks Git"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
+#: kallithea/templates/admin/settings/settings_mapping.html:28
 msgid ""
 "Verify if Kallithea's Git hooks are installed for each repository. "
 "Current hooks will be updated to the latest version."
@@ -4089,33 +3608,33 @@
 "Vérifier si les hooks Git de Kallithea sont installés pour chaque dépôt. "
 "Les hooks actuels seront mis à jour vers la dernière version."
 
-#: kallithea/templates/admin/settings/settings_mapping.html:28
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr "Écraser les hooks Git existants"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
+#: kallithea/templates/admin/settings/settings_mapping.html:35
 msgid ""
 "If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
 msgstr ""
 "Lors de l'installation des hooks Git, écraser tous les hooks existants, "
 "même s'ils ne semblent pas provenir de Kallithea. ATTENTION : cette "
 "opération détruira tous les hooks Git que vous avez déployés à la main !"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:35
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr "Relancer le scan des dépôts"
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "Option de construction de l'index"
 
-#: kallithea/templates/admin/settings/settings_search.html:12
+#: kallithea/templates/admin/settings/settings_search.html:9
 msgid "Build from scratch"
 msgstr "Construire ex nihilo"
 
-#: kallithea/templates/admin/settings/settings_search.html:15
+#: kallithea/templates/admin/settings/settings_search.html:12
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
@@ -4123,100 +3642,83 @@
 "Cette option ré-indexe complètement tous les dépôts pour pouvoir faire "
 "des recherches dans le texte complet."
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Mettre à jour l’index"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr "Vérification des mises à jour…"
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr "Version de Kallithea"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr "Vérifier les mises à jour"
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr "Fichier de configuration de Kallithea"
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr "Version de Python"
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
-msgstr "Plateforme"
+msgid "Check for updates"
+msgstr "Vérifier les mises à jour"
 
 #: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr "Fichier de configuration de Kallithea"
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr "Version de Python"
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr "Plateforme"
+
+#: kallithea/templates/admin/settings/settings_system.html:11
 msgid "Git version"
 msgstr "Version de Git"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr "Chemin de Git"
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr "Point d'accès aux informations de mise à jour"
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr "Note : vérifiez que le serveur peut accéder cette URL"
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr "Vérification des mises à jour…"
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr "Paquets Python"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Web"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "Nécessiter SSL pour les opérations de VCS"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-"Activez pour faire en sorte que Kallithea force l'utilisation de SSL pour"
-" pousser ou tirer. Si le certificat SSL est manquant, une erreur « HTTP "
-"406: Not Acceptable » sera renvoyée."
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "Afficher la taille du dépôt après un push"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "Journaliser les commandes de push"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "Journaliser les commandes de pull"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "Mettre à jour les dépôts après un push (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr "Extensions Mercurial"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Activer l'extension largefiles"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Activer l'extension hgsubversion"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
@@ -4224,54 +3726,55 @@
 "La bibliothèque hgsubversion doit être installée. Elle permet de cloner "
 "des dépôts SVN distants et de les migrer vers Mercurial."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 msgid "Location of repositories"
 msgstr "Emplacement des dépôts"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
 msgstr ""
 "Cliquez pour déverrouiller. Vous devez redémarrer Kallithea pour ce que "
 "réglage prenne effet."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 "Emplacement où les dépôts sont stockés sur le système de fichiers. La "
-"modification de cette valeur nécessite un re-démarrage et un nouveau "
-"scan."
-
-#: kallithea/templates/admin/settings/settings_visual.html:8
+"modification de cette valeur nécessite un re-démarrage et un nouveau scan."
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Général"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "Activer les champs supplémentaires sur les dépôts"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 "Permet d'enregistrer des champs personnalisés additionnels pour chaque "
 "dépôt."
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Afficher la version de Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr "Afficher ou cacher le numéro de version de Kallithea dans le pied de page."
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr "Utiliser Gravatar sur Kallithea"
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+"Afficher ou cacher le numéro de version de Kallithea dans le pied de page."
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr "Afficher les Gravatars des utilisateurs"
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4303,56 +3806,61 @@
 "                                                        {netloc}    "
 "emplacement réseau/hôte du serveur Kallithea en cours d'utilisation."
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr "URL de clone"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
 msgstr ""
-"Modèle de construction d'URL de clone. Par exemple : "
-"'{scheme}://{user}@{netloc}/{repo}'.\n"
+"Modèle de construction d'URL de clone. Par exemple : '{scheme}://{user}"
+"@{netloc}/{repo}'.\n"
 "                                                       Les variables "
 "suivantes sont disponibles :\n"
 "                                                        {scheme}    "
 "'http' ou 'https' envoyé à partir du serveur Kallithea en cours "
 "d'utilisation,\n"
-"                                                        {user}     nom de"
-" l'utilisateur courant,\n"
+"                                                        {user}     nom de "
+"l'utilisateur courant,\n"
 "                                                        {netloc}    "
 "emplacement réseau/hôte du serveur Kallithea en cours d'utilisation,\n"
 "                                                        {repo}    nom "
 "complet du dépôt,\n"
-"                                                        {repoid}    ID du"
-" dépôt, peut être utilisé pour cloner par ID."
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "Élements du tableau de bord"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                        {repoid}    ID du "
+"dépôt, peut être utilisé pour cloner par ID."
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr "Taille de la page du dépôt"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
 msgstr ""
-"Nombre d'éléments affichés dans la page principale du tableau de bord "
-"avant d'afficher la pagination."
+"Nombre d'éléments affichés dans les pages des dépôts avant d'afficher la "
+"pagination."
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
+msgstr "Taille de la page d'admin"
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr "Élements des pages admin"
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
@@ -4360,31 +3868,27 @@
 "Nombre d'éléments affichés dans les grilles des pages admin avant "
 "d'afficher la pagination."
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Icônes"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "Afficher l’icône de dépôt public sur les dépôts"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "Afficher l’icône de dépôt privé sur les dépôts"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 msgid "Show public/private icons next to repository names."
 msgstr "Afficher l’icône « public/privé » à côté du nom des dépôts."
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr "Meta-tagging"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr "Styliser les méta-tags reconnus :"
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
@@ -4392,24 +3896,29 @@
 "Analyser les méta-tags dans le champ de description du dépôt et les "
 "transformer en tags colorés."
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr "Styliser les méta-tags reconnus :"
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "Ajouter un groupe d'utilisateurs"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr "Groupes d'utilisateurs"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr "Ajouter un groupe d'utilisateurs"
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr "Description courte pour ce groupe d'utilisateur (optionnel)."
 
@@ -4428,13 +3937,13 @@
 msgstr "Groupe d'utilisateurs : %s"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Membres"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr "Voulez-vous vraiment supprimer ce groupe utilisateur : %s ?"
@@ -4443,15 +3952,15 @@
 msgid "Delete this user group"
 msgstr "Supprimer ce groupe d'utilisateurs"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Aucun membre pour l'instant"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Membres de groupe sélectionnés"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Membres disponibles"
 
@@ -4465,17 +3974,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "Utilisateurs"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr "Ajouter un utilisateur"
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "Confirmation"
 
@@ -4494,12 +4003,12 @@
 msgstr "Utilisateur : %s"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr "Source de l'enregistrement"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr "Dernière connexion"
 
@@ -4508,7 +4017,7 @@
 msgstr "Membre des groupes d'utilisateurs"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "Voulez-vous vraiment supprimer l’utilisateur « %s » ?"
@@ -4517,26 +4026,16 @@
 msgid "Delete this user"
 msgstr "Supprimer cet utilisateur"
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr "Hérité de %s"
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr "Changer l'avatar sur"
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-"E-mail manquant, veuillez mettre à jour l'adresse e-mail de cet "
-"utilisateur."
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr "Nom dans la source de l'enregistrement"
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "Confirmation du nouveau mot de passe"
 
@@ -4544,230 +4043,227 @@
 msgid "Users Administration"
 msgstr "Administration des utilisateurs"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr "Type d'authentification"
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "Instance de serveur : %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr "Support"
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Dépôt Mercurial"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Dépôt Git"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr "Créer un fork"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Résumé"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "Historique"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Fichiers"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "Basculer vers"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr "Afficher les requêtes de pull pour %s"
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Demandes de pull"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "Options"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr "Comparer le fork"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "Comparer"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "Rechercher"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr "Déverrouiller"
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr "Verrouiller"
 
-#: kallithea/templates/base/base.html:173
+#: kallithea/templates/base/base.html:174
 msgid "Follow"
 msgstr "Suivre"
 
-#: kallithea/templates/base/base.html:174
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr "Arrêter de suivre"
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "Fork"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr "Créer une requête de pull"
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr "Afficher les requêtes de pull pour %s"
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "Basculer vers"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr "Aucune correspondance trouvée"
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "Afficher l'activité récente"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "Journal public"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "Afficher les gists publics"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gists"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr "Tous les Gists publics"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr "Mes Gists publics"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr "Mes Gist privés"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "Recherche dans les dépôts"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr "Mes requêtes de pull"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr "Non connecté"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr "Connexion à votre compte"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
 msgstr "Mot de passe oublié ?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr "Vous n’avez pas de compte ?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "Se déconnecter"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr "Aucune correspondance trouvée"
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
-msgstr "Raccourcis clavier"
+msgid "Parent rev."
+msgstr "Révision parente"
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr "Raccourcis globaux"
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr "Révision fille"
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr "Hériter des réglages par défaut"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
-"Sélectionner pour hériter des réglages généraux, de la liste blanche d'IP"
-" et des permissions depuis les %s."
-
-#: kallithea/templates/base/default_perms_box.html:28
+"Sélectionner pour hériter des réglages généraux, de la liste blanche d'IP "
+"et des permissions depuis les %s."
+
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "Création de dépôts"
 
-#: kallithea/templates/base/default_perms_box.html:33
+#: kallithea/templates/base/default_perms_box.html:27
 msgid "Select this option to allow repository creation for this user"
 msgstr ""
 "Sélectionner cette option pour autoriser cet utilisateur à créer des "
 "dépôts"
 
-#: kallithea/templates/base/default_perms_box.html:40
+#: kallithea/templates/base/default_perms_box.html:33
 msgid "Create user groups"
 msgstr "Créer des groupes d'utilisateurs"
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 "Sélectionner cette option pour autoriser cet utilisateur à créer des "
 "groupes d'utilisateurs"
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "Forker les dépôts"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 "Sélectionner cette option pour autoriser cet utilisateur à forker des "
 "dépôts"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "Afficher"
 
@@ -4776,229 +4272,172 @@
 msgstr "Aucune permission définie pour l'instant"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Permission"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Éditer"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr "Aucune permission définie"
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr "Réessayer"
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr "Envoi…"
+
+#: kallithea/templates/base/root.html:30
+msgid "Unable to post"
+msgstr "Impossible de publier"
+
+#: kallithea/templates/base/root.html:31
 msgid "Add Another Comment"
 msgstr "Ajouter un autre commentaire"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "Arrêter de suivre ce dépôt"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "Suivre ce dépôt"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "Groupe"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "Membres"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "Chargement..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "chargement..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "Recherche tronquée"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "Aucun fichier correspondant"
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 msgid "Open New Pull Request from {0}"
 msgstr "Ouvrir une nouvelle requête de pull à partir de {0}"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr "Ouvrir une nouvelle requête de pull pour {0} &rarr; {1}"
 
-#: kallithea/templates/base/root.html:33
+#: kallithea/templates/base/root.html:41
 msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr "Afficher les changesets sélectionnés {0} &rarr; {1}"
 
-#: kallithea/templates/base/root.html:34
+#: kallithea/templates/base/root.html:42
 msgid "Selection Link"
 msgstr "Lien vers la sélection"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 msgid "Collapse Diff"
 msgstr "Replier le Diff"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 msgid "Expand Diff"
 msgstr "Déplier le Diff"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr "Aucune révision"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+"Saisir le nom de l'utilisateur ou du membre à qui donner l'autorisation"
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "Échec de la révocation de permission"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "Voulez-vous vraiment révoquer la permission pour {0} : {1} ?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr "activé"
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr "désactivé"
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Sélectionner le changeset"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr "Sélectionner le changeset"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "Signets de %s"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr "Comparer les marque-pages"
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Auteur"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Révision"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "Branches de %s"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr "Comparer les branches"
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "Tri ascendant"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "Tri descendant"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Aucun élément n’a été trouvé."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Erreur d’intégrité des données."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Chargement…"
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "Historique de %s"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] "Affichage de %d révision sur %d"
 msgstr[1] "Affichage de %d révisions sur %d"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "Vider la sélection"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr "Aller au sommet (tip) du dépôt"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "Comparer le fork avec %s"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "Comparer le fork avec le dépôt parent (%s)"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr "Filtre de branche :"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-"Statut du changeset : %s\n"
-"Cliquer pour ouvrir la requête de pull %s associée"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "Statut de changeset : %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr "Développer le message de commit"
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "Le changeset a des commentaires"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "Marque-page %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "Tag %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Branche %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Il n’y a aucun changement pour le moment"
 
@@ -5014,7 +4453,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "Ajouté"
 
@@ -5028,125 +4467,138 @@
 msgid "Affected %s files"
 msgstr "%s fichiers affectés"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr "Message de commit"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Âge"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Refs"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Ajouter ou téléverser des fichiers directement via Kallithea"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr "Ajouter un nouveau fichier"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr "Pusher le nouveau dépôt"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "Le dépôt existe déjà ?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr "Premier changeset dans cette liste (le plus vieux)"
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr "Dernier changeset dans cette liste (le plus récent)"
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr "Position dans cette liste de changesets"
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"Statut du changeset : %s par %s\n"
+"Cliquer pour ouvrir la requête de pull %s associée"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Statut de changeset : %s par %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr "Développer le message de commit"
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, python-format
+msgid "%s comments"
+msgstr "%s commentaires"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "Marque-page %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "Tag %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Branche %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "Changeset de %s"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr "Révision parente"
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr "Révision fille"
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "Statut du changeset"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "Diff brut"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr "Diff patch"
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr "Télécharger le diff"
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr "Fusion"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr "Grafté depuis :"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr "Transplanté depuis :"
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr "Remplacé par :"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr "Précédé par :"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] "%s fichier changé"
 msgstr[1] "%s fichiers changés"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "%s fichier changé avec %s insertions et %s suppressions"
 msgstr[1] "%s fichiers changés avec %s insertions et %s suppressions"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr "Afficher le diff complet quand même"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
-msgstr "Aucune révision"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+msgid "comment"
+msgstr "commentaire"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 msgid "on pull request"
@@ -5164,177 +4616,170 @@
 msgid "Delete comment?"
 msgstr "Supprimer le commentaire ?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
 msgstr "Changement de statut"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "Commentaire sur la ligne {1}."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-"Les commentaires sont analysés avec la syntaxe %s, avec le support de la "
-"commande %s."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
 msgstr ""
-"Utilisez @nomutilisateur dans ce texte pour envoyer une notification à un"
-" autre utilisateur"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Aperçu du commentaire"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr "Envoi…"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+"Les commentaires sont en texte brut. Utilisez @nomutilisateur pour "
+"envoyer une notification à un autre utilisateur."
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "Modifier le statut du changeset"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr "Voter pour le statut de la requête de pull"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr "Aucun changement"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr "Terminer la requête de pull"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr "Fermer"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Commentaire"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Aperçu"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "Vous devez être connecté pour poster des commentaires."
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "Se connecter maintenant"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "Masquer"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d commentaire"
 msgstr[1] "%d commentaires"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "%d de ligne"
 msgstr[1] "%d de ligne"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] "%d général"
 msgstr[1] "%d généraux"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-"Utilisez @nomutilisateur dans ce texte pour envoyer une notification à un"
-" autre utilisateur."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr "Voter pour le statut de la requête de pull"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr "Modifier le statut du changeset"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr "Aucun changement"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr "Fermer"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "Changesets de %s"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "Statut de changeset : %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "Fichiers affectés"
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr "Pas de fichier précédent"
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr "Fichier précédent"
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr "Modifié"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr "Supprimé"
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr "Renommé"
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr "Opération inconnue : %r"
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr "Aucun fichier suivant"
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr "Fichier suivant"
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr "Afficher le diff complet pour ce fichier"
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr "Afficher le diff complet côte-à-côte pour ce fichier"
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr "Afficher les commentaires de ligne"
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr "Supprimé"
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr "Renommé"
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "Aucun changeset"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "Ancêtre"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr "Premier changeset dans cette liste (le plus vieux)"
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr "Dernier changeset dans cette liste (le plus récent)"
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr "Position dans cette liste de changesets"
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr "Fusion croisée avec plusieurs ancêtres de fusion détectée !"
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+"Merci de fusionner la branche cible dans votre branche avant de créer une "
+"requête de pull."
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr "Ancêtre de fusion"
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr "Afficher le diff de fusion"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr "Ancêtre commun"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr "Aucun ancêtre commun trouvé - les dépôts n'ont aucun lien entre eux"
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr "est"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "Changesets de %s"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr "derrière"
 
@@ -5345,101 +4790,126 @@
 msgstr "Comparaison de %s"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr "Comparer les révisions"
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr "Échanger"
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr "Comparer les révisions, les branches, les marque-pages ou les tags."
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] "Affichage de %s commit"
 msgstr[1] "Affichage de %s commits"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "Afficher le diff complet"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Dépôt public"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr "Création du dépôt en cours..."
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "Dépôt vide"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "S’abonner au flux RSS de %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "S’abonner au flux ATOM de %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr "En cours de création"
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr "Le commentaire de %s sur le changeset de %s (%s) mentionne votre nom"
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr "Commentaire de %s sur le changeset de %s (%s)"
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "Le statut du changeset a été changé en"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr "Ceci est une notification automatique. Ne pas répondre à cet e-mail."
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "Mention dans le commentaire sur le changeset « %s »"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr "Commentaire sur le changeset « %s »"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr "Changeset sur"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr "branche"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr "par"
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr "Changement de statut :"
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr "La requête de pull a été fermée."
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Bonjour %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 "Nous avons reçu une demande de réinitialisation du mot de passe de votre "
 "compte."
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+"Cependant, ce compte est géré hors de ce système et le mot de passe ne "
+"peut pas être changé ici."
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr "Pour choisir un nouveau mot de passe, cliquez sur le lien suivant"
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
-"Si vous ne pouvez pas utiliser le lien ci-dessus, merci de saisir le code"
-" suivant dans le formulaire de réinitialisation de mot de passe"
-
-#: kallithea/templates/email_templates/password_reset.html:12
+"Si vous ne pouvez pas utiliser le lien ci-dessus, merci de saisir le code "
+"suivant dans le formulaire de réinitialisation de mot de passe"
+
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
@@ -5447,32 +4917,49 @@
 "Si vous n'avez pas demandé la réinitialisation de votre mot de passe, ne "
 "tenez pas compte de ce message."
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr "%s a mentionné votre nom dans la requête de pull %s « %s »"
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
-msgstr "%s vous demande de vérifier la requête de pull %s « %s »"
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr "Mention sur la requête de pull %s « %s » par %s"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr "Ajouté comme relecteur de la requête de pull %s « %s » par %s"
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+msgid "Pull request"
+msgstr "Requête de pull"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr "depuis"
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr "vers"
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr "Commentaire de %s sur la requête de pull %s « %s »"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr "Le commentaire a fermé la requête de pull avec le statut"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr "Le commentaire a été fait avec le statut"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "Visualiser cet utilisateur ici"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "Mention dans le commentaire sur la requête de pull %s « %s »"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Requête de pull %s « %s » fermée"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Commentaire sur la requête de pull %s « %s »"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
+msgstr "Nom complet"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5490,7 +4977,7 @@
 msgstr "Diff de fichier pour %s"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "Fichiers de %s"
@@ -5500,72 +4987,73 @@
 msgid "%s Files Add"
 msgstr "Ajout de fichiers pour %s"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr "Ajouter un nouveau fichier"
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Emplacement"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr "Saisir le nom du fichier..."
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "ou"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr "Uploader un fichier"
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr "Créer un nouveau fichier"
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr "Mode du nouveau fichier"
+msgid "New file type"
+msgstr "Type du nouveau fichier"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr "Message de commit"
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr "Commiter les changements"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "Révision précédente"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "Révision suivante"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "Suivre la branche courante"
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr "Rechercher dans la liste des fichiers"
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "Chargement de la liste des fichiers…"
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Taille"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "Dernière révision"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr "Dernière modification"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr "Dernier commiteur"
 
@@ -5575,7 +5063,7 @@
 msgstr "Suppression de fichiers pour %s"
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr "Supprimer le fichier"
 
@@ -5588,24 +5076,20 @@
 msgid "Edit file"
 msgstr "Éditer le fichier"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr "Afficher l'annotation"
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
 msgid "Download as Raw"
 msgstr "Télécharger au format brut"
 
-#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "Source"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "Édition du fichier"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5613,55 +5097,63 @@
 msgstr[0] "%s auteur"
 msgstr[1] "%s auteurs"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr "Diff par rapport à une révision"
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr "Afficher à une révision"
 
-#: kallithea/templates/files/files_source.html:10
+#: kallithea/templates/files/files_source.html:9
 msgid "Show Full History"
 msgstr "Afficher l'historique complet"
 
-#: kallithea/templates/files/files_source.html:11
+#: kallithea/templates/files/files_source.html:10
 msgid "Show Authors"
 msgstr "Afficher les auteurs"
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr "Afficher la source"
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
 msgstr "Éditer sur la branche : %s"
 
-#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/files/files_source.html:37
 msgid "Editing binary files not allowed"
 msgstr "Édition de fichiers binaires interdite"
 
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:40
 msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 "Édition de fichiers autorisée uniquement sur la révision de tête (head) "
 "de la branche"
 
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/files/files_source.html:41
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 "Suppression de fichiers autorisée uniquement sur la révision de tête "
 "(head) de la branche"
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "Fichier binaire (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr "Ce fichier est trop gros pour être affiché."
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr "Afficher l'annotation complète quand même."
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Ce fichier est trop gros pour être affiché"
+msgid "Show as raw."
+msgstr "Montrer en texte brut."
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5681,12 +5173,12 @@
 msgstr "Followers de %s"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "Followers"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "A commencé à suivre le dépôt :"
 
@@ -5695,37 +5187,37 @@
 msgid "Fork repository %s"
 msgstr "Forker le dépôt %s"
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "Nom du fork"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 "Révision par défaut pour les pages de fichiers, de téléchargements, de "
 "recherche et de documentation."
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Privé"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Copier les permissions"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "Copier les permissions depuis le dépôt forké"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "MÀJ après le clonage"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "Mettre à jour depuis la source après clonage"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr "Forker ce dépôt"
 
@@ -5735,40 +5227,40 @@
 msgstr "Forks de %s"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "Forks"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "Forké"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "Il n’y a pas encore de forks"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "Flux ATOM du journal"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "Flux RSS du journal"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "Mes dépôts"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "Aucune entrée pour le moment"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "Flux ATOM du journal public"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "Flux RSS du journal public"
 
@@ -5777,31 +5269,36 @@
 msgid "New Pull Request"
 msgstr "Nouvelle requête de pull"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Titre"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr "Résumer les modifications - ou laisser vide"
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "Saisir une courte description de cette requête de pull"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "Flux des changesets"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "Dépôt d'origine"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Révision"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "Dépôt de destination"
 
@@ -5813,6 +5310,10 @@
 msgid "Vote"
 msgstr "Voter"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Âge"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr "Depuis"
@@ -5836,7 +5337,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "Fermée"
 
@@ -5865,51 +5366,32 @@
 msgid "Pull request %s from %s#%s"
 msgstr "Requête de pull %s depuis %s#%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr "Résumer les changements"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr "Résultat du vote des relecteurs"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr "Résultat du vote"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr "Statut de la requête de pull calculé à partir des votes"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "Pas encore relue par"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d relecteur"
-msgstr[1] "%d relecteurs"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "La requête de pull a été relue par tous les relecteurs"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr "Il n'y a aucun relecteur"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr "Origine"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr "sur"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr "Cible"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
@@ -5917,54 +5399,67 @@
 "Ceci est juste une série de changesets, et n'a pas de cible ou de "
 "véritable ancêtre de fusion."
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "Puller les modifications"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr "Mettre à jour"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr "Prochaine itération"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr "Révision courante - aucun changement"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr "Relecteurs de la requête de pull"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+"Les itérations de requête de pull ne modifient pas de contenu une fois "
+"qu'elles sont créées. Sélectionnez une révision pour créer une nouvelle "
+"itération."
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr "Enregistrer les changements"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr "Créer une nouvelle itération avec ces modifications"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr "Annuler les modifications"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr "Relecteurs"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "Supprimer le relecteur"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr "Saisir le nom du relecteur à ajouter"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "Relecteurs potentiels"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr "Cliquer pour ajouter le propriétaire du dépôt comme relecteur :"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr "Enregistrer les changements"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr "Sauvegarder en tant que nouvelle requête de pull"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr "Annuler les modifications"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr "Contenu de la requête de pull"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr "Ancêtre commun"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5980,39 +5475,43 @@
 msgid "Pull Requests to '%s'"
 msgstr "Requête de pull vers '%s'"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr "Ouvrir une nouvelle requête de pull"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Afficher les requêtes de pull vers %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Afficher les requêtes de pull depuis '%s'"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 "Cacher les requêtes de pull fermées (afficher uniquement les requêtes de "
 "pull ouvertes)"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 "Afficher les requêtes de pull fermées (en plus des requêtes de pull "
 "ouvertes)"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr "Requêtes de pull créées par moi"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr "Requêtes de pull nécessitant ma relecture"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr "Requêtes de pull auxquelles je participe"
 
@@ -6026,29 +5525,29 @@
 msgid "Search in All Repositories"
 msgstr "Rechercher dans tous les dépôts"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "Termes de la recherches"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "Rechercher dans"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "Le contenu des fichiers"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "Les messages de commit"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "Les noms de fichiers"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "Permission refusée"
 
@@ -6058,80 +5557,80 @@
 msgstr "Statistiques pour %s"
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr "Flux ATOM pour %s"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "Flux RSS pour %s"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "Activer"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "Statistiques obtenues : "
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "Fichiers"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Afficher plus"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "commits"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "fichiers ajoutés"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "fichiers modifiés"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "fichiers supprimés"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "fichier ajouté"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "fichié modifié"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "fichier supprimé"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "Résumé de %s"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "Dépôt verrouillé par %s"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "Dépôt déverrouillé"
 
@@ -6139,85 +5638,476 @@
 msgid "Fork of"
 msgstr "Fork de"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Cloner depuis"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr "URL de clone"
-
-#: kallithea/templates/summary/summary.html:78
-msgid "Show by Name"
-msgstr "Afficher par nom"
-
-#: kallithea/templates/summary/summary.html:79
+#: kallithea/templates/summary/summary.html:68
 msgid "Show by ID"
 msgstr "Afficher par ID"
 
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr "Afficher par nom"
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "Populaires"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "Téléchargements"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "Il n’y a pas encore de téléchargements proposés"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "Les téléchargements sont désactivés pour ce dépôt"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "Télécharger en ZIP"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
-msgstr "Télécharger une archive contenant également les sous-dépôts éventuels"
-
-#: kallithea/templates/summary/summary.html:125
+msgstr ""
+"Télécharger une archive contenant également les sous-dépôts éventuels"
+
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr "Avec les sous-dépôts"
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "Taille du dépôt"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "Flux"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Derniers changements"
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr "Démarrage rapide"
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr "Démarrage rapide"
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr "Ajouter ou téléverser des fichiers directement via Kallithea"
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr "Pusher le nouveau dépôt"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "Le dépôt existe déjà ?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr "Fichier Lisez-moi de la revision %s:%s"
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "Télécharge %s comme %s"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "Tags de %s"
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr "Comparer les tags"
+#~ msgid ""
+#~ "Changing status on a changeset associated with a closed pull request "
+#~ "is not allowed"
+#~ msgstr ""
+#~ "La modification de l'état sur un ensemble de modifications associé à "
+#~ "une demande de tirage fermé n'est pas autorisé"
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Fermé, remplacé par %s."
+
+#~ msgid "This pull request can be updated with changes on %s:"
+#~ msgstr ""
+#~ "Cette demande de pull peut être mise à jour avec les modifications de "
+#~ "%s :"
+
+#~ msgid "Closing."
+#~ msgstr "Fermeture."
+
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr ""
+#~ "L’index de recherche n’est pas présent. Veuillez exécuter l’indexeur "
+#~ "de code Whoosh"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP %s non autorisée"
+
+#~ msgid "Repository no access"
+#~ msgstr "Aucun accès au dépôt"
+
+#~ msgid "Repository read access"
+#~ msgstr "Accès en lecture au dépôt"
+
+#~ msgid "Repository write access"
+#~ msgstr "Accès en écriture au dépôt"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Accès administrateur au dépôt"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Aucun accès au groupe de dépôts"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Accès en lecture au groupe de dépôts"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Accès en écriture au groupe de dépôts"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Accès administrateur au groupe de dépôts"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Création de dépôt désactivée"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Création de dépôt activée"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Fork de dépôt désactivé"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Fork de dépôt activé"
+
+#~ msgid "Register disabled"
+#~ msgstr "Enregistrement désactivé"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Enregistrer un nouvel utilisateur Kallithea manuellement activé"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr "Enregistrer un nouvel utilisateur Kallithea auto-activé"
+
+#~ msgid "Rejected"
+#~ msgstr "Rejetée"
+
+#~ msgid "Under Review"
+#~ msgstr "En cours de relecture"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Groupe de dépôts, pas d'accès"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Groupe de dépôts, accès en lecture"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Groupe de dépôts, accès en écriture"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Groupe de dépôts, accès d'administration"
+
+#~ msgid "User group no access"
+#~ msgstr "Groupe d'utilisateurs, pas d'accès"
+
+#~ msgid "User group read access"
+#~ msgstr "Groupe d'utilisateurs, accès en lecture"
+
+#~ msgid "User group write access"
+#~ msgstr "Groupe d'utilisateurs, accès en écriture"
+
+#~ msgid "User group admin access"
+#~ msgstr "Groupe d'utilisateurs, accès d'administration"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Création de groupes de dépôts désactivée"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Création de groupes de dépôts activée"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Création de groupes d'utilisateurs désactivée"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Création de groupes d'utilisateurs activée"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr ""
+#~ "Enregistrement des utilisateurs avec activation de compte manuelle"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr ""
+#~ "Enregistrement des utilisateurs avec activation de compte automatique"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s a commenté sur le changeset %(age)s"
+
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s a envoyé un message %(age)s"
+
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s vous a mentionné %(age)s"
+
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s s'est enregistré sur Kallithea %(age)s"
+
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s a ouvert une nouvelle demande de pull %(age)s"
+
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s a commenté la demande de pull %(age)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s a commenté sur le changeset à %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s a envoyé un message à %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s vous a mentionné à %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s s'est enregistré sur Kallithea à %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s a ouvert une nouvelle demande de pull à %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s a commenté la demande de pull à %(when)s"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "[Ajouté] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour "
+#~ "%(repo_name)s"
+
+#~ msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "[Commentaire] Demande de pull %(pr_nice_id)s à partir de %(ref)s pour "
+#~ "%(repo_name)s"
+
+#~ msgid "Dashboard"
+#~ msgstr "Tableau de bord"
+
+#~ msgid "quick filter..."
+#~ msgstr "Filtre rapide…"
+
+#~ msgid "repositories"
+#~ msgstr "Dépôts"
+
+#~ msgid "No repositories found."
+#~ msgstr "Pas de dépôts trouvés."
+
+#~ msgid "Remember me"
+#~ msgstr "Se souvenir de moi"
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Aucune branche n’a été créée pour le moment"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Aucun tag n’a été créé pour le moment"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Aucun signet n’a été créé"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Vous pouvez changer votre avatar sur"
+
+#~ msgid "Using"
+#~ msgstr "en utilisant l’adresse"
+
+#~ msgid "Missing email, please update your user email address."
+#~ msgstr ""
+#~ "Adresse courriel manquante, veuillez mettre à jour votre adresse "
+#~ "courriel."
+
+#~ msgid "My Notifications"
+#~ msgstr "Mes notifications"
+
+#~ msgid "All"
+#~ msgstr "Tous"
+
+#~ msgid "Comments"
+#~ msgstr "Commentaires"
+
+#~ msgid "Mark All Read"
+#~ msgstr "Tout marquer comme lu"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Aucune notification pour le moment"
+
+#~ msgid "Show Notification"
+#~ msgstr "Montrer Notification"
+
+#~ msgid "Notifications"
+#~ msgstr "Notifications"
+
+#~ msgid "%s Repository group dashboard"
+#~ msgstr "Tableau de bord du groupe de dépôts %s"
+
+#~ msgid "Home"
+#~ msgstr "Accueil"
+
+#~ msgid "with"
+#~ msgstr "comprenant"
+
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "Voulez-vous vraiment invalider le cache du dépôt ?"
+
+#~ msgid "Analytics HTML block"
+#~ msgstr "Bloc HTML pour l'analytique"
+
+#~ msgid ""
+#~ "HTML with JavaScript for web analytics systems like Google Analytics "
+#~ "or Piwik. This will be added at the bottom of every page."
+#~ msgstr ""
+#~ "HTML avec du JavaScript pour les systèmes d'analyse Web comme Google "
+#~ "Analytics ou Piwik. Ceci sera ajouté en bas de chaque page."
+
+#~ msgid "Web"
+#~ msgstr "Web"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "Nécessiter SSL pour les opérations de VCS"
+
+#~ msgid ""
+#~ "Activate to require SSL both pushing and pulling. If SSL certificate "
+#~ "is missing, it will return an HTTP Error 406: Not Acceptable."
+#~ msgstr ""
+#~ "Activez pour faire en sorte que Kallithea force l'utilisation de SSL "
+#~ "pour pousser ou tirer. Si le certificat SSL est manquant, une erreur "
+#~ "« HTTP 406: Not Acceptable » sera renvoyée."
+
+#~ msgid "Use Gravatars in Kallithea"
+#~ msgstr "Utiliser Gravatar sur Kallithea"
+
+#~ msgid "Dashboard items"
+#~ msgstr "Élements du tableau de bord"
+
+#~ msgid ""
+#~ "Number of items displayed in the main page dashboard before pagination "
+#~ "is shown."
+#~ msgstr ""
+#~ "Nombre d'éléments affichés dans la page principale du tableau de bord "
+#~ "avant d'afficher la pagination."
+
+#~ msgid "Missing email, please update this user email address."
+#~ msgstr ""
+#~ "E-mail manquant, veuillez mettre à jour l'adresse e-mail de cet "
+#~ "utilisateur."
+
+#~ msgid "Keyboard shortcuts"
+#~ msgstr "Raccourcis clavier"
+
+#~ msgid "Site-wide shortcuts"
+#~ msgstr "Raccourcis globaux"
+
+#~ msgid "members"
+#~ msgstr "Membres"
+
+#~ msgid "enabled"
+#~ msgstr "activé"
+
+#~ msgid "disabled"
+#~ msgstr "désactivé"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "Signets de %s"
+
+#~ msgid "Compare Bookmarks"
+#~ msgstr "Comparer les marque-pages"
+
+#~ msgid "Author"
+#~ msgstr "Auteur"
+
+#~ msgid "%s Branches"
+#~ msgstr "Branches de %s"
+
+#~ msgid "Compare Branches"
+#~ msgstr "Comparer les branches"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "Le changeset a des commentaires"
+
+#~ msgid "Refs"
+#~ msgstr "Refs"
+
+#~ msgid "Commenting on line {1}."
+#~ msgstr "Commentaire sur la ligne {1}."
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr ""
+#~ "Les commentaires sont analysés avec la syntaxe %s, avec le support de "
+#~ "la commande %s."
+
+#~ msgid "Use @username inside this text to notify another user"
+#~ msgstr ""
+#~ "Utilisez @nomutilisateur dans ce texte pour envoyer une notification à "
+#~ "un autre utilisateur"
+
+#~ msgid "Comment preview"
+#~ msgstr "Aperçu du commentaire"
+
+#~ msgid "Preview"
+#~ msgstr "Aperçu"
+
+#~ msgid "No common ancestor found - repositories are unrelated"
+#~ msgstr ""
+#~ "Aucun ancêtre commun trouvé - les dépôts n'ont aucun lien entre eux"
+
+#~ msgid "Comment from %s on %s changeset %s mentioned you"
+#~ msgstr ""
+#~ "Le commentaire de %s sur le changeset de %s (%s) mentionne votre nom"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "Le statut du changeset a été changé en"
+
+#~ msgid "This is an automatic notification. Don't reply to this mail."
+#~ msgstr ""
+#~ "Ceci est une notification automatique. Ne pas répondre à cet e-mail."
+
+#~ msgid "%s requested your review of %s pull request \"%s\""
+#~ msgstr "%s vous demande de vérifier la requête de pull %s « %s »"
+
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "Le commentaire a fermé la requête de pull avec le statut"
+
+#~ msgid "The comment was made with status"
+#~ msgstr "Le commentaire a été fait avec le statut"
+
+#~ msgid "View this user here"
+#~ msgstr "Visualiser cet utilisateur ici"
+
+#~ msgid "Previous revision"
+#~ msgstr "Révision précédente"
+
+#~ msgid "Next revision"
+#~ msgstr "Révision suivante"
+
+#~ msgid "Follow current branch"
+#~ msgstr "Suivre la branche courante"
+
+#~ msgid "Editing file"
+#~ msgstr "Édition du fichier"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "Pas encore relue par"
+
+#~ msgid "Pull request was reviewed by all reviewers"
+#~ msgstr "La requête de pull a été relue par tous les relecteurs"
+
+#~ msgid "There are no reviewers"
+#~ msgstr "Il n'y a aucun relecteur"
+
+#~ msgid "Update"
+#~ msgstr "Mettre à jour"
+
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Relecteurs de la requête de pull"
+
+#~ msgid "Save as New Pull Request"
+#~ msgstr "Sauvegarder en tant que nouvelle requête de pull"
+
+#~ msgid "%s Tags"
+#~ msgstr "Tags de %s"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Comparer les tags"
 
 #~ msgid "No comments."
 #~ msgstr "Aucun commentaire."
@@ -6249,9 +6139,6 @@
 #~ msgid "No Files"
 #~ msgstr "Aucun fichier"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "Le nom d’utilisateur « %(username)s » n’est pas autorisé"
 
@@ -6264,12 +6151,6 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "URL de clonage invalide"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
 #~ msgid "Defaults"
 #~ msgstr "Par défaut"
 
@@ -6301,7 +6182,8 @@
 #~ msgstr "Écraser les paramètres existants"
 
 #~ msgid "Default IP Whitelist for All Users"
-#~ msgstr "Liste blanche d'adresses IP par défaut pour tous les utilisateurs"
+#~ msgstr ""
+#~ "Liste blanche d'adresses IP par défaut pour tous les utilisateurs"
 
 #~ msgid "Default User Permissions Overview"
 #~ msgstr "Vue d'ensemble des permissions utilisateur par défaut"
@@ -6330,21 +6212,9 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Récupérer les changements depuis le site distant"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
 #~ msgid "edit"
 #~ msgstr "éditer"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
 #~ msgid "Email prefix"
 #~ msgstr "Préfixe courriel"
 
@@ -6384,60 +6254,15 @@
 #~ msgid "Default permissions"
 #~ msgstr "Permissions par défaut"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Status change from pull request"
-#~ msgstr "Changement de statut -> %s"
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
 #~ msgid "Mimetype"
 #~ msgstr "Type MIME"
 
 #~ msgid "My Repos"
 #~ msgstr "Mes dépôts"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "Propriétaire"
 
-#~ msgid "reviewer"
-#~ msgstr ""
-
 #~ msgid "Your new password"
 #~ msgstr "Votre nouveau mot de passe"
 
@@ -6447,17 +6272,5 @@
 #~ msgid "Open New Pull Request for Selected Changesets"
 #~ msgstr "Nouvelle requête de pull"
 
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
 #~ msgid "Created by"
 #~ msgstr "créé"
--- a/kallithea/i18n/how_to	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/how_to	Sun Mar 31 21:28:56 2019 +0200
@@ -18,25 +18,26 @@
 automatic checks etc. Weblate imports the source code tree directly from
 the version control system, and commits edits back from time to time.
 
-When registering at Weblate, make sure you name and email address you prefer to
-be used when your changes are committed. We can and probably will amend changesets
-coming from Weblate, but having things right from the beginning makes things easier.
+When registering at Weblate, make sure you use the name and email address you
+prefer to be used when your changes are committed. We can and probably will
+amend changesets coming from Weblate, but having things right from the beginning
+makes things easier.
 
 Weblate performs sanity checks all the time and tries to prevent you from ignoring
-them. Most common mistakes are inconsistent punctuation, whitespaces, missing or extra
+them. Most common mistakes are inconsistent punctuation, whitespace, missing or extra
 format parameters, untranslated strings copied into the translation. Please perform
 necessary corrections when they're needed, or override the false positives.
 
 
-Merging translations from Weblate
----------------------------------
+Merging translations from Weblate (admin-only)
+----------------------------------------------
 
 Weblate rebases its changes every time it pulls from our repository. Pulls are triggered
 by a web hook from Our Own Kallithea every time it receives new commits. Usually merging
-the new translations is a straightforward process consisting of a pull from Weblate-hosted
-repository which is available under Data Exports tab in Weblate interface.
+the new translations is a straightforward process consisting of a pull from the Weblate-hosted
+repository which is available under the Data Exports tab in the Weblate interface.
 
-Weblate tries to minimise the number of commits, but that's not always work, especially
+Weblate tries to minimise the number of commits, but that doesn't always work, especially
 when two translators work with different languages at more or less the same time.
 It makes sense sometimes to re-order or fold commits by the same author when they touch
 just the same language translation. That, however, may confuse Weblate sometimes, in
@@ -44,6 +45,28 @@
 using its administrative interface.
 
 
+Regenerating translatations after source code changes (admin-only)
+------------------------------------------------------------------
+
+When the Kallithea source code changes, both the location as the content of
+translation strings can change. It is therefore necessary to regularly
+regenerate the `kallithea.pot` file containing these strings, as well as aligning
+the translation files (`*.po`).
+
+First update the translation strings::
+
+    python2 setup.py extract_messages
+
+Then regenerate the translation files. This could either be done with `python2
+setup.py update_catalog` or with `msgmerge` from the `gettext` package. As
+Weblate is also touching these translation files, it is preferred to use the
+same tools (`msgmerge`) and settings as Weblate to minimize the diff::
+
+    find kallithea/i18n -name kallithea.po | xargs -I '{}' \
+        msgmerge --width=76 --backup=none --previous --update '{}' \
+        kallithea/i18n/kallithea.pot
+
+
 Manual creation of a new language translation
 ---------------------------------------------
 
@@ -70,8 +93,8 @@
     python2 setup.py compile_catalog -l <new_language_code>
 
 
-Updating translations
----------------------
+Manually updating translations
+------------------------------
 
 Extract the latest versions of strings for translation by running::
 
@@ -81,17 +104,17 @@
 
     python2 setup.py update_catalog -l <new_language_code>
 
-Edit the new updated translation file. Repeat all steps after `init_catalog` step from
-new translation instructions
+Edit the newly updated translation file. Repeat all steps after the
+`init_catalog` step from the 'new translation' instructions above.
 
 
 Testing translations
 --------------------
 
-Edit kallithea/tests/test.ini file and set lang attribute to::
-
-    lang=<new_language_code>
+Edit `kallithea/tests/conftest.py` and set `i18n.lang` to `<new_language_code>`
+and run Kallithea tests by executing::
 
-Run Kallithea tests by executing::
+    py.test
 
-    nosetests
+
+.. _Weblate: http://weblate.org/
--- a/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/hu/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,11 +7,11 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2015-04-11 00:59+0200\n"
 "Last-Translator: Balázs Úr <urbalazs@gmail.com>\n"
-"Language-Team: Hungarian "
-"<https://hosted.weblate.org/projects/kallithea/kallithea/hu/>\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"
@@ -19,442 +19,454 @@
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 "X-Generator: Weblate 2.3-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+msgid "No permission to change status"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr ""
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr ""
-
-#: kallithea/controllers/feed.py:56
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
 msgstr ""
 
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr ""
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr ""
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr ""
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr ""
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr ""
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
-#, python-format
-msgid "%s (closed)"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:152
-#: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
-msgid "Changeset"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:173
-msgid "Special"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:174
-msgid "Peer branches"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
-msgid "Bookmarks"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:310
-#, python-format
-msgid "Error creating pull request: %s"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
+#: kallithea/controllers/pullrequests.py:71
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:182
+msgid "Peer branches"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
 #, python-format
 msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
-msgid "Pull request updated"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:529
-msgid "Successfully deleted pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:595
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:597
-msgid "This pull request has been closed and can not be updated."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -466,149 +478,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -616,622 +630,619 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 #, fuzzy
 msgid "Repository has been locked"
 msgstr "Ennek a tárolónak %s elágazása van"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 #, fuzzy
 msgid "Repository has been unlocked"
 msgstr "Ennek a tárolónak %s elágazása van"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid ""
-"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
-"missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Private Repository"
 msgid "Invalidated %s repositories"
 msgstr "Tároló törlése"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1239,1158 +1250,652 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
-#, fuzzy
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
-#, fuzzy
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr ""
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2398,146 +1903,129 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2545,30 +2033,16 @@
 msgstr[1] "%s bejegyzés"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2576,7 +2050,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2585,108 +2059,108 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid ""
-"Private repositories are only visible to people explicitly added as "
-"collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2695,45 +2169,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2742,7 +2216,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2765,16 +2240,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2783,45 +2258,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2831,182 +2306,197 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, fuzzy, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "A csoport törlésének megerősítése"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "A csoport törlésének megerősítése"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -3014,105 +2504,68 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr ""
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3126,167 +2579,173 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid ""
-"Note: This will also give all users API access to create repositories "
-"everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "A csoport törlésének megerősítése"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3295,30 +2754,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3343,12 +2801,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3359,16 +2817,32 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3376,102 +2850,97 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr "A csoport törlésének megerősítése"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr ""
+#, fuzzy, python-format
+#| msgid "This repository has %s fork"
+#| msgid_plural "This repository has %s forks"
+msgid "Repository group %s"
+msgstr "Ennek a tárolónak %s elágazása van"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "Tároló törlése"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3504,22 +2973,22 @@
 msgid "%s Repository Settings"
 msgstr "%s tároló beállítások"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3528,63 +2997,61 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
+#, python-format
+msgid "Locked by %s on %s"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:60
-msgid "Confirm to lock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
-msgid "Lock Repository"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3592,32 +3059,32 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Tároló törlése"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 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:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3628,107 +3095,109 @@
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "Tároló törlése"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Remote repository"
+msgid "Fork of repository"
+msgstr "Tároló törlése"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Tároló törlése"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-msgid "Confirm to pull changes from remote repository."
-msgstr ""
-
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3738,34 +3207,39 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "Tároló törlése"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3781,7 +3255,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3802,7 +3276,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3814,276 +3288,266 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:64
-msgid "Location of repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:69
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4100,92 +3564,98 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4204,13 +3674,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4219,15 +3689,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4241,17 +3711,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4270,12 +3740,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4284,7 +3754,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4293,24 +3763,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4318,222 +3780,219 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr ""
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4542,227 +4001,171 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-msgid "Add Another Comment"
-msgstr "Egy másik hozzászólás hozzáadása"
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-msgid "Open New Pull Request from {0}"
-msgstr ""
+msgid "Add Another Comment"
+msgstr "Egy másik hozzászólás hozzáadása"
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
+msgid "Open New Pull Request from {0}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4778,7 +4181,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4792,38 +4195,60 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "No comments."
+msgid "%s comments"
+msgstr "Nincsenek hozzászólások."
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4831,86 +4256,78 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
-msgstr ""
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "No comments."
+msgid "comment"
+msgstr "Nincsenek hozzászólások."
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 msgid "on pull request"
@@ -4929,172 +4346,167 @@
 msgid "Delete comment?"
 msgstr "Hozzászólás törlése?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "%s módosításcsomag"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "%d sorközi"
 msgstr[1] "%d sorközi"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr ""
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "%s módosításcsomag"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -5105,127 +4517,173 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "%s changesets"
+msgid "Changeset on"
+msgstr "%s módosításcsomag"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "%s módosításcsomag"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "Ennek a tárolónak %s elágazása van"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+msgid "Pull request"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5244,7 +4702,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5254,72 +4712,73 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5329,7 +4788,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5342,24 +4801,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5367,50 +4822,58 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5431,12 +4894,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5445,35 +4908,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5483,40 +4946,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5525,31 +4988,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5561,6 +5029,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5584,7 +5056,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5597,7 +5069,7 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
 msgstr ""
 
@@ -5611,104 +5083,97 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "Remove reviewer"
+msgid "Reviewers"
+msgstr "Átnéző eltávolítása"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "Átnéző eltávolítása"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "Lehetséges átnézők"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5724,35 +5189,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5766,29 +5235,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5798,80 +5267,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5879,458 +5348,82 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr ""
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
-#~ msgid "increase diff context to %(num)s lines"
-#~ msgstr ""
-
-#~ msgid "No comments."
-#~ msgstr "Nincsenek hozzászólások."
-
-#~ msgid "public journal"
-#~ msgstr ""
-
-#~ msgid "journal"
-#~ msgstr ""
-
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Cannot delete %s it still contains attached forks"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked"
-#~ msgstr ""
-
-#~ msgid "Locked"
-#~ msgstr ""
-
-#~ msgid "Repository has been %s"
-#~ msgstr ""
-
-#~ msgid "You can't edit this user"
-#~ msgstr ""
-
-#~ msgid "compare view"
-#~ msgstr ""
-
-#~ msgid "fork name %s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s"
-#~ msgstr ""
-
-#~ msgid "No Files"
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
-#~ msgid "You can't remove this user since it's crucial for entire application"
-#~ msgstr ""
-
-#~ msgid "Username \"%(username)s\" is forbidden"
-#~ msgstr ""
-
-#~ msgid "invalid password"
-#~ msgstr ""
-
-#~ msgid "invalid user name"
-#~ msgstr ""
-
-#~ msgid "Your account is disabled"
-#~ msgstr ""
-
-#~ msgid "Repository name %(repo)s is disallowed"
-#~ msgstr ""
-
-#~ msgid "invalid clone URL"
-#~ msgstr ""
-
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "This email address is already taken"
-#~ msgstr ""
-
-#~ msgid "email \"%(email)s\" does not exist."
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
-#~ msgid "Defaults"
-#~ msgstr ""
-
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
-#~ msgid "Watched"
-#~ msgstr ""
-
-#~ msgid "My Permissions"
-#~ msgstr ""
-
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
-#~ msgid "delete"
-#~ msgstr ""
-
-#~ msgid "current IP"
-#~ msgstr ""
-
-#~ msgid "Permissions Administration"
-#~ msgstr ""
-
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Repository creation"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
-#~ msgid "Default User Permissions Overview"
-#~ msgstr ""
-
-#~ msgid "none"
-#~ msgstr ""
-
-#~ msgid "read"
-#~ msgstr ""
-
-#~ msgid "write"
-#~ msgstr ""
-
-#~ msgid "admin"
-#~ msgstr ""
-
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "default"
-#~ msgstr ""
-
-#~ msgid "revoke"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr ""
-
-#~ msgid "private repository"
-#~ msgstr ""
-
-#~ msgid "Remote URL"
-#~ msgstr ""
-
-#~ msgid "Pull Changes from Remote Location"
-#~ msgstr ""
-
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
-#~ msgid "edit"
-#~ msgstr ""
-
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
-#~ msgid "Default permissions"
-#~ msgstr ""
-
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "merge"
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Status change from pull request"
-#~ msgstr ""
-
-#~ msgid "Comment from pull request"
-#~ msgstr ""
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
-#~ msgid "Mimetype"
-#~ msgstr ""
-
-#~ msgid "My Repos"
-#~ msgstr ""
-
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
-#~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid "Your password reset link was sent"
-#~ msgstr ""
-
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changeset __S"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
-#~ msgid "Created by"
-#~ msgstr ""
-
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "Changeset not found"
-#~ msgstr ""
-
-#~ msgid "Non-admins can can fork repositories"
-#~ msgstr ""
-
-#~ msgid "Pull Requests from %s'"
-#~ msgstr ""
-
+#, fuzzy
+#~| msgid "Remote repository"
+#~ msgid "Repository Group"
+#~ msgstr "Tároló törlése"
--- a/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/ja/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -12,11 +12,11 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2016-01-07 01:53+0000\n"
 "Last-Translator: Takumi IINO <trot.thunder@gmail.com>\n"
-"Language-Team: Japanese "
-"<https://hosted.weblate.org/projects/kallithea/stable/ja/>\n"
+"Language-Team: Japanese <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/ja/>\n"
 "Language: ja\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -24,447 +24,473 @@
 "Plural-Forms: nplurals=1; plural=0;\n"
 "X-Generator: Weblate 2.5-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "まだチェンジセットがありません"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "なし"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(閉鎖済み)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "空白を表示"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "空白を無視"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "diff コンテキストを %(num)s 行増やす"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "プルリクエストステータスを変更する権限がありません"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "プルリクエストの削除に成功しました"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "お探しのリビジョンはこのリポジトリにはありません"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr "クローズしたプルリクエストに関連するチェンジセットのステータスを変更することは許可されていません"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "リビジョンを選択"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "リポジトリの最新のリビジョン(tip)に移動"
+
+#: kallithea/controllers/compare.py:72
+#, fuzzy
+#| msgid "Cannot compare repositories without using common ancestor"
+msgid "Cannot compare repositories of different types"
+msgstr "共通の祖先を持たないのでリポジトリを比較できません"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "共通の祖先を持たないのでリポジトリを比較できません"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "応答がありません"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "不明なエラー"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr "形式が間違っているため、サーバーはリクエストを処理できませんでした。"
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"形式が間違っているため、サーバーはリクエストを処理できませんでした。"
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "リソースにアクセスする権限がありません"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "このページを閲覧する権限がありません"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "リソースが見つかりません"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
-msgstr "サーバーが不正な状態になったため、リクエストに答えることができませんでした。"
-
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "%s リポジトリでの変更"
-
-#: kallithea/controllers/feed.py:56
+msgstr ""
+"サーバーが不正な状態になったため、リクエストに答えることができませんでし"
+"た。"
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s が %s にコミット"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "チェンジセットが大きすぎるため、省略しました..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s フィード"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "チェンジセットが大きすぎるため、省略しました..."
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s が %s にコミット"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "%s リポジトリでの変更"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "新しいファイルを追加"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "まだファイルがありません。 %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, fuzzy, python-format
 msgid "%s at %s"
 msgstr "%s と %s の間"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "このリポジトリは %s によって %s にロックされました"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 #, fuzzy
-#| msgid "You can only delete files with revision being a valid branch "
 msgid "You can only delete files with revision being a valid branch"
 msgstr "有効なブランチ上のリビジョンからしかファイルを削除できません"
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Kallithea経由で %s を削除"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "%s ファイルの削除に成功しました"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "コミット中にエラーが発生しました"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 #, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
 msgid "You can only edit files with revision being a valid branch"
 msgstr "有効なブランチを示すリビジョンでのみファイルを編集できます "
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Kallithea経由で %s を変更"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "変更点なし"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "%s へのコミットが成功しました"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Kallithea経由でファイルを追加"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "内容がありません"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "ファイル名がありません"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "場所には相対パスかつ .. を含まないパスを入力してください"
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "ダウンロードは無効化されています"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "%s は未知のリビジョンです"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "空のリポジトリ"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "未知のアーカイブ種別です"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "チェンジセット"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "ブランチ"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "タグ"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "リポジトリ %s のフォーク中にエラーが発生しました"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "グループ"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "リポジトリ"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "ブランチ"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "閉鎖済みブランチ"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "タグ"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "ブックマーク"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "公開ジャーナル"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "ジャーナル"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "キャプチャが一致しません"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Kallitheaへの登録を受け付けました"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "%sへの登録を受け付けました"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "パスワードリセットの確認コードが送信されました"
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr "無効なパスワードリセットトークン"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "パスワードを更新しました"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (閉鎖済み)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "チェンジセット"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "スペシャル"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "相手のブランチ"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "ブックマーク"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "プルリクエスト作成中にエラーが発生しました: %s"
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "説明がありません"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "新しいプルリクエストの作成に成功しました"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "プルリクエストの作成中にエラーが発生しました"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "新しいプルリクエストの作成に成功しました"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "プルリクエストレビュアー"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "%s で置き換えられたのでクローズします。"
-
-#: kallithea/controllers/pullrequests.py:470
-#, fuzzy
-msgid "Pull request update created"
-msgstr "プルリクエストレビュアー"
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "説明がありません"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "プルリクエストを更新しました"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "プルリクエストの削除に成功しました"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
-msgstr "このプルリクエストはすでにクローズされていて、更新することはできません。"
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
+msgstr ""
+"このプルリクエストはすでにクローズされていて、更新することはできません。"
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
 msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
 
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "ノート: ブランチ%sには別のヘッド%sがあります。"
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
 msgstr "Gitのプルリクエストはまだ更新をサポートしていません。"
 
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr "プルリクエストステータスを変更する権限がありません"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "クローズ。"
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "プルリクエストを更新するためのチェンジセットが見つかりません。"
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "無効な検索クエリーです。\\\"で囲んで下さい。"
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "検索するためのインデックスがありません。whooshでインデックスを作成して下さい"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "検索を実行する際にエラーが発生しました。"
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "まだデータの準備ができていません"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "このリポジトリの統計は無効化されています"
 
@@ -476,149 +502,151 @@
 msgid "error occurred during update of auth settings"
 msgstr "認証設定の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "デフォルト設定の更新に成功しました"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "デフォルト設定の更新中にエラーが発生しました"
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "永久"
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr "永久"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 分"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 分"
-
-#: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
 msgid "1 hour"
 msgstr "1 時間"
 
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 日"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 ヶ月"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "有効期間"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "gist の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "gist %s を削除しました"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
 msgstr "変更しない"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr "Gist の内容を更新しました"
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr "Gist データを更新しました"
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Gist %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr "このユーザーはアプリケーション全体で非常に重要なので編集できません"
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "アカウントの更新に成功しました"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "ユーザー %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "パスワードの更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "ユーザーにメールアドレス %s を追加しました"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "メールの保存時にエラーが発生しました"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "ユーザーからメールアドレスを削除しました"
 
-#: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
 msgid "API key successfully created"
 msgstr "APIキーの作成に成功しました"
 
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
 msgid "API key successfully reset"
 msgstr "APIキーのリセットに成功しました"
 
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "APIキーの削除に成功しました"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "読込"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "書込"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -626,1782 +654,1315 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "管理"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "無効"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "手動でアカウントをアクティベートする"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "自動でアカウントをアクティベートする"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "外部アカウントを手動でアクティベートする"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "外部アカウントを自動でアクティベートする"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "無効"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "手動でアカウントをアクティベートする"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "自動でアカウントをアクティベートする"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "外部アカウントを手動でアクティベートする"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "外部アカウントを自動でアクティベートする"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "有効"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr "全般の権限の更新に成功しました"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "権限の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "リポジトリグループ %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "リポジトリグループ %s を作成しました"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "リポジトリグループ %s を更新しました"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "リポジトリグループ %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "このグループは %s 個のリポジトリを含んでいるため削除できません"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "このグループは %s 個のサブグループを含んでいるため削除できません"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "リポジトリグループ %s を削除しました"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "リポジトリグループ %s の削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "自分自身の管理者としての権限を取り消すことはできません"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "リポジトリグループ権限を更新しました"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "権限の取消中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "リポジトリ %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "リポジトリ %s を %s から作成しました"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "リポジトリ %s を %s としてフォークしました"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "リポジトリ %s を作成しました"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "リポジトリ %s の更新に成功しました"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "リポジトリ %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "%s 個のフォークを切り離しました"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "%s 個のフォークを削除しました"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "リポジトリ %s を削除しました"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
-msgstr "フォークしたリポジトリが存在するため、 リポジトリ %s は削除できません"
-
-#: kallithea/controllers/admin/repos.py:326
+msgstr ""
+"フォークしたリポジトリが存在するため、 リポジトリ %s は削除できません"
+
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "%s の削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "リポジトリ権限を更新しました"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
 msgstr "フィールドの作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "フィールドの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- フォークではありません --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "公開ジャーナルでのリポジトリの可視性を更新しました"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr "このリポジトリの公開ジャーナルの設定中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:512
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "ありません"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "%s リポジトリを %s のフォークとする"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "操作中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "リポジトリがロックされました"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "リポジトリのロックが解除されました"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "アンロック中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "キャッシュの無効化に成功しました"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "キャッシュの無効化中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "リモートから取得"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "リモートから取得中にエラーが発生しました"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "リポジトリステートの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "VCS設定を更新しました"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
-msgstr "\"hgsubversion\"ライブラリが見つからないため、hgsubversionサポートを有効に出来ません"
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+msgstr ""
+"\"hgsubversion\"ライブラリが見つからないため、hgsubversionサポートを有効に"
+"出来ません"
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr "アプリケーション設定の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr "リポジトリの再スキャンに成功しました。 追加: %s 削除: %s。"
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Invalidate cache for all repositories"
 msgid "Invalidated %s repositories"
 msgstr "すべてのリポジトリのキャッシュを無効化する"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "アプリケーション設定を更新しました"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "表示設定を更新しました"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr "表示設定の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr "メールアドレスを入力してください"
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr "メール送信タスクを作成しました"
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "まだデータの準備ができていません"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "新しいフックを追加しました"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "フックを更新しました"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "フックの作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Whooshの再インデックスタスクを予定に入れました"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "ユーザーグループ %s を作成しました"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "ユーザーグループ %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "ユーザーグループ %s を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "ユーザーグループ %s の更新中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "ユーザーグループの削除に成功しました"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr "ユーザーグループの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "対象に同じ物を選ぶことはできません"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "ユーザーグループ権限を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "権限を更新しました"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "権限の保存時にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "ユーザー %s を作成しました"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "ユーザー %s の作成中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "ユーザーの更新に成功しました"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "ユーザーの削除に成功しました"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "ユーザーの削除中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr "デフォルト ユーザーを編集できません"
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "ユーザーホワイトリストにIP %s を追加しました"
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "IPアドレスの保存中にエラーが発生しました"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr "ユーザーホワイトリストからIPアドレスを削除しました"
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IPアドレス %s は許可されません"
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+"このアクションを実行するためには登録済みのユーザーである必要があります"
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr "このページを閲覧するためにはサインインが必要です"
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr "APIキーが無効です"
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
 msgstr ""
 
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr "このアクションを実行するためには登録済みのユーザーである必要があります"
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr "このページを閲覧するためにはサインインが必要です"
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr "ファイルシステム内にリポジトリが見つかりません"
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "リビジョンが見つかりません"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "バイナリファイル"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr "チェンジセットが大きすぎるため省略しました。差分を表示する場合は差分メニューを使用してください"
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+"チェンジセットが大きすぎるため省略しました。差分を表示する場合は差分メ"
+"ニューを使用してください"
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "検出された変更はありません"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "削除されたブランチ: %s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "作成したタグ: %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "リビジョンが見つかりません"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "%s から %s までのすべてのチェンジセットを表示"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr "比較ビュー"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "と"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s 以上"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "リビジョン"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr "フォーク名 %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr "プルリクエスト #%s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "リポジトリを[削除]"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "リポジトリを[作成]"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "フォークしてリポジトリを[作成]"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "リポジトリを[フォーク]"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "リポジトリを[更新]"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "リポジトリからアーカイブを[ダウンロード]"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "リポジトリを[削除]"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "ユーザーを[作成]"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "ユーザーを[更新]"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "ユーザーグループを[作成]"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "ユーザーグループを[更新]"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "リポジトリのリビジョンに[コメント]"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "プルリクエストに[コメント]"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "プルリクエストを[クローズ]"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[プッシュ]"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "リポジトリに[Kallithea経由でコミット]"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "リポジトリに[リモートからプル]"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[プル]"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "リポジトリの[フォローを開始]"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "リポジトリの[フォローを停止]"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " と %s 以上"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "ファイルはありません"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "新しいファイル"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "変更"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "削除"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "リネーム"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
 "the filesystem please run the application again in order to rescan "
 "repositories"
 msgstr ""
-"%s "
-"リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプリケーションを再起動してください"
-
-#: kallithea/lib/utils2.py:415
+"%s リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作"
+"られたか名前が変更されたためです。リポジトリをもう一度チェックするためにア"
+"プリケーションを再起動してください"
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d 年"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d ヶ月"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d 日"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d 時間"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d 分"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d 秒"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "%s 以内"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s 前"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "%s と %s の間"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s と %s 前"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "たったいま"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "リポジトリへのアクセス権限無し"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "リポジトリに読込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "リポジトリに書込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "リポジトリに管理権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "リポジトリグループへのアクセス権限なし"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "リポジトリグループに読込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "リポジトリグループに書込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "リポジトリグループに管理権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Kallithea 管理者"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "リポジトリの作成を有効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "リポジトリの作成を有効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "リポジトリのフォークを無効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "リポジトリのフォークを有効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "新規登録を無効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Kallitheaに登録した新しいユーザーを手動でアクティベートする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Kallitheaに登録した新しいユーザーを自動でアクティベートする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "未レビュー"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "承認"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "却下"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "レビュー中"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "top level"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "リポジトリグループへのアクセス権限なし"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "リポジトリグループに読込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "リポジトリグループに書込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "リポジトリグループに管理権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "ユーザーグループへのアクセス権限なし"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "ユーザーグループに読込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "ユーザーグループに書込権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "ユーザーグループに管理権限でアクセス"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "リポジトリグループの作成を無効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "リポジトリグループの作成を有効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "ユーザーグループの作成を無効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "ユーザーグループの作成を有効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "新規登録を無効にする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "ユーザーの新規登録時に手動でアカウントをアクティベートする"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成が有効です"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr "リポジトリグループの書き込みパーミッションを使ったリポジトリ作成は無効です"
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "%s 行目"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Mention]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "top level"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Kallithea 管理者"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr "デフォルトユーザーは新しいリポジトリにアクセスできません"
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
-msgstr "デフォルトユーザーは新しいリポジトリに読み取りアクセスする権限があります"
-
-#: kallithea/model/db.py:1670
+msgstr ""
+"デフォルトユーザーは新しいリポジトリに読み取りアクセスする権限があります"
+
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
-msgstr "デフォルトユーザーは新しいリポジトリに書き込みアクセスする権限があります"
-
-#: kallithea/model/db.py:1671
+msgstr ""
+"デフォルトユーザーは新しいリポジトリに書き込みアクセスする権限があります"
+
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr "管理者のみがリポジトリのグループを作成できます"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr "非管理者がリポジトリのグループを作成できます"
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr "管理者だけがユーザー グループを作成することができます"
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr "非管理者ユーザーがグループを作成することができます"
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr "管理者だけがトップレベルにリポジトリを作成することができます"
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr "非管理者がトップレベルにリポジトリを作成することができます"
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+"リポジトリグループの書き込みパーミッションを使ったリポジトリ作成が有効です"
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+"リポジトリグループの書き込みパーミッションを使ったリポジトリ作成は無効です"
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr "管理者のみがリポジトリをフォークすることができます"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 #, fuzzy
-#| msgid "Non-admins can can fork repositories"
 msgid "Non-admins can fork repositories"
 msgstr "非管理者がリポジトリをフォークすることができます"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "新規登録を無効にする"
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr "ユーザーの新規登録時に手動でアカウントをアクティベートする"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする"
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr "未レビュー"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr "レビュー中"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "承認"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "承認"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "ログイン名を入力してください"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "%(min)i 文字以上必要です"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "パスワードを入力してください"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "%(min)i 文字以上必要です"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr "数字だけの名前は使えません"
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s がチェンジセットにコメント %(age)s"
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s がメッセージを送信 %(age)s"
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s がにあなたにメンション %(age)s"
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s がKallitheaに登録 %(age)s"
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s が新しいプルリクエストを作成 %(age)s"
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s がプルリクエストにコメント %(age)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s がチェンジセットにコメント %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s がメッセージを送信 %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s がにあなたにメンション %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s がKallitheaに登録 %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s が新しいプルリクエストを作成 %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s がプルリクエストにコメント %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, fuzzy, python-format
+#| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "プルリクエストに[コメント]"
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "新しいユーザー %(new_username)s が登録されました"
 
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "%(user)s がプリリクエスト #%(pr_id)s: %(pr_title)s のレビューを求めています"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "プルリクエストに[コメント]"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "クローズ"
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr "%(user)s がプリリクエスト #%(pr_nice_id)s: %(pr_title)s のレビューを求めています"
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s がプリリクエスト #%(pr_nice_id)s: %(pr_title)s のレビューを求めて"
+"います"
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "プルリクエスト作成中にエラーが発生しました: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "このプルリクエストを削除してもよろしいですか?"
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "最新のtip"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "新規ユーザー登録"
 
-#: kallithea/model/user.py:256
+#: kallithea/model/user.py:253
 #, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr "このユーザーを削除できません。このユーザーはアプリケーションにとって必要不可欠です。"
-
-#: kallithea/model/user.py:261
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+"このユーザーを削除できません。このユーザーはアプリケーションにとって必要不"
+"可欠です。"
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
-msgstr "ユーザー \"%s\" はまだ %s 個のリポジトリの所有者のため削除することはできません。リポジトリの所有者を変更するか削除してください: %s"
-
-#: kallithea/model/user.py:266
+msgstr ""
+"ユーザー \"%s\" はまだ %s 個のリポジトリの所有者のため削除することはできま"
+"せん。リポジトリの所有者を変更するか削除してください: %s"
+
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
-"ユーザー \"%s\" はまだ %s "
-"個のリポジトリグループの所有者のため削除することはできません。リポジトリグループの所有者を変更するか削除してください: %s"
-
-#: kallithea/model/user.py:273
+"ユーザー \"%s\" はまだ %s 個のリポジトリグループの所有者のため削除すること"
+"はできません。リポジトリグループの所有者を変更するか削除してください: %s"
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
-"ユーザー \"%s\" はまだ %s "
-"個のユーザーグループの所有者のため削除することはできません。ユーザーグループの所有者を変更するか削除してください。 %s"
-
-#: kallithea/model/user.py:360
+"ユーザー \"%s\" はまだ %s 個のユーザーグループの所有者のため削除することは"
+"できません。ユーザーグループの所有者を変更するか削除してください。 %s"
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "パスワードリセットのリンク"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr "パスワードの再設定通知"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "空のリストにはできません"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "ユーザー名 \"%(username)s\" はすでに使われています"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "ユーザー名 %(username)s は使用できません"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr "ユーザー名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットまたはアンダースコア(_)から始まる必要があります"
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+"ユーザー名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)し"
+"か使えません。また、アルファベットまたはアンダースコア(_)から始まる必要が"
+"あります"
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr "入力が正しくありません"
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "ユーザー名 %(username)s は不正です"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "不正なユーザーグループ名です"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "ユーザーグループ \"%(usergroup)s\" はすでに存在します"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
-msgstr "ユーザーグループ名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットから始まる必要があります"
-
-#: kallithea/model/validators.py:193
+msgstr ""
+"ユーザーグループ名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッ"
+"シュ(-)しか使えません。また、アルファベットから始まる必要があります"
+
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "このグループは親にできません"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "グループ \"%(group_name)s\" はすでに存在します"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "グループ名 \"%(group_name)s\" を持つリポジトリはすでに存在します"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "パスワードに利用出来ない文字列(non-ascii)です"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr "古いpasswordが間違っています"
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "パスワードが一致しません"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr "ユーザー名とパスワードの組み合わせが無効です"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "トークンが一致しません"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "リポジトリ名 %(repo)s は許可されていません"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "リポジトリ %(repo)s はすでに存在します"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
-msgstr "リポジトリ \"%(repo)s\" は グループ \"%(group)s\" にすでに存在します"
-
-#: kallithea/model/validators.py:350
+msgstr ""
+"リポジトリ \"%(repo)s\" は グループ \"%(group)s\" にすでに存在します"
+
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "リポジトリグループ名 \"%(repo)s\" はすでに存在します"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr "無効なリポジトリのURL"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "フォークは親と同じ種別の必要があります"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "このグループにリポジトリを作成する権限がありません"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "ルートにリポジトリを作成する権限がありません"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr "この場所にグループを作成する権限がありません"
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr "ユーザー名かユーザーグループが不正です"
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "不正なパスです"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr "このメールアドレスはすでに取得されています"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "メールアドレス \"%(email)s\" がみつかりません"
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
-msgstr "LDAPのこのCNに対するログイン属性は必須です。 - これは \"ユーザー名\" と同じです"
-
-#: kallithea/model/validators.py:774
+msgstr ""
+"LDAPのこのCNに対するログイン属性は必須です。 - これは \"ユーザー名\" と同"
+"じです"
+
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "有効なIPv4かIPv6のアドレスを入力してください"
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr "ネットワークサイズ (bits) は0-32の範囲にする必要があります ( %(bits)r は不正です)"
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+"ネットワークサイズ (bits) は0-32の範囲にする必要があります ( %(bits)r は不"
+"正です)"
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
-msgstr "キー名にはアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)、数字が使えます"
-
-#: kallithea/model/validators.py:822
+msgstr ""
+"キー名にはアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)、数"
+"字が使えます"
+
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "ファイル名はディレクトリ内にすることはできません"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
-msgstr "%(loaded)s プラグインと %(next_to_load)s プラグインで同じ名前が使われています"
-
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+msgstr ""
+"%(loaded)s プラグインと %(next_to_load)s プラグインで同じ名前が使われてい"
+"ます"
+
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "ダッシュボード"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "クイックフィルタ..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "リポジトリ"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "リポジトリを追加"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "リポジトリグループを追加"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr "管理者権限をもっているため編集できます"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "リポジトリグループを編集"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "グループ名"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "リポジトリ"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "説明"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "名前"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "最後の変更点"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Tip"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "所有者"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "昇順で並び換え"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "降順で並び替え"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "リポジトリが見つかりません。"
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "データエラー"
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "読み込み中..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "ログイン"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "%s へログイン"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "ユーザー名"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "パスワード"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "次回から自動的にサインイン"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "パスワードを忘れた?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "アカウントを持っていない?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "サインイン"
 
@@ -2409,176 +1970,147 @@
 msgid "Password Reset"
 msgstr "パスワードのリセット"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr "%sのパスワードをリセット"
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr "パスワードのリセット"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "メールアドレス"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr "キャプチャ"
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 msgid "Send Password Reset Email"
 msgstr "パスワードリセットメールを送信"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
-msgstr "システムに登録されている場合、パスワードリセットのリンクを指定されたメールアドレスに送信します。"
-
-#: kallithea/templates/password_reset_confirmation.html:19
+msgstr ""
+"システムに登録されている場合、パスワードリセットのリンクを指定されたメール"
+"アドレスに送信します。"
+
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr "新しいパスワード"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr "新しいパスワードの確認"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "サインアップ"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr "%s へサインアップ"
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "パスワード再入力"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "名前"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "名字"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "メールアドレス"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "まだブランチがありません"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "閉鎖済みブランチ"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "まだタグがありません"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "まだブックマークがありません"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr "管理ジャーナル"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "ジャーナルフィルタ..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr "フィルター"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
 msgstr[0] "%s 個のエントリ"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "アクション"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "リポジトリ"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "日時"
 
@@ -2586,7 +2118,7 @@
 msgid "From IP"
 msgstr "アクセス元IPアドレス"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "まだアクションがありません"
 
@@ -2595,108 +2127,112 @@
 msgstr "認証設定"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "認証"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr "認証プラグイン"
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr "有効なプラグイン"
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
-msgstr "カンマ区切りのプラグインの一覧です。Kallitheaはプラグインの並び順でユーザー認証を試みます"
-
-#: kallithea/templates/admin/auth/auth_settings.html:34
+msgstr ""
+"カンマ区切りのプラグインの一覧です。Kallitheaはプラグインの並び順でユー"
+"ザー認証を試みます"
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr "有効な組み込みプラグイン"
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr "プラグイン"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "保存"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr "リポジトリのデフォルト設定"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "リポジトリの種別"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "非公開リポジトリ"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
-msgstr "非公開リポジトリはコラボレーターとして明示的に追加された人のみ閲覧できます。"
-
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+msgstr ""
+"非公開リポジトリはコラボレーターとして明示的に追加された人のみ閲覧できま"
+"す。"
+
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "統計を有効にする"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "概要ページの統計ウィンドウを有効にします。"
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "ダウンロードを有効にする"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "概要ページのダウンロードメニューを有効にします。"
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "ロックを有効にする"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "リポジトリのpullのロックを有効にします。"
 
@@ -2705,47 +2241,47 @@
 msgid "Edit Gist"
 msgstr "Gistを編集"
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
-"編集開始後にGistが更新されています。あなたの変更箇所をコピーしておき、 %(here)s "
-"をクリックして新しいバージョンを読み込みなおしてください。"
-
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+"編集開始後にGistが更新されています。あなたの変更箇所をコピーしておき、 "
+"%(here)s をクリックして新しいバージョンを読み込みなおしてください。"
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Gist の説明..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr "Gist 有効期間"
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "失効"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr "しない"
 
@@ -2754,7 +2290,8 @@
 msgstr "Gistを更新"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "キャンセル"
 
@@ -2777,16 +2314,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr "新しい Gist を作成"
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "作成日"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "まだgistがありません"
 
@@ -2795,45 +2332,47 @@
 msgid "New Gist"
 msgstr "Gistを新規作成"
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
+#: kallithea/templates/admin/gists/new.html:45
+#, fuzzy
+#| msgid "name this file..."
+msgid "Name this gist ..."
 msgstr "ファイルに名前をつける..."
 
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr "非公開 Gist を作成"
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr "公開 Gist を作成"
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "リセット"
 
@@ -2843,182 +2382,197 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
-#: kallithea/templates/admin/gists/show.html:37
+#: kallithea/templates/admin/gists/show.html:35
 msgid "Public Gist"
 msgstr "公開 Gist"
 
-#: kallithea/templates/admin/gists/show.html:39
+#: kallithea/templates/admin/gists/show.html:37
 msgid "Private Gist"
 msgstr "非公開 Gist"
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "削除"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr "このGistを削除してもよろしいですか?"
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "編集"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr "Raw形式で表示"
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "作成日"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "Raw形式で表示"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "アカウント"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "プロフィール"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr "メールアドレス"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr "APIキー"
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr "所有しているリポジトリ"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr "ウォッチ中のリポジトリ"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr "権限の表示"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr "ビルトイン"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "このAPIキーをリセットしてもよろしいですか?: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr "期限切れ"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "このAPIキーを削除してもよろしいですか?: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr "削除"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr "API キーが指定されていません"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr "新しいAPIキー"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "追加"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "プライマリ"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "このメールアドレスを削除してもよろしいですか? : %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr "追加のメールアドレスはありません。"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "新しいメールアドレス"
 
@@ -3026,105 +2580,69 @@
 msgid "Change Your Account Password"
 msgstr "パスワードを変更してください"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr "現在のパスワード"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "新しいパスワード"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr "新しいパスワードの確認"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
-msgstr "アバターを変更できます : "
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr "現在の IP アドレス"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change avatar at"
+msgid "Change %s avatar at"
+msgstr "アバターを変更:"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "メールアドレス:"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr "アバターは無効です"
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr "メールアドレスがありません。更新してください。"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr "現在の IP アドレス"
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "あなたが所有者のリポジトリ"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "レコードが見つかりません"
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "名前"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr "あなたがウォッチしているリポジトリ"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "通知"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "すべて"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "コメント"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "プルリクエスト"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr "すべて既読としてマーク"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "まだ通知がありません"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr "通知を表示"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "通知"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr "デフォルトの権限"
 
@@ -3138,204 +2656,226 @@
 msgid "IP Whitelist"
 msgstr "IPアドレスのホワイトリスト"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "匿名アクセス"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "匿名アクセス"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, fuzzy, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
-msgstr "ログインしなくても Kallithea にアクセスできるようにします。匿名ユーザーは %s ユーザーパーミッションを使います"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+msgstr ""
+"ログインしなくても Kallithea にアクセスできるようにします。匿名ユーザーは "
+"%s ユーザーパーミッションを使います"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
-msgstr "選択したパーミッションで、各リポジトリのデフォルトパーミッションをリセットします。各リポジトリの既存のカスタムデフォルトパーミッション設定は無くなるので注意してください"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+msgstr ""
+"選択したパーミッションで、各リポジトリのデフォルトパーミッションをリセット"
+"します。各リポジトリの既存のカスタムデフォルトパーミッション設定は無くなる"
+"ので注意してください"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "既存のリポジトリを import しますか?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "リポジトリグループ"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "リポジトリグループ"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
-msgstr "選択したパーミッションで、各リポジトリグループのデフォルトパーミッションをリセットします。各リポジトリグループの既存のカスタムデフォルトパーミッション設定は無くなるので注意してください"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+msgstr ""
+"選択したパーミッションで、各リポジトリグループのデフォルトパーミッションを"
+"リセットします。各リポジトリグループの既存のカスタムデフォルトパーミッショ"
+"ン設定は無くなるので注意してください"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "既存のリポジトリを import しますか?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 #, fuzzy
 msgid "Permissions for the Default user on new repository groups."
-msgstr "親のリポジトリグループにセットされているパーミッションをコピーします。"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+msgstr ""
+"親のリポジトリグループにセットされているパーミッションをコピーします。"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "ユーザーグループ"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 #, fuzzy
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
-msgstr "選択したパーミッションで、各ユーザーグループのデフォルトパーミッションをリセットします。各ユーザーグループの既存のカスタムデフォルトパーミッション設定は無くなるので注意してください"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+msgstr ""
+"選択したパーミッションで、各ユーザーグループのデフォルトパーミッションをリ"
+"セットします。各ユーザーグループの既存のカスタムデフォルトパーミッション設"
+"定は無くなるので注意してください"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr "トップレベルリポジトリの作成"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr "グループ書き込み権限でのリポジトリ作成"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 #, fuzzy
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
-msgstr "リポジトリグループに書き込みパーミッションを付与すると、グループ内にリポジトリを作成できるようになります"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+msgstr ""
+"リポジトリグループに書き込みパーミッションを付与すると、グループ内にリポジ"
+"トリを作成できるようになります"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "ユーザーグループ作成"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "リポジトリのフォーク"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "新規登録"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "外部認証アカウントのアクティベート"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "このIPアドレスを削除してもよろしいですか? : %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr "すべてのIPアドレスが許可されています。"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "新しいIPアドレス"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "リポジトリグループ"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "グループ名"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "親グループ"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr "親グループのパーミッションをコピー"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
-msgstr "親のリポジトリグループにセットされているパーミッションをコピーします。"
+msgstr ""
+"親のリポジトリグループにセットされているパーミッションをコピーします。"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
 #, python-format
 msgid "%s Repository Group Settings"
 msgstr "%s リポジトリグループ設定"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr "子グループを追加"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "設定"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "高度な設定"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "権限設定"
@@ -3360,12 +2900,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "作成日"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3375,16 +2915,38 @@
 msgid "Delete this repository group"
 msgstr "このリポジトリグループを削除"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "disabled"
+msgid "Visible"
+msgstr "無効"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "Add Repository"
+msgid "Add repos"
+msgstr "リポジトリを追加"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#, fuzzy
+#| msgid "Add user group"
+msgid "Add/Edit groups"
+msgstr "ユーザーグループを追加"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr "ユーザー/ユーザーグループ"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3393,105 +2955,106 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr "取消"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "新規追加"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr "子要素にも適用"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr "両方"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
-msgstr "このグループに属する全ての子要素のパーミッションを設定または無効化します。選択されていれば、非公開でないリポジトリや他のリポジトリも対象に含みます。"
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+msgstr ""
+"このグループに属する全ての子要素のパーミッションを設定または無効化します。"
+"選択されていれば、非公開でないリポジトリや他のリポジトリも対象に含みます。"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr "グループに対してpullのロックを有効にします。このオプションはグループに含まれる全てのグループとリポジトリに適用されます。"
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+"グループに対してpullのロックを有効にします。このオプションはグループに含ま"
+"れる全てのグループとリポジトリに適用されます。"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr "このグループを削除"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr "このグループを削除してもよろしいですか?: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr "%s リポジトリグループダッシュボード"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "ホーム"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "と"
+#, fuzzy, python-format
+#| msgid "Repository Group: %s"
+msgid "Repository group %s"
+msgstr "リポジトリグループ: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr "リポジトリグループ管理"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr "トップレベルリポジトリ数"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr "リモートリポジトリをクローン"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr "短く要点を絞ってください。長い説明にはREADMEファイルを利用してください。"
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+"短く要点を絞ってください。長い説明にはREADMEファイルを利用してください。"
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "オプション:このリポジトリが属するグループを選択します"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "作成するリポジトリの種別を指定します"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "ランディングリビジョン"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
-msgstr "ファイルページ、ダウンロード、全文検索インデックス、READMEなどの生成に使うデフォルトのリビジョン"
+msgstr ""
+"ファイルページ、ダウンロード、全文検索インデックス、READMEなどの生成に使う"
+"デフォルトのリビジョン"
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
 #, fuzzy, python-format
@@ -3507,35 +3070,39 @@
 msgid ""
 "Repository \"%(repo_name)s\" is being created, you will be redirected "
 "when this process is finished.repo_name"
-msgstr "リポジトリ \"%(repo_name)s\" を作成中です。処理を完了したらリダイレクトします。"
+msgstr ""
+"リポジトリ \"%(repo_name)s\" を作成中です。処理を完了したらリダイレクトし"
+"ます。"
 
 #: kallithea/templates/admin/repos/repo_creating.html:39
 msgid ""
 "We're sorry but error occurred during this operation. Please check your "
 "Kallithea server logs, or contact administrator."
-msgstr "恐れいります。操作中にエラーが発生しました。 Kallithea サーバのログをチェックするか、管理者に問い合わせてください。"
+msgstr ""
+"恐れいります。操作中にエラーが発生しました。 Kallithea サーバのログを"
+"チェックするか、管理者に問い合わせてください。"
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
 #, python-format
 msgid "%s Repository Settings"
 msgstr "%s リポジトリ設定"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr "拡張フィールド"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr "キャッシュ"
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "リモート"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "統計"
 
@@ -3544,63 +3111,62 @@
 msgstr "Parent"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr "保存"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr "一覧から別のフォークとしてこのリポジトリを手動で設定します。"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr "公開ジャーナルでの可視性"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "公開ジャーナルから削除する"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr "公開ジャーナルへ追加"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
-msgstr "公開ジャーナルでは、このリポジトリに対して行った操作のすべてが公開されます"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+msgstr ""
+"公開ジャーナルでは、このリポジトリに対して行った操作のすべてが公開されます"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr "ロック"
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr "このリポジトリのロックを解除しますか?"
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr "リポジトリのロックを解除"
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr "このリポジトリのロックを解除しますか?"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
-msgstr "リポジトリのロックを解除"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 msgid "Confirm to lock repository."
 msgstr "このリポジトリをロックしますか?"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "リポジトリをロック"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "リポジトリはロックされていません"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3608,31 +3174,31 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "このリポジトリを削除してもよろしいですか? : %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "このリポジトリを削除"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] "このリポジトリには %s 個のフォークがあります"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "フォークの切り離し"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "フォークも削除"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3643,105 +3209,109 @@
 msgid "Invalidate Repository Cache"
 msgstr "リポジトリのキャッシュを無効化"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr "リポジトリのキャッシュを無効化してもよろしいですか?"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
-msgstr "このリポジトリのキャッシュを手動で無効化します。リポジトリへの初回アクセス時に再びキャッシュされます。"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgstr ""
+"このリポジトリのキャッシュを手動で無効化します。リポジトリへの初回アクセス"
+"時に再びキャッシュされます。"
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 msgid "List of Cached Values"
 msgstr "キャッシュしている値の一覧"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "プレフィックス"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "キー"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "アクティブ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr "ラベル"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "このフィールドを削除してもよろしいですか? : %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "新しいフィールドのキー"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "新しいフィールドのラベル"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "ラベルを入力してください"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "新しいフィールドの説明"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "フィールドの説明を入力してください"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr "拡張フィールドは無効化されています"
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr "非公開リポジトリ"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "リポジトリを[フォーク]"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr "リモートリポジトリURL"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr "リモートリポジトリから変更を取り込む"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 msgid "Confirm to pull changes from remote repository."
 msgstr "リモートリポジトリから変更を取り込んでもよろしいですか?"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr "このリポジトリにリモートURLは設定されていません"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr "リポジトリID"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr "これは何?"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr "id を使ってURLを表現"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3750,37 +3320,50 @@
 "                               This is useful for CI systems, or any "
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
-"通常、リポジトリの名前を変更したり、別のグループに移動すると、リポジトリのURLが変わります。\n"
+"通常、リポジトリの名前を変更したり、別のグループに移動すると、リポジトリの"
+"URLが変わります。\n"
 "上のURLを使えば、常にリポジトリにアクセスできます。\n"
-"この機能は、CIを使っている場合や、3rd pirtyのサービス向けにURLを固定化したいときに便利です。"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+"この機能は、CIを使っている場合や、3rd pirtyのサービス向けにURLを固定化した"
+"いときに便利です。"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr "リモートリポジトリ"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr "リポジトリURL"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
-msgstr "オプション: リモートリポジトリのURLです。設定した場合、このURLから変更を取得することができます。"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+msgstr ""
+"オプション: リモートリポジトリのURLです。設定した場合、このURLから変更を取"
+"得することができます。"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
-msgstr "ファイルページ、ダウンロード、検索、READMEのデフォルトのリビジョンを指定します"
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+msgstr ""
+"ファイルページ、ダウンロード、検索、READMEのデフォルトのリビジョンを指定し"
+"ます"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+#, fuzzy
+#| msgid "Type name of reviewer to add"
+msgid "Type name of user"
+msgstr "追加するレビュアーの名前を入力"
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "リポジトリの所有者を変更"
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
 msgid "Processed commits"
 msgstr "処理済みコミット数"
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 msgid "Processed progress"
 msgstr "処理状況"
 
@@ -3796,7 +3379,7 @@
 msgid "Repositories Administration"
 msgstr "リポジトリ管理"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr "状態"
 
@@ -3817,7 +3400,7 @@
 msgstr "表示"
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "フック"
 
@@ -3829,281 +3412,287 @@
 msgid "System Info"
 msgstr "システム情報"
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr "テストメールの送信"
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "送信"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "サイト名"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr "このKallitheaサービスのカスタムタイトルを設定します。"
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr "このKallitheaサービスのカスタムタイトルを設定します。"
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "HTTP認証レルム"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:37
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr "ReCaptcha 公開鍵"
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr "reCaptchaの公開鍵。"
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr "ReCaptcha 秘密鍵"
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
-msgstr "reCaptchaの秘密鍵。この値が設定されると登録時のキャプチャが有効になります。"
-
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+msgstr ""
+"reCaptchaの秘密鍵。この値が設定されると登録時のキャプチャが有効になりま"
+"す。"
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr "設定を保存"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr "組み込みのMercurialフック (編集不可)"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr "カスタムフック"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
-msgstr "フックを使うと、リポジトリへのプッシュやプルといった特定のイベントに合わせて、何らかのアクションを実行できます。フック機能では、Pythonの関数を呼び出したり、外部アプリケーションを起動したりできます。"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr "カスタムフック"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+msgstr ""
+"フックを使うと、リポジトリへのプッシュやプルといった特定のイベントに合わせ"
+"て、何らかのアクションを実行できます。フック機能では、Pythonの関数を呼び出"
+"したり、外部アプリケーションを起動したりできます。"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "フックの削除に失敗しました"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Rescan option"
+msgid "Rescan options"
 msgstr "再スキャンオプション"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr "見つからないリポジトリのレコードを削除"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "すべてのリポジトリのキャッシュを無効化する"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 #, fuzzy
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr "すべてのリポジトリのキャッシュを無効化する"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr "Gitフックをインストール"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
+#: kallithea/templates/admin/settings/settings_mapping.html:28
 msgid ""
 "Verify if Kallithea's Git hooks are installed for each repository. "
 "Current hooks will be updated to the latest version."
-msgstr "各リポジトリに Kallitheas の Gitフックがインストールされているか確認してください。現在のフックは最新版に更新されます"
-
-#: kallithea/templates/admin/settings/settings_mapping.html:28
+msgstr ""
+"各リポジトリに Kallitheas の Gitフックがインストールされているか確認してく"
+"ださい。現在のフックは最新版に更新されます"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr "既存のGitフックを上書きする"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
+#: kallithea/templates/admin/settings/settings_mapping.html:35
 msgid ""
 "If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
 msgstr ""
-"GitフックをインストールするとKallitheaから設定されたものであっても既存のフックは全て上書きされます。警告: "
-"この操作はあなたが手動で配置したGitのカスタムフックを全て破壊します!"
-
-#: kallithea/templates/admin/settings/settings_mapping.html:35
+"GitフックをインストールするとKallitheaから設定されたものであっても既存の"
+"フックは全て上書きされます。警告: この操作はあなたが手動で配置したGitのカ"
+"スタムフックを全て破壊します!"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr "リポジトリを再スキャン"
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "インデックス作成時の設定"
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr "一度削除してから再度インデックスを作成"
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr "一度削除してから再度インデックスを作成"
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
-msgstr "このオプションを使うと、全文検索の機能が正しく発揮されるよう、 Kallithea 中の全てのファイルのインデックスを再生成します。"
-
-#: kallithea/templates/admin/settings/settings_search.html:21
+msgstr ""
+"このオプションを使うと、全文検索の機能が正しく発揮されるよう、 Kallithea "
+"中の全てのファイルのインデックスを再生成します。"
+
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "再インデックス"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr "更新を確認中..."
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr "Kallithea バージョン"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr "更新を確認"
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr "Kallithea の設定ファイル"
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr "Python バージョン"
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
-msgstr "プラットフォーム"
+msgid "Check for updates"
+msgstr "更新を確認"
 
 #: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr "Kallithea の設定ファイル"
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr "Python バージョン"
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr "プラットフォーム"
+
+#: kallithea/templates/admin/settings/settings_system.html:11
 msgid "Git version"
 msgstr "Git バージョン"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr "Git パス"
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr "更新情報のエンドポイント"
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr "ノート: サーバーがこのURLにアクセスできることを確認して下さい"
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr "更新を確認中..."
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr "Python パッケージ"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Web"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "VCSの操作にSSLを必須とする"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr "プッシュ、プル時にSSLを要求します。SSLでない場合はHTTP Error 406: Not Acceptableを返します。"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "プッシュ後にリポジトリのサイズを表示する"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "ユーザーのプッシュコマンドを記録する"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "ユーザーのプルコマンドを記録する"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "プッシュ後にリポジトリを更新する (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr "Mercurialエクステンション"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "largefilesエクステンションを有効にする"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "hgsubversionエクステンションを有効にする"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
-"hgsubversion "
-"ライブラリのインストールが必要です。リモートのSVNリポジトリをクローンしてMercurialリポジトリに変換するすることが可能です。"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+"hgsubversion ライブラリのインストールが必要です。リモートのSVNリポジトリを"
+"クローンしてMercurialリポジトリに変換するすることが可能です。"
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 msgid "Location of repositories"
 msgstr "リポジトリの場所"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr "アンロックする。この設定を有効にするためにはKallitheaの再起動が必要です。"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+"アンロックする。この設定を有効にするためにはKallitheaの再起動が必要です。"
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
-msgstr "リポジトリを保存するファイルシステム上の場所。この値を変更した場合、サーバーの再起動とリポジトリフォルダの再スキャンが必要です。"
-
-#: kallithea/templates/admin/settings/settings_visual.html:8
+msgstr ""
+"リポジトリを保存するファイルシステム上の場所。この値を変更した場合、サー"
+"バーの再起動とリポジトリフォルダの再スキャンが必要です。"
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "一般"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "リポジトリの拡張フィールドを使用する"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr "追加のカスタムフィールドをリポジトリ毎に保存することを許可します。"
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Kallitheaのバージョンを表示する"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr "フッターに表示されるKallitheaのバージョン番号の表示、非表示を設定します。"
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr "Gravatorsを利用する"
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+"フッターに表示されるKallitheaのバージョン番号の表示、非表示を設定します。"
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 #, fuzzy
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
@@ -4120,112 +3709,140 @@
 "                                                        {netloc}    "
 "network location/server host of running Kallithea server"
 msgstr ""
-"Gravatar URL を設定すると、外部のアバターサーバーアプリケーションを使用します。\n"
+"Gravatar URL を設定すると、外部のアバターサーバーアプリケーションを使用し"
+"ます。\n"
 "必要に応じて、 URL に以下の変数を使ってください。\n"
-"{scheme} Kallithea サーバからリクエストを送信するときに使うスキーム。 'http' または 'https'\n"
+"{scheme} Kallithea サーバからリクエストを送信するときに使うスキーム。 "
+"'http' または 'https'\n"
 "{email} ユーザーのメールアドレス\n"
-"{md5email} ユーザーのメールアドレスの md5 ハッシュ値 (gravatar.com で使っています)\n"
+"{md5email} ユーザーのメールアドレスの md5 ハッシュ値 (gravatar.com で使っ"
+"ています)\n"
 "{size} サーバーアプリケーションに要求する画像のサイズ\n"
 "{netloc} Kallithea サーバーのアドレスまたはホスト名"
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr "クローンURL"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
+#, fuzzy
+#| msgid ""
+#| "Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+#| "{repo}'.\n"
+#| "The following variables are available:\n"
+#| "{scheme} 'http' or 'https' sent from running Kallithea server,\n"
+#| "{user}   current user username,\n"
+#| "{netloc} network location/server host of running Kallithea server,\n"
+#| "{repo}   full repository name,\n"
+#| "{repoid} ID of repository, can be used to contruct clone-by-id"
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
 msgstr ""
-"クローン URL のスキーマは、 '{scheme}://{user}@{netloc}/{repo}' "
-"のような形式にします。使える変数は下記の通りです:\n"
+"クローン URL のスキーマは、 '{scheme}://{user}@{netloc}/{repo}' のような形"
+"式にします。使える変数は下記の通りです:\n"
 "                                                        {scheme} "
-"Kallithea サーバからリクエストを送信するときに使うスキーム。 'http' または 'https'\n"
-"                                                        {user}   "
-"現在のユーザーのユーザー名\n"
+"Kallithea サーバからリクエストを送信するときに使うスキーム。 'http' また"
+"は 'https'\n"
+"                                                        {user}   現在の"
+"ユーザーのユーザー名\n"
 "                                                        {netloc} "
 "Kallithea サーバーのアドレスまたはホスト名\n"
-"                                                        {repo}   "
-"リポジトリの完全な名前\n"
-"                                                        {repoid} リポジトリの "
-"ID。 clone-by-id に使います。"
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "ダッシュボードの項目"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                        {repo}   リポジト"
+"リの完全な名前\n"
+"                                                        {repoid} リポジト"
+"リの ID。 clone-by-id に使います。"
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "リポジトリサイズ"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+#, fuzzy
+#| msgid ""
+#| "Number of items displayed in the admin pages grids before pagination "
+#| "is shown."
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
-msgstr "メインページダッシュボードで1ページに表示する要素数。"
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr "管理ページで、ページ分割しないでグリッドに表示する項目の数"
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+#, fuzzy
+#| msgid "Admin pages items"
+msgid "Admin page size"
+msgstr "管理ページの項目"
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr "管理ページの項目"
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr "管理ページで、ページ分割しないでグリッドに表示する項目の数"
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "アイコン"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "公開リポジトリのアイコンを表示する"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "非公開リポジトリのアイコンを表示する"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 msgid "Show public/private icons next to repository names."
 msgstr "リポジトリ名の隣に公開/非公開アイコンを表示します。"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr "メタタグ"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr "次のメタタグを変換する:"
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr "リポジトリの説明のメタタグを解析して色つきのタグに変換します。"
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr "次のメタタグを変換する:"
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "ユーザーグループを追加"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr "ユーザーグループ"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr "ユーザーグループを追加"
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr "このユーザーグループの簡潔な説明を書いてください。"
 
@@ -4244,13 +3861,13 @@
 msgstr "ユーサーグループ: %s"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "メンバー"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr "このユーザーグループを削除してもよろしいですか?: %s"
@@ -4259,15 +3876,15 @@
 msgid "Delete this user group"
 msgstr "このユーザーグループを削除"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "まだメンバーがいません"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "グループメンバーを選ぶ"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "有効なメンバー"
 
@@ -4281,17 +3898,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "ユーザー"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr "ユーザーを追加"
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "パスワード再入力"
 
@@ -4310,12 +3927,12 @@
 msgstr "ユーザー: %s"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr "アカウントのソース"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr "最終ログイン日時"
 
@@ -4324,7 +3941,7 @@
 msgstr "グループのメンバー数"
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "このユーザーを削除してもよろしいですか? : %s"
@@ -4333,24 +3950,16 @@
 msgid "Delete this user"
 msgstr "このユーザーを削除"
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr "%s から継承"
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr "アバターを変更:"
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr "メールアドレスがありません。このユーザーのメールアドレスを更新してください。"
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr "アカウントのソースでの名前"
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "新しいパスワード 再入力"
 
@@ -4358,222 +3967,228 @@
 msgid "Users Administration"
 msgstr "ユーザー管理"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr "認証タイプ"
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "サーバーインスタンス: %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr "サポート"
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Mercurialリポジトリ"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Gitリポジトリ"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr "フォークを作成"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "要約"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "履歴"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "ファイル"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "ブランチの切り替え"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr "%s のプルリクエストを表示"
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "プルリクエスト"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "オプション"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr "フォークと比較"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "比較"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "検索"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr "アンロック"
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr "ロック"
 
-#: kallithea/templates/base/base.html:173
+#: kallithea/templates/base/base.html:174
 msgid "Follow"
 msgstr "フォロー"
 
-#: kallithea/templates/base/base.html:174
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr "アンフォロー"
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "フォーク"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr "プルリクエストを作成"
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr "%s のプルリクエストを表示"
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "ブランチの切り替え"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr "一致するものが見つかりません"
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "最近の活動を表示"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "公開ジャーナル"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "公開 gists を表示"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gists"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr "すべての公開 Gists"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr "公開 Gists"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr "非公開 Gists"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "リポジトリから検索"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr "私のプルリクエスト"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr "ログインしていません"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr "あなたのアカウントにログイン"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "パスワードを忘れた?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "アカウントを持っていない?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "ログアウト"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr "一致するものが見つかりません"
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
-msgstr "キーボードショートカット"
+msgid "Parent rev."
+msgstr "親リビジョン"
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr "サイト全体"
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr "子リビジョン"
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr "デフォルト権限を継承"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "リポジトリを作成する"
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+"ユーザーにリポジトリ作成を許可する場合はこのオプションを選んでください"
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr "ユーザーにリポジトリ作成を許可する場合はこのオプションを選んでください"
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr "ユーザーグループを作成"
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
-msgstr "ユーザーにユーザーグループの作成を許可する場合はこのオプションを選んでください"
-
-#: kallithea/templates/base/default_perms_box.html:52
+msgstr ""
+"ユーザーにユーザーグループの作成を許可する場合はこのオプションを選んでくだ"
+"さい"
+
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "リポジトリをフォークする"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
-msgstr "ユーザーにリポジトリのフォークを許可する場合はこのオプションを選んでください"
+msgstr ""
+"ユーザーにリポジトリのフォークを許可する場合はこのオプションを選んでくださ"
+"い"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "表示"
 
@@ -4582,229 +4197,173 @@
 msgstr "まだ権限設定がありません"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "権限"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "権限を編集"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr "権限が設定されていません"
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr "送信中..."
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "ダウンロードを有効にする"
+
+#: kallithea/templates/base/root.html:31
 msgid "Add Another Comment"
 msgstr "別のコメントを追加"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "このリポジトリのフォローをやめる"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "このリポジトリのフォローする"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "グループ"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "メンバー"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "読み込み中..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "読み込み中..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "検索結果は省略されています"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "マッチするファイルはありません"
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 msgid "Open New Pull Request from {0}"
 msgstr "新しいプルリクエストを{0}から作成"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr "{0} &rarr; {1}から新しいプルリクエストを作成する"
 
-#: kallithea/templates/base/root.html:33
+#: kallithea/templates/base/root.html:41
 msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr "選択したチェンジセット{0} &rarr; {0}を表示"
 
-#: kallithea/templates/base/root.html:34
+#: kallithea/templates/base/root.html:42
 #, fuzzy
 msgid "Selection Link"
 msgstr "セレクション・リンク"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 msgid "Collapse Diff"
 msgstr "差分をたたむ"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 msgid "Expand Diff"
 msgstr "差分を表示"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr "リビジョンなし"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "権限の取消に失敗しました"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "権限 {0}: {1} を取り消してもよろしいですか?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr "有効"
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr "無効"
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "リビジョンを選択"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr "チェンジセットを指定"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "%s ブックマーク"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr "ブックマークを比較"
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "作成者"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "リビジョン"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "%s ブランチ"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr "ブランチを比較"
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "昇順で並び換え"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "降順で並び替え"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "レコードが見つかりません"
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "データエラー"
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "読み込み中..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "%s チェンジログ"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] "%d / %d リビジョンを表示"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "選択を解除"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr "リポジトリの最新のリビジョン(tip)に移動"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "%s とフォークを比較"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "フォーク元(%s)と比較"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr "ブランチフィルタ:"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-"チェンジセットステータス: %s\n"
-"関連するプルリクエスト %s を開く"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "チェンジセットステータス: %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr "コミットメッセージを展開"
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "チェンジセットにコメントがあります"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "ブックマーク %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "タグ %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "ブランチ %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "まだ変更がありません"
 
@@ -4820,7 +4379,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "追加"
 
@@ -4834,126 +4393,143 @@
 msgid "Affected %s files"
 msgstr "%s ファイルに影響"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr "コミットメッセージ"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "経過時間"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Refs"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Kallithea経由で直接ファイルを追加またはアップロード"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr "新しいファイルを追加"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr "新しいリポジトリをプッシュ"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "存在するリポジトリをプッシュ"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, fuzzy, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"チェンジセットステータス: %s\n"
+"関連するプルリクエスト %s を開く"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "チェンジセットステータス: %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr "コミットメッセージを展開"
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d 個のコメント"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "ブックマーク %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "タグ %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "ブランチ %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "%s チェンジセット"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr "親リビジョン"
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr "子リビジョン"
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "チェンジセットステータス"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "diffとして差分を表示"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr "パッチとして差分を表示"
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr "差分をダウンロード"
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr "マージ"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 #, fuzzy
 msgid "Grafted from:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "作成日"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] "%s ファイルに影響"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "%s ファイルに影響。 %s 個の追加と %s 個の削除"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr "とにかくすべての差分を表示"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
-msgstr "リビジョンなし"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "Comment"
+msgid "comment"
+msgstr "コメント"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -4973,169 +4549,177 @@
 msgid "Delete comment?"
 msgstr "コメントを削除しますか?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
 msgstr "ステータスを変更"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "{1} 行目にコメント"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr "コメントには %s 構文 ( %s サポートつき ) が利用できます。"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr "テキスト内で @username を使うと、そのユーザーに通知されます"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "コメントのプレビュー"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr "送信中..."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr "テキスト内で @username を使うと、そのユーザーに通知されます。"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "リビジョンステータスを設定"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr "プルリクエストステータスの投票"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr "変更なし"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "プルリクエスト #%s にコメント"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(閉鎖済み)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "コメント"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "プレビュー"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "コメントにはログインする必要があります。"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "今すぐログインする"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "隠す"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d 個のコメント"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "%d inline"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] "%d general"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr "テキスト内で @username を使うと、そのユーザーに通知されます。"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr "プルリクエストステータスの投票"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr "リビジョンステータスを設定"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr "変更なし"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(閉鎖済み)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "%s チェンジセット"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "チェンジセットステータス: %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "影響のあるファイル"
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "削除されたファイル"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "変更しない"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr "削除"
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr "リネーム"
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "%s は未知のリビジョンです"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "ファイル名がありません"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "追加されたファイル"
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr "このファイルのすべての差分を表示"
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr "このファイルの差分を並べて表示"
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr "インラインコメントを表示"
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr "削除"
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr "リネーム"
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "チェンジセットはありません"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "祖先"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:19
+#, fuzzy
+#| msgid "Common ancestor"
+msgid "Merge Ancestor"
+msgstr "共通の祖先"
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr "マージの差分を表示"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr "共通の祖先"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr "共通の祖先が見つかりません - リポジトリ同士に関連がありません"
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr "is"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "%s チェンジセット"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr "behind"
 
@@ -5146,129 +4730,189 @@
 msgstr "%s 比較"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr "リビジョンを比較"
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr "入れ替え"
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr "リビジョン、ブランチ、ブックマークもしくはタグの比較を行います。"
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] "%s コミットを表示"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "すべての差分を表示"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "公開リポジトリ"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr "リポジトリを作成しています..."
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "まだチェンジセットがありません"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "%s の RSS フィードを購読"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "%s の ATOM フィードを購読"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr "作成中"
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s がチェンジセットにコメント %(age)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Comment from %s on %s changeset %s"
+msgid "Comment on Changeset \"%s\""
 msgstr "%sから%sのチェンジセット%sに対するコメント"
 
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "チェンジセットを次に変更"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr "これは自動的に送信される通知です。このメールに返信しないでください。"
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset flow"
+msgid "Changeset on"
+msgstr "変更の流れ"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "ブランチ"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "ステータスを変更"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr ""
+"このプルリクエストはすでにクローズされていて、更新することはできません。"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "こんにちは %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr "あなたのアカウントのパスワードリセットリクエストを受け取りました。"
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr "新しいパスワードを設定するために、次のリンクをクリックしてください"
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
-msgstr "パスワードリセットのリクエストをしていない場合、このメッセージは無視してください。"
-
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
+msgstr ""
+"パスワードリセットのリクエストをしていない場合、このメッセージは無視してく"
+"ださい。"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention on Pull Request %s \"%s\" by %s"
 msgstr "%sさんが%sへのプルリクエスト \"%s\"でメンションしました"
 
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s requested your review of %s pull request \"%s\""
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
 msgstr "%sさんが%sへのプルリクエスト \"%s\"でレビューを要求しています"
 
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "プルリクエスト #%s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
 #: kallithea/templates/email_templates/pull_request_comment.html:4
-#, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr "%sから%sのプルリクエスト \"%s\"に対するコメント"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%sさんが%sへのプルリクエスト \"%s\"でメンションしました"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "%s からのプルリクエスト"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "プルリクエストに[コメント]"
+
+#: kallithea/templates/email_templates/registration.html:22
 #, fuzzy
-msgid "The comment closed the pull request with status"
-msgstr "%s がプルリクエスト\"%s\" にコメントしました"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-#, fuzzy
-msgid "The comment was made with status"
-msgstr "プルリクエストを以下のステータスで閉じました:"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "このユーザを閲覧する"
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "グループ名"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5286,7 +4930,7 @@
 msgstr "%s ファイル差分"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "%s ファイル"
@@ -5296,72 +4940,74 @@
 msgid "%s Files Add"
 msgstr "%s ファイルを追加"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr "新しいファイルを追加"
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "場所"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr "ファイル名..."
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "または"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr "アップロードファイル"
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr "新しいファイルを作成"
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr "ファイルモード"
+#, fuzzy
+msgid "New file type"
+msgstr "新しいファイル"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr "コミットメッセージ"
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr "変更をコミット"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "前のリビジョン"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "次のリビジョン"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "このブランチで追跡"
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr "ファイル一覧を検索"
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "ファイル一覧を読み込み中..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "サイズ"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "最後のリビジョン"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr "最終更新日"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr "最後の作成者"
 
@@ -5371,7 +5017,7 @@
 msgstr "%s のファイルを削除"
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr "ファイルを削除"
 
@@ -5384,75 +5030,83 @@
 msgid "Edit file"
 msgstr "ファイルを編集"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr "アノテーションを表示"
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
 msgid "Download as Raw"
 msgstr "Raw形式でダウンロード"
 
-#: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "ソース"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "ファイルを編集"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
 msgid_plural "%s authors"
 msgstr[0] "%s 人の作成者"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr "このリビジョンとの差分"
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr "このリビジョンを表示"
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr "全ての履歴を表示"
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr "全ての履歴を表示"
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr "作成者を表示"
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr "ソースを表示"
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Edit on Branch:%s"
+msgid "Edit on Branch: %s"
 msgstr "ブランチ:%s で編集"
 
-#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/files/files_source.html:37
 msgid "Editing binary files not allowed"
 msgstr "バイナリファイルの編集は行えません"
 
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:40
 msgid "Editing files allowed only when on branch head revision"
 msgstr "ファイル編集はブランチのヘッドリビジョンでのみ許可されています"
 
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/files/files_source.html:41
 msgid "Deleting files allowed only when on branch head revision"
 msgstr "ファイルの削除はブランチのヘッドリビジョンでのみ行えます"
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "バイナリファイル (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "表示するには大きすぎるファイルです"
+
+#: kallithea/templates/files/files_source.html:71
+#, fuzzy
+msgid "Show full annotation anyway."
+msgstr "とにかくすべての差分を表示"
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "表示するには大きすぎるファイルです"
+#, fuzzy
+msgid "Show as raw."
+msgstr "Raw形式で表示"
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5472,12 +5126,12 @@
 msgstr "%s フォロワー"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "フォロワー"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "フォロー開始日 -"
 
@@ -5486,35 +5140,37 @@
 msgid "Fork repository %s"
 msgstr "リポジトリ %s をフォーク"
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "フォーク名"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
-msgstr "ファイルページ、ダウンロード、検索、READMEのデフォルトのリビジョンを指定します。"
-
-#: kallithea/templates/forks/fork.html:68
+msgstr ""
+"ファイルページ、ダウンロード、検索、READMEのデフォルトのリビジョンを指定し"
+"ます。"
+
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "非公開"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "権限のコピー"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "フォーク元リポジトリから権限をコピーします"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "クローン後にupdateする"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "クローンした後にソースをチェックアウトします"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr "このリポジトリをフォーク"
 
@@ -5524,40 +5180,40 @@
 msgstr "%s フォーク"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "フォーク"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "フォークしました"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "まだフォークがありません"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "ATOM ジャーナルフィード"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "RSS ジャーナルフィード"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "私のリポジトリ"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "まだエントリがありません"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "ATOM 公開ジャーナルフィード"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "RSS 公開ジャーナルフィード"
 
@@ -5566,31 +5222,36 @@
 msgid "New Pull Request"
 msgstr "新しいプルリクエスト"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "タイトル"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "このプルリクエストの簡潔な説明を書いてください"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "変更の流れ"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "元のリポジトリ"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "リビジョン"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "相手のリポジトリ"
 
@@ -5602,6 +5263,10 @@
 msgid "Vote"
 msgstr "投票"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "経過時間"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr "From"
@@ -5625,7 +5290,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "クローズ"
 
@@ -5640,7 +5305,9 @@
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
 #, python-format
 msgid "Confirm again to delete this pull request with %s comments"
-msgstr "このプルリクエストには %s件のコメントがありますが削除してもよろしいですか?"
+msgstr ""
+"このプルリクエストには %s件のコメントがありますが削除してもよろしいです"
+"か?"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:6
 #, python-format
@@ -5652,103 +5319,101 @@
 msgid "Pull request %s from %s#%s"
 msgstr "%s からのプルリクエスト"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr "変更の概要"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+#, fuzzy
+#| msgid "Reviewer voting result"
+msgid "Voting Result"
 msgstr "レビュアーの投票結果"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr "投票からプルリクエストのステータスを計算"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "未レビュー"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d 人のレビュアー"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "プルリクエストはすべてのレビュアーにレビューされました"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr "レビュアーがいません"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr "Origin"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr "on"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr "Target"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "変更を取得:"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr "更新"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "新規登録"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr "現在のリビジョン ー 変更なし"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr "プルリクエストレビュアー"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr "変更を保存"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr "変更をキャンセル"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "reviewer"
+msgid "Reviewers"
+msgstr "レビュアー"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "レビュアーを削除"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr "追加するレビュアーの名前を入力"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "レビュワー候補"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr "クリックしてリポジトリの所有所をレビュアーに追加:"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr "変更を保存"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr "新しいプルリクエストとして保存"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr "変更をキャンセル"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr "プルリクエストの内容"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr "共通の祖先"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5756,7 +5421,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "%s' からのプルリクエスト"
 
@@ -5765,35 +5429,44 @@
 msgid "Pull Requests to '%s'"
 msgstr "'%s' へのプルリクエスト"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr "新しいプルリクエストを作成"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr "%s へのプルリクエストを表示"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "%s からのプルリクエストを表示"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
-msgstr "クローズしたプルリクエストを隠す(クローズしていないプルリクエストのみ表示する)"
-
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+msgstr ""
+"クローズしたプルリクエストを隠す(クローズしていないプルリクエストのみ表示"
+"する)"
+
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
-msgstr "クローズしたプルリクエストも表示(クローズしていないプルリクエストに加えて)"
-
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+msgstr ""
+"クローズしたプルリクエストも表示(クローズしていないプルリクエストに加えて)"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr "作成したプルリクエスト"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "プルリクエストレビュアー"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr "参加しているプルリクエスト"
 
@@ -5807,29 +5480,29 @@
 msgid "Search in All Repositories"
 msgstr "全てのリポジトリから検索"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "検索キーワード"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "検索対象"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "ファイルの内容"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "コミットメッセージ"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "ファイル名"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "権限がありません"
 
@@ -5839,80 +5512,80 @@
 msgstr "%s 統計情報"
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr "%s ATOM フィード"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "%s RSS フィード"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "有効にする"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "収集した統計情報: "
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "ファイル"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "もっと表示"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "コミット"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "追加されたファイル"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "変更されたファイル"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "削除されたファイル"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "コミット"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "追加されたファイル"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "変更されたファイル"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "削除されたファイル"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "%s 要約"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "リポジトリは %s によってロックされました"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "リポジトリはロックされていません"
 
@@ -5920,85 +5593,368 @@
 msgid "Fork of"
 msgstr "フォーク元"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "クローン元"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr "クローンURL"
-
-#: kallithea/templates/summary/summary.html:78
-msgid "Show by Name"
-msgstr "名前で表示"
-
-#: kallithea/templates/summary/summary.html:79
+#: kallithea/templates/summary/summary.html:68
 msgid "Show by ID"
 msgstr "IDで表示"
 
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr "名前で表示"
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "トレンドファイル"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "ダウンロード"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "まだダウンロードがありません"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "このリポジトリのダウンロードは無効化されています"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "ZIPでダウンロード"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "チェックするとダウンロードアーカイブにサブリポジトリが含まれます"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr "サブリポジトリを含める"
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "リポジトリサイズ"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "フィード"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "最近の変更"
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr "クイックスタート"
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr "クイックスタート"
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr "Kallithea経由で直接ファイルを追加またはアップロード"
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr "新しいリポジトリをプッシュ"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "存在するリポジトリをプッシュ"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr "リビジョン %s:%s の README ファイル"
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "%s を %s でダウンロード"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s タグ"
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr "タグを比較"
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr ""
+#~ "検索するためのインデックスがありません。whooshでインデックスを作成して"
+#~ "下さい"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IPアドレス %s は許可されません"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s がチェンジセットにコメント %(age)s"
+
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s がメッセージを送信 %(age)s"
+
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s がにあなたにメンション %(age)s"
+
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s がKallitheaに登録 %(age)s"
+
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s が新しいプルリクエストを作成 %(age)s"
+
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s がプルリクエストにコメント %(age)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s がチェンジセットにコメント %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s がメッセージを送信 %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s がにあなたにメンション %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s がKallitheaに登録 %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s が新しいプルリクエストを作成 %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s がプルリクエストにコメント %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "リポジトリグループ"
+
+#~ msgid "My Notifications"
+#~ msgstr "通知"
+
+#~ msgid "All"
+#~ msgstr "すべて"
+
+#~ msgid "Comments"
+#~ msgstr "コメント"
+
+#~ msgid "Mark All Read"
+#~ msgstr "すべて既読としてマーク"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "すべて既読としてマーク"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "まだ通知がありません"
+
+#~ msgid "Show Notification"
+#~ msgstr "通知を表示"
+
+#~ msgid "Notifications"
+#~ msgstr "通知"
+
+#~ msgid "Home"
+#~ msgstr "ホーム"
+
+#~ msgid "with"
+#~ msgstr "と"
+
+#~ msgid "members"
+#~ msgstr "メンバー"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "チェンジセットにコメントがあります"
+
+#~ msgid "Author"
+#~ msgstr "作成者"
+
+#~ msgid "Refs"
+#~ msgstr "Refs"
+
+#, fuzzy
+#~ msgid "Commenting on line."
+#~ msgstr "{1} 行目にコメント"
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "プルリクエスト #%s にコメント"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "日時"
+
+#~ msgid "Previous revision"
+#~ msgstr "前のリビジョン"
+
+#~ msgid "Next revision"
+#~ msgstr "次のリビジョン"
+
+#~ msgid "Follow current branch"
+#~ msgstr "このブランチで追跡"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "未レビュー"
+
+#~ msgid "%d reviewer"
+#~ msgid_plural "%d reviewers"
+#~ msgstr[0] "%d 人のレビュアー"
+
+#~ msgid "Pull request was reviewed by all reviewers"
+#~ msgstr "プルリクエストはすべてのレビュアーにレビューされました"
+
+#~ msgid "There are no reviewers"
+#~ msgstr "レビュアーがいません"
+
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "プルリクエストレビュアー"
+
+#~ msgid "Dashboard"
+#~ msgstr "ダッシュボード"
+
+#~ msgid "Group Name"
+#~ msgstr "グループ名"
+
+#~ msgid "Remember me"
+#~ msgstr "次回から自動的にサインイン"
+
+#~ msgid "name this file..."
+#~ msgstr "ファイルに名前をつける..."
+
+#~ msgid "Change your avatar at"
+#~ msgstr "アバターを変更できます : "
+
+#~ msgid "Using"
+#~ msgstr "メールアドレス:"
+
+#~ msgid "Missing email, please update your user email address."
+#~ msgstr "メールアドレスがありません。更新してください。"
+
+#~ msgid "%s Repository group dashboard"
+#~ msgstr "%s リポジトリグループダッシュボード"
+
+#~ msgid "Rescan option"
+#~ msgstr "再スキャンオプション"
+
+#~ msgid "Web"
+#~ msgstr "Web"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "VCSの操作にSSLを必須とする"
+
+#~ msgid ""
+#~ "Activate to require SSL both pushing and pulling. If SSL certificate "
+#~ "is missing, it will return an HTTP Error 406: Not Acceptable."
+#~ msgstr ""
+#~ "プッシュ、プル時にSSLを要求します。SSLでない場合はHTTP Error 406: Not "
+#~ "Acceptableを返します。"
+
+#~ msgid "Use Gravatars in Kallithea"
+#~ msgstr "Gravatorsを利用する"
+
+#, fuzzy
+#~| msgid ""
+#~| "Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+#~| "{repo}'.\n"
+#~| "The following variables are available:\n"
+#~| "{scheme} 'http' or 'https' sent from running Kallithea server,\n"
+#~| "{user}   current user username,\n"
+#~| "{netloc} network location/server host of running Kallithea server,\n"
+#~| "{repo}   full repository name,\n"
+#~| "{repoid} ID of repository, can be used to contruct clone-by-id"
+#~ msgid ""
+#~ "Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+#~ "{repo}'.\n"
+#~ "                                                        The following "
+#~ "variables are available:\n"
+#~ "                                                        {scheme} "
+#~ "'http' or 'https' sent from running Kallithea server,\n"
+#~ "                                                        {user}   "
+#~ "current user username,\n"
+#~ "                                                        {netloc} "
+#~ "network location/server host of running Kallithea server,\n"
+#~ "                                                        {repo}   full "
+#~ "repository name,\n"
+#~ "                                                        {repoid} ID of "
+#~ "repository, can be used to construct clone-by-id"
+#~ msgstr ""
+#~ "クローン URL のスキーマは、 '{scheme}://{user}@{netloc}/{repo}' のよう"
+#~ "な形式にします。使える変数は下記の通りです:\n"
+#~ "                                                        {scheme} "
+#~ "Kallithea サーバからリクエストを送信するときに使うスキーム。 'http' ま"
+#~ "たは 'https'\n"
+#~ "                                                        {user}   現在の"
+#~ "ユーザーのユーザー名\n"
+#~ "                                                        {netloc} "
+#~ "Kallithea サーバーのアドレスまたはホスト名\n"
+#~ "                                                        {repo}   リポジ"
+#~ "トリの完全な名前\n"
+#~ "                                                        {repoid} リポジ"
+#~ "トリの ID。 clone-by-id に使います。"
+
+#~ msgid "Dashboard items"
+#~ msgstr "ダッシュボードの項目"
+
+#~ msgid ""
+#~ "Number of items displayed in the main page dashboard before pagination "
+#~ "is shown."
+#~ msgstr "メインページダッシュボードで1ページに表示する要素数。"
+
+#~ msgid "Admin pages items"
+#~ msgstr "管理ページの項目"
+
+#~ msgid "quick filter..."
+#~ msgstr "クイックフィルタ..."
+
+#~ msgid "Change avatar at"
+#~ msgstr "アバターを変更:"
+
+#~ msgid "Missing email, please update this user email address."
+#~ msgstr ""
+#~ "メールアドレスがありません。このユーザーのメールアドレスを更新してくだ"
+#~ "さい。"
+
+#~ msgid "Keyboard shortcuts"
+#~ msgstr "キーボードショートカット"
+
+#~ msgid "Site-wide shortcuts"
+#~ msgstr "サイト全体"
+
+#~ msgid "Forgot password ?"
+#~ msgstr "パスワードを忘れた?"
+
+#~ msgid "Ancestor"
+#~ msgstr "祖先"
+
+#~ msgid "No common ancestor found - repositories are unrelated"
+#~ msgstr "共通の祖先が見つかりません - リポジトリ同士に関連がありません"
+
+#~ msgid "Comment from %s on %s changeset %s"
+#~ msgstr "%sから%sのチェンジセット%sに対するコメント"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "チェンジセットを次に変更"
+
+#~ msgid "This is an automatic notification. Don't reply to this mail."
+#~ msgstr ""
+#~ "これは自動的に送信される通知です。このメールに返信しないでください。"
+
+#~ msgid "%s mentioned you on %s pull request \"%s\""
+#~ msgstr "%sさんが%sへのプルリクエスト \"%s\"でメンションしました"
+
+#~ msgid "%s requested your review of %s pull request \"%s\""
+#~ msgstr "%sさんが%sへのプルリクエスト \"%s\"でレビューを要求しています"
+
+#~ msgid "Comment from %s on %s pull request \"%s\""
+#~ msgstr "%sから%sのプルリクエスト \"%s\"に対するコメント"
+
+#, fuzzy
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "%s がプルリクエスト\"%s\" にコメントしました"
+
+#, fuzzy
+#~ msgid "The comment was made with status"
+#~ msgstr "プルリクエストを以下のステータスで閉じました:"
+
+#~ msgid "View this user here"
+#~ msgstr "このユーザを閲覧する"
+
+#~ msgid "Edit on Branch:%s"
+#~ msgstr "ブランチ:%s で編集"
+
+#~ msgid "Repository Size"
+#~ msgstr "リポジトリサイズ"
 
 #~ msgid "No comments."
 #~ msgstr "%d 個のコメント"
@@ -6030,9 +5986,6 @@
 #~ msgid "No Files"
 #~ msgstr "ファイルなし"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "ユーザー名 \"%(username)s\" は許可されていません"
 
@@ -6045,11 +5998,17 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "無効なクローンURIです"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr "無効なクローンURIです。有効な http(s)/svn+http(s) のURIを指定してください"
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr "リビジョン %(revs)s はすでにプルリクエストの一部かステータスが設定されています"
+#~ msgid ""
+#~ "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
+#~ msgstr ""
+#~ "無効なクローンURIです。有効な http(s)/svn+http(s) のURIを指定してくださ"
+#~ "い"
+
+#~ msgid ""
+#~ "Revisions %(revs)s are already part of pull request or have set status"
+#~ msgstr ""
+#~ "リビジョン %(revs)s はすでにプルリクエストの一部かステータスが設定され"
+#~ "ています"
 
 #~ msgid "Defaults"
 #~ msgstr "デフォルト設定"
@@ -6165,9 +6124,6 @@
 #~ msgid "user groups"
 #~ msgstr "ユーザーグループ"
 
-#~ msgid "show"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "チェンジセットのステータスを変更"
 
@@ -6186,21 +6142,9 @@
 #~ msgid "My Repos"
 #~ msgstr "リポジトリ"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "所有者"
 
-#~ msgid "reviewer"
-#~ msgstr "レビュアー"
-
 #~ msgid "Your new password"
 #~ msgstr "新しいパスワード"
 
@@ -6222,3 +6166,172 @@
 #~ msgid "Created by"
 #~ msgstr "作成日"
 
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "リポジトリのキャッシュを無効化してもよろしいですか?"
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr "コメントには %s 構文 ( %s サポートつき ) が利用できます。"
+
+#~ msgid "Use @username inside this text to notify another user"
+#~ msgstr "テキスト内で @username を使うと、そのユーザーに通知されます"
+
+#~ msgid "Comment preview"
+#~ msgstr "コメントのプレビュー"
+
+#~ msgid "Preview"
+#~ msgstr "プレビュー"
+
+#~ msgid "New file mode"
+#~ msgstr "ファイルモード"
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "%s で置き換えられたのでクローズします。"
+
+#~ msgid "Closing."
+#~ msgstr "クローズ。"
+
+#~ msgid "Repository no access"
+#~ msgstr "リポジトリへのアクセス権限無し"
+
+#~ msgid "Repository read access"
+#~ msgstr "リポジトリに読込権限でアクセス"
+
+#~ msgid "Repository write access"
+#~ msgstr "リポジトリに書込権限でアクセス"
+
+#~ msgid "Repository admin access"
+#~ msgstr "リポジトリに管理権限でアクセス"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "リポジトリグループへのアクセス権限なし"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "リポジトリグループに読込権限でアクセス"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "リポジトリグループに書込権限でアクセス"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "リポジトリグループに管理権限でアクセス"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "リポジトリの作成を有効にする"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "リポジトリの作成を有効にする"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "リポジトリのフォークを無効にする"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "リポジトリのフォークを有効にする"
+
+#~ msgid "Register disabled"
+#~ msgstr "新規登録を無効にする"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Kallitheaに登録した新しいユーザーを手動でアクティベートする"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr "Kallitheaに登録した新しいユーザーを自動でアクティベートする"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "未レビュー"
+
+#~ msgid "Rejected"
+#~ msgstr "却下"
+
+#~ msgid "Under Review"
+#~ msgstr "レビュー中"
+
+#~ msgid "Repository group no access"
+#~ msgstr "リポジトリグループへのアクセス権限なし"
+
+#~ msgid "Repository group read access"
+#~ msgstr "リポジトリグループに読込権限でアクセス"
+
+#~ msgid "Repository group write access"
+#~ msgstr "リポジトリグループに書込権限でアクセス"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "リポジトリグループに管理権限でアクセス"
+
+#~ msgid "User group no access"
+#~ msgstr "ユーザーグループへのアクセス権限なし"
+
+#~ msgid "User group read access"
+#~ msgstr "ユーザーグループに読込権限でアクセス"
+
+#~ msgid "User group write access"
+#~ msgstr "ユーザーグループに書込権限でアクセス"
+
+#~ msgid "User group admin access"
+#~ msgstr "ユーザーグループに管理権限でアクセス"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "リポジトリグループの作成を無効にする"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "リポジトリグループの作成を有効にする"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "ユーザーグループの作成を無効にする"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "ユーザーグループの作成を有効にする"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "ユーザーの新規登録時に手動でアカウントをアクティベートする"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "ユーザーの新規登録時に自動でアカウントをアクティベートする"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "%(user)s がプリリクエスト #%(pr_id)s: %(pr_title)s のレビューを求めてい"
+#~ "ます"
+
+#~ msgid "repositories"
+#~ msgstr "リポジトリ"
+
+#~ msgid "No repositories found."
+#~ msgstr "リポジトリが見つかりません。"
+
+#~ msgid "There are no branches yet"
+#~ msgstr "まだブランチがありません"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "まだタグがありません"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "まだブックマークがありません"
+
+#~ msgid "enabled"
+#~ msgstr "有効"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "%s ブックマーク"
+
+#~ msgid "Compare Bookmarks"
+#~ msgstr "ブックマークを比較"
+
+#~ msgid "%s Branches"
+#~ msgstr "%s ブランチ"
+
+#~ msgid "Compare Branches"
+#~ msgstr "ブランチを比較"
+
+#~ msgid "Editing file"
+#~ msgstr "ファイルを編集"
+
+#~ msgid "Update"
+#~ msgstr "更新"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "新しいプルリクエストとして保存"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s タグ"
+
+#~ msgid "Compare Tags"
+#~ msgstr "タグを比較"
--- a/kallithea/i18n/kallithea.pot	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/kallithea.pot	Sun Mar 31 21:28:56 2019 +0200
@@ -1,451 +1,464 @@
 # Translations template for Kallithea.
-# Copyright (C) 2017 Various authors, licensing as GPLv3
+# Copyright (C) 2019 Various authors, licensing as GPLv3
 # This file is distributed under the same license as the Kallithea project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: Kallithea 0.3.2\n"
+"Project-Id-Version: Kallithea 0.4.0rc1\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "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"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"Generated-By: Babel 2.5.3\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90 kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+msgid "No permission to change status"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid "Changing status on a changeset associated with a closed pull request is not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr ""
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
+#: kallithea/controllers/error.py:84
 msgid "The request could not be understood by the server due to malformed syntax."
 msgstr ""
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:55
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
 
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
-msgstr ""
-
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr ""
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr ""
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr ""
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757 kallithea/controllers/pullrequests.py:184
+#: kallithea/model/scm.py:706
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758 kallithea/controllers/pullrequests.py:185
+#: kallithea/model/scm.py:717
 msgid "Tags"
 msgstr ""
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56 kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr ""
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid "This pull request is based on another %s revision and there is no simple diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid "Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -457,149 +470,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -607,1755 +622,1230 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 msgid "Repository has been locked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
+#: kallithea/lib/base.py:495
 msgid "CSRF token leak has been detected - all form tokens have been expired"
 msgstr ""
 
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
+#: kallithea/lib/diffs.py:213
 msgid "Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701 kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid "Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid "Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid "[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on %(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid "[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from %(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid "[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from %(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
+#: kallithea/model/pull_request.py:76
 #, python-format
 msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr ""
 
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid "Cannot create pull request - criss cross merge detected, please merge a later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid "This iteration is based on another %s revision and there is no simple diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
+#: kallithea/model/user.py:253
 msgid "You can't remove this user since it is crucial for the entire application"
 msgstr ""
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, python-format
 msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid "User \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, python-format
 msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid "The password to your account %s has been changed using password reset form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr ""
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
+#: kallithea/model/validators.py:732
 #, python-format
 msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 msgstr ""
 
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2363,142 +1853,125 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 msgid "A password reset link will be sent to the specified email address if it is registered in the system."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid "Note that you must use the same browser session for this as the one used to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2506,30 +1979,16 @@
 msgstr[1] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2537,7 +1996,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2546,104 +2005,104 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid "Comma-separated list of plugins; Kallithea will try user authentication in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid "Private repositories are only visible to people explicitly added as collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid "Private repositories are only visible to people explicitly added as collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2652,43 +2111,43 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid "Gist was update since you started editing. Copy your changes and click %(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2697,7 +2156,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2720,16 +2180,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2738,45 +2198,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2786,182 +2246,197 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -2969,105 +2444,67 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
+#: kallithea/templates/admin/my_account/my_account_password.html:39
 #, python-format
 msgid "This account is managed with %s and the password cannot be changed here"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr ""
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3081,151 +2518,155 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:40
-msgid "Apply to all existing repository groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
-msgid "Permissions for the Default user on new repository groups."
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
-msgid "User group"
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:53
-msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
-msgid "Apply to all existing user groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
-msgid "Permissions for the Default user on new user groups."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid "Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
 msgid "With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57 kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3234,30 +2675,28 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3282,12 +2721,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3298,16 +2737,32 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3315,95 +2770,87 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid "Set or revoke permission to all children of that group, including non-private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
+msgid "Repository group %s"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid "Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
 msgid "Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid "Default revision for files page, downloads, full text search index and readme generation"
 msgstr ""
 
@@ -3430,22 +2877,22 @@
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3454,90 +2901,88 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid "All actions done in this repository will be visible to everyone in the public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
+#, python-format
+msgid "Locked by %s on %s"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:60
-msgid "Confirm to lock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
-msgid "Lock Repository"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid "Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid "The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it."
 msgstr ""
 
@@ -3545,134 +2990,139 @@
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-msgid "Confirm to pull changes from remote repository."
-msgstr ""
-
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the repository URL changes.\n"
 "                               Using the above permanent URL guarantees that this repository always will be accessible on that URL.\n"
 "                               This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid "Optional: URL of a remote repository. If set, the repository can be pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3688,7 +3138,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3709,7 +3159,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3721,81 +3171,81 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
-msgid "HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid "HTML (possibly with                         JavaScript and/or CSS) that will be added to the bottom                         of every page. This can be used for web analytics                         systems, but also to                         perform instance-specific customizations like adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
-msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:17
 msgid "Custom Hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid "Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem."
 msgstr ""
 
@@ -3803,159 +3253,147 @@
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid "Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid "Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid "If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid "If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid "This option completely reindexeses all of the repositories for proper fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid "Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
 msgid "Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:64
-msgid "Location of repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:69
 msgid "Click to unlock. You must restart Kallithea in order to make this setting take effect."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid "Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
@@ -3963,11 +3401,11 @@
 msgid "Shows or hides a version number of Kallithea displayed in the footer."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following variables of the URL will be replaced accordingly.\n"
@@ -3978,79 +3416,84 @@
 "                                                        {netloc}    network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
 "Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.\n"
-"                                                        The following variables are available:\n"
-"                                                        {scheme} 'http' or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current user username,\n"
-"                                                        {netloc} network location/server host of running Kallithea server,\n"
-"                                                        {repo}   full repository name,\n"
-"                                                        {repoid} ID of repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
-msgid "Number of items displayed in the main page dashboard before pagination is shown."
+"                                                    The following variables are available:\n"
+"                                                    {scheme} 'http' or 'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user username,\n"
+"                                                    {netloc} network location/server host of running Kallithea server,\n"
+"                                                    {repo}   full repository name,\n"
+"                                                    {repoid} ID of repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid "Number of items displayed in the repository pages before pagination is shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid "Number of items displayed in the admin pages grids before pagination is shown."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:75
-msgid "Icons"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:80
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid "Parses meta tags from the repository description field and turns them into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:111
-msgid "Parses meta tags from the repository description field and turns them into colored tags."
-msgstr ""
-
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59 kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4069,13 +3512,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4084,15 +3527,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4106,17 +3549,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4135,12 +3578,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4149,7 +3592,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4158,24 +3601,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4183,220 +3618,214 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86 kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89 kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
+#: kallithea/templates/base/base.html:178 kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr ""
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid "Select to inherit global settings, IP whitelist and permissions from the %s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4405,227 +3834,171 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-msgid "Open New Pull Request from {0}"
+msgid "Add Another Comment"
 msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
+msgid "Open New Pull Request from {0}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4641,7 +4014,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4655,38 +4028,59 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, python-format
+msgid "%s comments"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4694,85 +4088,75 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+msgid "comment"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
@@ -4791,171 +4175,164 @@
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr ""
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid "Please merge the target branch to your branch before creating a pull request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -4966,123 +4343,161 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid "This account is however managed outside this system and the password cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid "Should you not be able to use the link above, please type the following code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid "If it weren't you who requested the password reset, just disregard this message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+msgid "Pull request"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5101,7 +4516,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5111,72 +4526,73 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5186,7 +4602,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5199,24 +4615,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5224,50 +4636,58 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5288,12 +4708,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5302,35 +4722,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5340,40 +4760,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5382,31 +4802,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5418,6 +4843,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5441,7 +4870,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5468,102 +4897,91 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid "This is just a range of changesets and doesn't have a target or a real merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid "Pull request iterations do not change content once created. Select a revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5579,35 +4997,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5621,29 +5043,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5653,80 +5075,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5734,83 +5156,78 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr ""
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
--- a/kallithea/i18n/nb_NO/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/nb_NO/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -5,263 +5,291 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: Kallithea 0.3.2\n"
+"Project-Id-Version: Kallithea 0.3.99\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2018-02-05 15:38+0000\n"
-"Last-Translator: Michal Čihař <michal@cihar.com>\n"
-"Language-Team: Norwegian Bokmål "
-"<https://hosted.weblate.org/projects/kallithea/stable/nb_NO/>\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2019-03-14 01:03+0000\n"
+"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n"
+"Language-Team: Norwegian Bokmål <https://hosted.weblate.org/projects/"
+"kallithea/kallithea/nb_NO/>\n"
 "Language: nb_NO\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 2.19-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"X-Generator: Weblate 3.5.1\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Ingen endringssett enda"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Ingen"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(lukket)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Vis blanktegn"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorer blanktegn"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Øk diff-bindeleddsinformasjon til %(num)s linjer"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permission to change pull request status"
+msgid "No permission to change status"
+msgstr "Ingen tilgang til endring av innsendingsforespørselsstatus"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Slettet flettingsforespørsel %s"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "En slik revisjon funnes ikke for denne pakkebrønnen"
 
-#: kallithea/controllers/changeset.py:383
-msgid "Changing status on a changeset associated with a closed pull request is not allowed"
-msgstr ""
-"Endring av status for et endringssett tilknyttet en lukket "
-"innhentingsforespørsel tillates ikke"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Velg endringssett"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "Fant ikke annen pakkebrønn %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr "Kan ikke sammenligne pakkebrønner av forskjellige typer"
+
+#: kallithea/controllers/compare.py:244
+#, fuzzy
+msgid "Cannot show empty diff"
+msgstr "Kan ikke vise tom diff"
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Kan ikke sammenligne pakkebrønner uten bruk av felles opphav"
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Ingen respons"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr "Ukjent feil"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-"Forespørselen kunne ikke forstås av tjeneren som følge av feilaktig syntaks."
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"Forespørselen kunne ikke forstås av tjeneren som følge av feilaktig "
+"syntaks."
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Uautorisert tilgang til ressurs"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Du har ikke tilgang til å se denne siden"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Kunne ikke finne ressursen"
 
-#: kallithea/controllers/error.py:109
-msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
 msgstr ""
 "Tjeneren støtte på en uventet tilstand som forhindret utøvelse av "
 "forespørsel."
 
-#: kallithea/controllers/feed.py:55
-#, fuzzy, python-format
-msgid "Changes on %s repository"
-msgstr "Endringer i %s-pakkebrønn"
-
-#: kallithea/controllers/feed.py:56
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s sendte inn %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Endringsettet var for stort og har blitt avskåret…"
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, fuzzy, python-format
 msgid "%s %s feed"
 msgstr "%s %s kilde"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Endringsettet var for stort og har blitt avskåret…"
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s sendte inn %s"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, fuzzy, python-format
+msgid "Changes on %s repository"
+msgstr "Endringer i %s-pakkebrønn"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Klikk her for å legge til ny fil"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Ingen filer enda. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, fuzzy, python-format
 msgid "%s at %s"
 msgstr "%s den %s"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Denne pakkebrønnen har blitt påst av %s den %s"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 #, fuzzy
 msgid "You can only delete files with revision being a valid branch"
-msgstr "Du kan bare slette filer med en revisjon som er en gyldig forgrening"
-
-#: kallithea/controllers/files.py:328
+msgstr ""
+"Du kan bare slette filer med en revisjon som er en gyldig forgrening"
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Slettet filen %s via Kallithea"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Filen %s ble slettet"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Feil inntraff under innsendelse"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
-msgstr "Du kan bare redigere filer med en revisjon som er en gyldig avgrening"
-
-#: kallithea/controllers/files.py:391
+msgstr ""
+"Du kan bare redigere filer med en revisjon som er en gyldig avgrening"
+
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Filen %s ble endret via Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Ingen endringer"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Innsendt til %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Fil lagt til via Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Inget innhold"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Inget filnavn"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
-msgstr "Plasseringen må være en relativ sti, og kan ikke inneholde .. i stien"
-
-#: kallithea/controllers/files.py:526
+msgstr ""
+"Plasseringen må være en relativ sti, og kan ikke inneholde .. i stien"
+
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Nedlastinger avskrudd"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Ukjent revisjon %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Tom pakkebrønn"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Ukjent arkivtype"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Endringssett"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Forgreninger"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Etiketter"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "En uventet feil inntraff under forgrening av pakkebrønnen %s"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Grupper"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Pakkebrønner"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
@@ -269,200 +297,191 @@
 msgid "Branch"
 msgstr "Forgrening"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Lukkede forgreninger"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Etikett"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Bokmerke"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Offentlig loggbok"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Loggbok"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 #, fuzzy
 msgid "Bad captcha"
 msgstr "Feilaktig CAPTCHA"
 
-#: kallithea/controllers/login.py:152
-#, fuzzy
-msgid "You have successfully registered into Kallithea"
-msgstr "Du har registrert deg på Kallithea"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, fuzzy, python-format
+msgid "You have successfully registered with %s"
+msgstr "Du har registrer deg på %s"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "Passordbekreftelseskode sendt"
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr "Ugyldig passordtilbakestillingssymbol"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Passord oppdatert"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, fuzzy, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "Ugyldig analytiker \"%s\" angitt"
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (lukket)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Endringssett"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 #, fuzzy
 msgid "Special"
 msgstr "Spesiell"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "Likemennsforgreninger"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Bokmerker"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Feil ved opprettelse av ny innsendelsesforespørsel: %s"
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Ingen beskrivelse"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "Åpnet en ny innsendelsesforespørsel"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, fuzzy, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr "Ugyldig analytiker \"%s\" angitt"
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "Feil inntraff under opprettelse av innsendelsesforespørsel"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr "Manglende endringssett siden forrige innsendingsforespørsel:"
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr "Nye endringssett på %s %s siden forrige innsendingsforespørsel:"
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr "Opphav endret seg ikke - vis diff siden forrige versjon:"
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid "This pull request is based on another %s revision and there is no simple diff."
-msgstr ""
-"Denne innsendingsforespørselen er basert på en annen %s-revisjon, og det "
-"finnes ingen enkel diff."
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr "Ingen endringer funnet på %s %s siden forrige versjon."
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "Lukket, erstattet av %s."
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr "Innsendingsforespørsel opprettet"
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Åpnet en ny innsendelsesforespørsel"
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Ingen beskrivelse"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "Innsendingsforespørsel oppdatert"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Slettet innsendingsforespørsel"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Denne innsendingsforespørselen har allerede blitt flettet inn i %s."
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 "Denne innsendingsforespørselen har blitt lukket, og kan ikke oppdateres."
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr "Denne innsendingsforespørselen kan ikke oppdateres med endringer på %s:"
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-"Ingen endringssett funnet for oppdatering av denne innsendingsforespørselen."
-
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Merk: Forgreningen %s har et annet hode: %s."
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr "Git-innsendingsforespørsler støtter ikke oppdateringer enda."
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr "Ingen tilgang til endring av innsendingsforespørselsstatus"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Lukker."
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "Ugyldig søkespørring. Prøv å sette den i sistattegn."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Det er ingen indeks å søke i. Kjør en annen whoosh-indekserer"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr "Tjeneren har ingen søkeindeks."
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Feil inntraff under søkeoperasjon."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "Ingen data klar enda"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "Statistikk er avskrudd for denne pakkebrønnen"
 
@@ -474,152 +493,155 @@
 msgid "error occurred during update of auth settings"
 msgstr "feil inntraff under oppdatering av autentiseringsinnstillinger"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Forvalgte innstillinger oppdatert"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "Feil inntraff under oppdatering av forvalg"
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr "For alltid"
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr "For alltid"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "Fem minutter"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "Fem minutter"
-
-#: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
 msgid "1 hour"
 msgstr "Én time"
 
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/gists.py:61
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "Én dag"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "Én måned"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Livstid"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Feil inntraff under gist-opprettelse"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Slettet gist-en %s"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
 msgstr "Uendret"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr "Oppdaterte gist-innhold"
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr "Oppdaterte gist-data"
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, fuzzy, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Feil inntraff under oppdatering av gist-en %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
-"Du kan ikke endre denne brukeren siden den er avgjørende for hele programmet"
-
-#: kallithea/controllers/admin/my_account.py:129
+"Du kan ikke endre denne brukeren siden den er avgjørende for hele "
+"programmet"
+
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Kontoen din ble oppdatert"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, fuzzy, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Feil inntraff under oppdatering av brukeren %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Feil inntraff under oppdatering av brukerpassord"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, fuzzy, python-format
 msgid "Added email %s to user"
 msgstr "La til e-postadressen %s for bruker"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 #, fuzzy
 msgid "An error occurred during email saving"
 msgstr "Feil inntraff under lagring av e-postadresse"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 #, fuzzy
 msgid "Removed email from user"
 msgstr "Fjernet e-postadresse fra bruker"
 
-#: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
 msgid "API key successfully created"
 msgstr "API-nøkkel opprettet"
 
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
 msgid "API key successfully reset"
 msgstr "API-nøkkel tilbakestilt"
 
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "API-nøkkel slettet"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "LEs"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Skriv"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -627,1902 +649,1411 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Admin"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Avskrudd"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Tillatt med manuell kontoaktivering"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Tillatt med automatisk kontoaktivering"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Manuell aktivering av ekstern konto"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Automatisk aktivering av ekstern konto"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Avskrudd"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Tillatt med manuell kontoaktivering"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Tillatt med automatisk kontoaktivering"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Manuell aktivering av ekstern konto"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Automatisk aktivering av ekstern konto"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Påskrudd"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 #, fuzzy
 msgid "Global permissions updated successfully"
 msgstr "Globale tilganger oppdatert"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Feil inntraff under oppdatering av tilganger"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Feil inntraff under opprettelse av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Opprettet pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Oppdaterte pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Feil inntraff under oppdatering av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Denne gruppen inneholder %s pakkebrønner og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Denne grunnen inneholder %s undergrupper og kan ikke slettes"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Fjernet pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Feil inntraff under sletting av pakkebrønnsgruppen %s"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 #, fuzzy
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Kan ikke tilbakekalle egen administratortilgang"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Pakkebrønnsgruppetilganger oppdatert"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "En feil inntraff under tilbakekalling av tilgang"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Feil under opprettelse av pakkebrønnen %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Opprettet pakkebrønnen %s fra %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "Forgrenet pakkebrønnen %s som %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Opprettet pakkebrønnen %s"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Pakkebrønnen %s ble oppdatert"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Feil under oppdatering av pakkebrønnen %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "Avhektet %s forgreninger"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "Slettet %s forgreninger"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Slettet pakkebrønnen %s"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "Kan ikke slette pakkebrønne %s, som fremdeles har forgreninger"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "En feil inntraff under sletting av %s"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Pakkebrønnstilganger oppdatert"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr "Feil inntraff under opprettelse av felt"
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Feil inntraff under fjerning av felt"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Ikke en forgrening --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Oppdaterte pakkebrønnssynlighet i offentlig loggbok"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 #, fuzzy
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
-"En feil inntraff under innlemmelse av denne pakkebrønnen i offentlig loggbok"
-
-#: kallithea/controllers/admin/repos.py:512
+"En feil inntraff under innlemmelse av denne pakkebrønnen i offentlig "
+"loggbok"
+
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Ingenting"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Markerte pakkebrønnen %s som en forgrening av %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "En feil inntraff under denne operasjonen"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "Pakkebrønnen har blitt låst"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "Pakkebrønnen har blitt låst opp"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "En feil inntraff under opplåsning"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
+msgstr "Oppdaterte VCS-innstillinger"
+
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
 msgstr ""
 
 #: kallithea/controllers/admin/settings.py:174
-msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:383
+msgstr "Skriv inn e-postadresse"
+
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Ingen data klar enda"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
-msgstr ""
-
-#: kallithea/controllers/admin/user_groups.py:163
+msgstr "Opprettet brukergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
-msgstr ""
-
-#: kallithea/controllers/admin/user_groups.py:224
+msgstr "Oppdaterte brukergruppe %s"
+
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
+#, fuzzy
 msgid "Successfully deleted user group"
-msgstr ""
-
-#: kallithea/controllers/admin/user_groups.py:247
+msgstr "Brukergruppe slettet"
+
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
-#, python-format
-msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
-msgstr ""
-
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/helpers.py:1264
+#, python-format
+msgid ""
+"%s repository is not mapped to db perhaps it was created or renamed from "
+"the filesystem please run the application again in order to rescan "
+"repositories"
+msgstr ""
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
-msgstr ""
-
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+msgstr "Lukker"
+
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
-#, python-format
-msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s"
-msgstr ""
-
-#: kallithea/model/user.py:266
-#, python-format
-msgid "User \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
-#, python-format
-msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:263
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
-#, python-format
-msgid "The password to your account %s has been changed using password reset form."
-msgstr ""
-
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/user.py:414
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:97
+msgstr "Brukernavnet \"%(username)s\" finnes allerede"
+
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
-msgstr ""
-
-#: kallithea/model/validators.py:99
-msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+msgstr "Brukernavnet \"%(username)s\" kan ikke brukes"
+
+#: kallithea/model/validators.py:78
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
-msgstr ""
-
-#: kallithea/model/validators.py:153
+msgstr "Ugyldig brukergruppenavn"
+
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:155
-msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
-msgstr ""
-
-#: kallithea/model/validators.py:193
+msgstr "Brukergruppen \"%(usergroup)s\" finnes allerede"
+
+#: kallithea/model/validators.py:136
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
-msgstr ""
-
-#: kallithea/model/validators.py:285
+msgstr "Ugyldig gammelt passord"
+
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
-msgstr ""
-
-#: kallithea/model/validators.py:300
+msgstr "Passordene samsvarer ikke"
+
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
+msgstr "Ugyldig brukernavn eller passord"
+
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
 msgstr ""
 
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
-msgstr ""
-
-#: kallithea/model/validators.py:466
-msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL"
-msgstr ""
-
-#: kallithea/model/validators.py:489
+msgstr "Ugyldig pakkebrønnsnettadresse"
+
+#: kallithea/model/validators.py:420
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
-msgstr ""
-
-#: kallithea/model/validators.py:705
+msgstr "Dette er ikke en gyldig sti"
+
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
-msgstr ""
-
-#: kallithea/model/validators.py:725
-#, python-format
+msgstr "E-postadressen er allerede i bruk"
+
+#: kallithea/model/validators.py:682
+#, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
-msgstr ""
-
-#: kallithea/model/validators.py:762
-msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
-msgstr ""
-
-#: kallithea/model/validators.py:774
+msgstr "Fant ikke e-postadressen \"%(email)s\""
+
+#: kallithea/model/validators.py:719
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
-msgstr ""
-
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+msgstr "Skriv inn en gyldig IPv4- eller IPv6-adresse"
+
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
-msgstr ""
-
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
+msgstr "Om"
+
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
-msgstr ""
-
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
+msgstr "Legg til pakkebrønn"
+
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr "Legg til pakkebrønnsgruppe"
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
-msgstr ""
-
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+msgstr "Rediger pakkebrønnsgruppe"
+
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Pakkebrønn"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
-msgstr ""
-
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+msgstr "Beskrivelse"
+
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
-msgstr ""
-
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+msgstr "Siste endring"
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
-msgstr ""
-
-#: kallithea/templates/index_base.html:132
+msgstr "Tips"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
-msgstr ""
-
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+msgstr "Eier"
+
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
-msgstr ""
-
-#: kallithea/templates/login.html:13
+msgstr "Logg inn"
+
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
-msgstr ""
-
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
+msgstr "Logg inn på %s"
+
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
-msgstr ""
-
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+msgstr "Brukernavn"
+
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
-msgstr ""
+msgstr "Passord"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+#, fuzzy
+msgid "Stay logged in after browser restart"
+msgstr "Forbli innlogget etter omstart av nettleser"
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
-msgstr ""
-
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+msgstr "Glemt passordet ditt?"
+
+#: kallithea/templates/login.html:55
+#, fuzzy
 msgid "Don't have an account ?"
-msgstr ""
-
-#: kallithea/templates/login.html:59
+msgstr "Har du ikke en konto?"
+
+#: kallithea/templates/login.html:62
+#, fuzzy
 msgid "Sign In"
-msgstr ""
+msgstr "Logg inn"
 
 #: kallithea/templates/password_reset.html:5
 msgid "Password Reset"
-msgstr ""
-
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+msgstr "Passordstilbakestilling"
+
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
-msgstr ""
-
-#: kallithea/templates/password_reset.html:14
+msgstr "Tilbakestill passordet ditt til %s"
+
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
-msgstr ""
-
-#: kallithea/templates/password_reset.html:25
+msgstr "Tilbakestill passordet ditt"
+
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
-msgstr ""
-
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+msgstr "E-postadresse"
+
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
+#, fuzzy
 msgid "Captcha"
-msgstr ""
-
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
+msgstr "CAPTCHA"
 
 #: kallithea/templates/password_reset.html:47
-msgid "A password reset link will be sent to the specified email address if it is registered in the system."
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:19
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
-msgid "Note that you must use the same browser session for this as the one used to request the password reset."
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:48
+msgstr "Nytt passord"
+
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:56
+msgstr "Bekreft nytt passord"
+
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
-msgstr ""
-
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+msgstr "Bekreft"
+
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
-msgstr ""
-
-#: kallithea/templates/register.html:12
+msgstr "Registrer"
+
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
-msgstr ""
-
-#: kallithea/templates/register.html:42
+msgstr "Registrer på %s"
+
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
-msgstr ""
-
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+msgstr "Skriv inn passord på ny"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
-msgstr ""
-
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+msgstr "Fornavn"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
-msgstr ""
-
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+msgstr "Etternavn"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
-msgstr ""
-
-#: kallithea/templates/register.html:92
+msgstr "E-post"
+
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
-msgstr ""
+msgstr "Filter"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2530,144 +2061,135 @@
 msgstr[1] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
-msgstr ""
+msgstr "Dato"
 
 #: kallithea/templates/admin/admin_log.html:9
+#, fuzzy
 msgid "From IP"
-msgstr ""
-
-#: kallithea/templates/admin/admin_log.html:63
+msgstr "Fra IP"
+
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
-msgstr ""
+msgstr "Ingen handlinger enda"
 
 #: kallithea/templates/admin/auth/auth_settings.html:5
 msgid "Authentication Settings"
-msgstr ""
+msgstr "Identitetsbekreftelsesinnstillinger"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:28
+msgstr "Identitetsbekreftelse"
+
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:31
+msgstr "Identitetsbekreftelsesprogramtillegg"
+
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:33
-msgid "Comma-separated list of plugins; Kallithea will try user authentication in plugin order"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:34
+msgstr "Aktiverte programtillegg"
+
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:51
+msgstr "Tilgjengelige innebygde programtillegg"
+
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
-msgstr ""
+msgstr "Programtillegg"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
-msgstr ""
+msgstr "Lagre"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
+msgstr "Type"
+
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr "Privat pakkebrønn"
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid "Private repositories are only visible to people explicitly added as collaborators."
-msgstr ""
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr "Skru på statistikk"
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
+msgstr "Skru på nedlastinger"
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+msgstr "Skru på nedlastingsmeny på sammendragsside."
+
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+msgstr "Skru på låsing"
+
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2676,54 +2198,57 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
-#, python-format
-msgid "Gist was update since you started editing. Copy your changes and click %(here)s to reload new version."
-msgstr ""
-
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was update since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
-msgstr ""
-
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+msgstr "Utløper"
+
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
-msgstr ""
+msgstr "Aldri"
 
 #: kallithea/templates/admin/gists/edit.html:145
 msgid "Update Gist"
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
-msgstr ""
+msgstr "Avbryt"
 
 #: kallithea/templates/admin/gists/index.html:6
 #: kallithea/templates/admin/gists/index.html:16
@@ -2744,16 +2269,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
-msgstr ""
-
-#: kallithea/templates/admin/gists/index.html:74
+msgstr "Opprettet"
+
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2762,45 +2287,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2810,182 +2335,197 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
+msgstr "Nettadresse"
+
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:56
+msgstr "Slett"
+
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr "Rediger"
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:35
+msgstr "Min konto"
+
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:36
+msgstr "Profil"
+
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:38
+msgstr "E-postadresser"
+
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:39
+msgstr "API-nøkler"
+
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+msgstr "Eide pakkebrønner"
+
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+msgstr "Fjern"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+msgstr "Ny API-nøkkel"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+msgstr "Legg til"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -2993,107 +2533,70 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+msgstr "Nåværende passord"
+
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+msgstr "Nytt passord"
+
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
-msgstr ""
+msgstr "Bekreft nytt passord"
+
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr "Nåværende IP"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr "Gravatar"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
+msgstr "Endre %s-avatar på"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
+msgstr "Avatarer er avskrudd"
 
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr ""
+msgstr "Pakkebrønner du eier"
+
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Navn"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
+msgstr "Pakkebrønner du holder oppsyn med"
 
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
-msgstr ""
+msgstr "Forvalgte tilganger"
 
 #: kallithea/templates/admin/permissions/permissions.html:28
 #: kallithea/templates/admin/settings/settings.html:29
@@ -3103,153 +2606,175 @@
 #: kallithea/templates/admin/permissions/permissions.html:29
 #: kallithea/templates/admin/users/user_edit.html:32
 msgid "IP Whitelist"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+msgstr "IP-hvitliste"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
-#, python-format
-msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
-msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+msgstr "Anonym tilgang"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr "Tillat anonym tilgang"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+msgid ""
+"All default permissions on each repository will be reset to chosen "
+"permission, note that all custom default permission on repositories will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
-msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
+msgid ""
+"All default permissions on each repository group will be reset to chosen "
+"permission, note that all custom default permission on repository groups "
+"will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:40
-msgid "Apply to all existing repository groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
-msgid "Permissions for the Default user on new repository groups."
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid ""
+"All default permissions on each user group will be reset to chosen "
+"permission, note that all custom default permission on user groups will "
+"be lost"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
-msgid "User group"
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:53
-msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
-msgid "Apply to all existing user groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
-msgid "Permissions for the Default user on new user groups."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
-msgid "With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+msgid ""
+"With this, write permission to a repository group allows creating "
+"repositories inside that group. Without this, group write permissions "
+"mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3258,30 +2783,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3306,12 +2830,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3322,16 +2846,32 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3339,96 +2879,97 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
-msgid "Set or revoke permission to all children of that group, including non-private repositories and other groups if selected."
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
-msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
+msgid ""
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
+msgid "Repository group %s"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
-msgid "Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+msgid ""
+"Optional: URL of a remote repository. If set, the repository will be "
+"created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
-msgid "Default revision for files page, downloads, full text search index and readme generation"
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
@@ -3442,11 +2983,15 @@
 
 #: kallithea/templates/admin/repos/repo_creating.html:27
 #, python-format
-msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name"
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:39
-msgid "We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator."
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
@@ -3454,22 +2999,22 @@
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3478,225 +3023,244 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
-msgid "All actions done in this repository will be visible to everyone in the public journal."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
+#, python-format
+msgid "Locked by %s on %s"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:60
-msgid "Confirm to lock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
-msgid "Lock Repository"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
-msgid "Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
+msgid ""
+"Force locking on the repository. Works only when anonymous access is "
+"disabled. Triggering a pull locks the repository.  The user who is "
+"pulling locks the repository; only the user who pulled and locked it can "
+"unlock it by doing a push."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
-msgid "The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it."
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:4
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
-msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again."
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-msgid "Confirm to pull changes from remote repository."
-msgstr ""
-
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
-"In case this repository is renamed or moved into another group the repository URL changes.\n"
-"                               Using the above permanent URL guarantees that this repository always will be accessible on that URL.\n"
-"                               This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
-msgid "Optional: URL of a remote repository. If set, the repository can be pulled from this URL."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
+msgid ""
+"Optional: URL of a remote repository. If set, the repository can be "
+"pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3712,7 +3276,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3733,7 +3297,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3745,336 +3309,374 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
-msgid "HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
-msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
-msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:17
 msgid "Custom Hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
-msgid "Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem."
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid "Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid "If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
-msgid "This option completely reindexeses all of the repositories for proper fulltext search capabilities."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:21
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid "Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
-msgid "Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial."
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_vcs.html:64
-msgid "Location of repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:69
-msgid "Click to unlock. You must restart Kallithea in order to make this setting take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
-msgid "Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:8
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
-"                                                        The following variables of the URL will be replaced accordingly.\n"
-"                                                        {scheme}    'http' or 'https' sent from running Kallithea server,\n"
-"                                                        {email}     user email,\n"
-"                                                        {md5email}  md5 hash of the user email (like at gravatar.com),\n"
-"                                                        {size}      size of the image that is expected from the server application,\n"
-"                                                        {netloc}    network location/server host of running Kallithea server"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:42
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.\n"
-"                                                        The following variables are available:\n"
-"                                                        {scheme} 'http' or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current user username,\n"
-"                                                        {netloc} network location/server host of running Kallithea server,\n"
-"                                                        {repo}   full repository name,\n"
-"                                                        {repoid} ID of repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
-msgid "Number of items displayed in the main page dashboard before pagination is shown."
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
-msgid "Number of items displayed in the admin pages grids before pagination is shown."
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:75
-msgid "Icons"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:80
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:111
-msgid "Parses meta tags from the repository description field and turns them into colored tags."
-msgstr ""
-
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4093,13 +3695,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4108,15 +3710,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4130,17 +3732,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4159,12 +3761,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4173,7 +3775,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4182,24 +3784,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4207,220 +3801,219 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
-msgid "Show recent activity"
-msgstr ""
-
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
-msgid "Public journal"
-msgstr ""
-
-#: kallithea/templates/base/base.html:233
-msgid "Show public gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:234
-msgid "Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:238
-msgid "All Public Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:240
-msgid "My Public Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:241
-msgid "My Private Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:246
-msgid "Search in repositories"
-msgstr ""
-
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
-msgid "My Pull Requests"
-msgstr ""
-
-#: kallithea/templates/base/base.html:289
-msgid "Not Logged In"
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
 msgstr ""
 
 #: kallithea/templates/base/base.html:296
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:313
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:315
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:316
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:321
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:360
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:19
-#, python-format
-msgid "Select to inherit global settings, IP whitelist and permissions from the %s."
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:15
+#, python-format
+msgid ""
+"Select to inherit global settings, IP whitelist and permissions from the "
+"%s."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4429,227 +4022,171 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-msgid "Open New Pull Request from {0}"
+msgid "Add Another Comment"
 msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
+msgid "Open New Pull Request from {0}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Velg endringssett"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4665,7 +4202,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4679,38 +4216,60 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%s committed on %s"
+msgid "%s comments"
+msgstr "%s sendte inn %s"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4718,86 +4277,78 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
-msgstr ""
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "%s committed on %s"
+msgid "comment"
+msgstr "%s sendte inn %s"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 msgid "on pull request"
@@ -4815,171 +4366,166 @@
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr ""
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, python-format
+msgid "Unknown operation: %r"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -4990,123 +4536,169 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+msgid "Status change:"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
-msgid "Should you not be able to use the link above, please type the following code into the password reset form"
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:12
-msgid "If it weren't you who requested the password reset, just disregard this message."
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request updated"
+msgid "Pull request"
+msgstr "Innsendingsforespørsel oppdatert"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5125,7 +4717,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5135,72 +4727,73 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5210,7 +4803,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5223,24 +4816,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5248,50 +4837,58 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5312,12 +4909,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5326,35 +4923,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5364,40 +4961,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5406,31 +5003,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5442,6 +5044,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5465,7 +5071,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5492,102 +5098,95 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
-msgid "This is just a range of changesets and doesn't have a target or a real merge ancestor."
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5603,35 +5202,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5645,29 +5248,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5677,80 +5280,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5758,82 +5361,134 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr ""
 
+#: kallithea/templates/summary/summary.html:177
+msgid "Quick Start"
+msgstr ""
+
 #: kallithea/templates/summary/summary.html:188
-msgid "Quick Start"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:202
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "Det er ingen indeks å søke i. Kjør en annen whoosh-indekserer"
+
+#~ msgid ""
+#~ "Changing status on a changeset associated with a closed pull request "
+#~ "is not allowed"
+#~ msgstr ""
+#~ "Endring av status for et endringssett tilknyttet en lukket "
+#~ "innhentingsforespørsel tillates ikke"
+
+#, fuzzy
+#~ msgid "You have successfully registered into Kallithea"
+#~ msgstr "Du har registrert deg på Kallithea"
+
+#~ msgid "Missing changesets since the previous pull request:"
+#~ msgstr "Manglende endringssett siden forrige innsendingsforespørsel:"
+
+#~ msgid "New changesets on %s %s since the previous pull request:"
+#~ msgstr "Nye endringssett på %s %s siden forrige innsendingsforespørsel:"
+
+#~ msgid "Ancestor didn't change - show diff since previous version:"
+#~ msgstr "Opphav endret seg ikke - vis diff siden forrige versjon:"
+
+#~ msgid ""
+#~ "This pull request is based on another %s revision and there is no "
+#~ "simple diff."
+#~ msgstr ""
+#~ "Denne innsendingsforespørselen er basert på en annen %s-revisjon, og "
+#~ "det finnes ingen enkel diff."
+
+#~ msgid "No changes found on %s %s since previous version."
+#~ msgstr "Ingen endringer funnet på %s %s siden forrige versjon."
+
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Lukket, erstattet av %s."
+
+#~ msgid "Pull request update created"
+#~ msgstr "Innsendingsforespørsel opprettet"
+
+#~ msgid "This pull request can be updated with changes on %s:"
+#~ msgstr ""
+#~ "Denne innsendingsforespørselen kan ikke oppdateres med endringer på %s:"
+
+#~ msgid "No changesets found for updating this pull request."
+#~ msgstr ""
+#~ "Ingen endringssett funnet for oppdatering av denne "
+#~ "innsendingsforespørselen."
+
+#~ msgid "Git pull requests don't support updates yet."
+#~ msgstr "Git-innsendingsforespørsler støtter ikke oppdateringer enda."
+
+#~ msgid "Closing."
+#~ msgstr "Lukker."
+
+#~ msgid "An error occurred during creation of field"
+#~ msgstr "Feil inntraff under opprettelse av felt"
--- a/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,457 +7,479 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2017-01-10 07:35+0000\n"
-"Last-Translator: Sam Jaques <sam.jaques@me.com>\n"
-"Language-Team: Dutch (Belgium) "
-"<https://hosted.weblate.org/projects/kallithea/stable/nl_BE/>\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2017-06-18 16:22+0000\n"
+"Last-Translator: Thomas De Schampheleire <patrickdepinguin@gmail.com>\n"
+"Language-Team: Dutch (Belgium) <https://hosted.weblate.org/projects/"
+"kallithea/kallithea/nl_BE/>\n"
 "Language: nl_BE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 2.11-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"X-Generator: Weblate 2.15-dev\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Er zijn nog geen changesets"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Geen"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(gesloten)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Toon witruimtes"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 #, fuzzy
 msgid "Ignore whitespace"
 msgstr "Negeer witruimtes"
 
-#: kallithea/controllers/changeset.py:169
-#, fuzzy, python-format
+#: kallithea/controllers/changeset.py:163
+#, python-format
 msgid "Increase diff context to %(num)s lines"
-msgstr "vergroot de diff context met %(num)s lijnen"
-
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+msgstr "Vergroot de diff context tot %(num)s lijnen"
+
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "Selecteer de changeset"
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Deze revisie bestaat niet in deze repository"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-"Het is niet toegestaan de status te wijzigen van een changeset "
-"geassocieerd met een gesloten pull request"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Selecteer de changeset"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr ""
+
+#: kallithea/controllers/compare.py:72
+#, fuzzy
+#| msgid "Cannot compare repositories without using common ancestor"
+msgid "Cannot compare repositories of different types"
+msgstr ""
+"Kan geen repositories vergelijken zonder een gemeenschappelijke voorouder "
+"te gebruiken"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
-
-#: kallithea/controllers/error.py:71
+"Kan geen repositories vergelijken zonder een gemeenschappelijke voorouder "
+"te gebruiken"
+
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "Geen antwoord"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
-msgstr "Onbekende fout"
-
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+msgstr "Ongekende fout"
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"De aanvraag kon niet door de server begrepen worden wegens incorrecte "
+"syntax."
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
-msgstr ""
-
-#: kallithea/controllers/error.py:105
+msgstr "Ongeautoriseerde toegang tot resource"
+
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
-msgstr "Je hebt geen toegang tot deze pagina"
-
-#: kallithea/controllers/error.py:107
+msgstr "U hebt geen permissie om deze pagina te bekijken"
+
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
-msgstr ""
-
-#: kallithea/controllers/error.py:109
+msgstr "De resource kon niet gevonden worden"
+
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
-
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr ""
-
-#: kallithea/controllers/feed.py:56
+"De server kon de aanvraag niet voldoen wegens een onverwachte toestand."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s committeerde op %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "De changeset was te groot en werd afgekort..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
-msgstr ""
+msgstr "%s %s feed"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Veranderingen in repository %s"
 
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr "Klik hier om een nieuw bestand toe te voegen"
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:193
+msgstr "Er zijn nog geen bestanden. %s"
+
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+msgstr "%s op %s"
+
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:317
+msgstr "Deze repository is gelocket door %s op %s"
+
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
-
-#: kallithea/controllers/files.py:328
+"Men kan enkel bestanden verwijderen als de revisie een geldige branch is"
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:350
+msgstr "Bestand %s verwijderd via Kallithea"
+
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+msgstr "Bestand %s succesvol verwijderd"
+
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
-msgstr ""
-
-#: kallithea/controllers/files.py:377
+msgstr "Er trad een fout op tijdens het committeren"
+
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
-
-#: kallithea/controllers/files.py:391
+"Men kan enkel bestanden wijzigen als de revisie een geldige branch is"
+
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:407
+msgstr "Bestand %s gewijzigd via Kallithea"
+
+#: kallithea/controllers/files.py:402
 msgid "No changes"
-msgstr ""
-
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+msgstr "Geen wijzigingen"
+
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:443
+msgstr "Succesvol gecommitteerd naar %s"
+
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:464
+msgstr "Bestand toegevoegd via Kallithea"
+
+#: kallithea/controllers/files.py:459
 msgid "No content"
-msgstr ""
-
-#: kallithea/controllers/files.py:468
+msgstr "Geen inhoud"
+
+#: kallithea/controllers/files.py:463
 msgid "No filename"
-msgstr ""
-
-#: kallithea/controllers/files.py:493
+msgstr "Geen bestandsnaam"
+
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
-msgstr ""
-
-#: kallithea/controllers/files.py:526
+msgstr "De locatie moet een relatief pad zijn en mag geen .. bevatten"
+
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
-msgstr ""
-
-#: kallithea/controllers/files.py:537
+msgstr "Downloads uitgeschakeld"
+
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:539
+msgstr "Ongekende revisie %s"
+
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
-msgstr ""
-
-#: kallithea/controllers/files.py:541
+msgstr "Lege repository"
+
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
-msgstr ""
-
-#: kallithea/controllers/files.py:771
+msgstr "Ongekende archieftype"
+
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
-msgstr ""
-
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+msgstr "Changesets"
+
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
-msgstr ""
-
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+msgstr "Branches"
+
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
-msgstr ""
-
-#: kallithea/controllers/forks.py:186
+msgstr "Tags"
+
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
-msgstr ""
-
-#: kallithea/controllers/home.py:84
+msgstr "Er is een fout opgetreden tijdens het forken van de repository %s"
+
+#: kallithea/controllers/home.py:78
 msgid "Groups"
-msgstr ""
-
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+msgstr "Groepen"
+
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
-msgstr ""
-
-#: kallithea/controllers/home.py:130
+msgstr "Repositories"
+
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
-msgstr ""
-
-#: kallithea/controllers/home.py:136
+msgstr "Branch"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Gesloten branches"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
-msgstr ""
-
-#: kallithea/controllers/home.py:142
+msgstr "Tag"
+
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
-msgstr ""
-
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+msgstr "Bladwijzer"
+
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
-msgstr ""
-
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+msgstr "Publiek logboek"
+
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
-msgstr ""
-
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+msgstr "Logboek"
+
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
-msgstr ""
-
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+msgstr "Incorrecte captcha"
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "U bent succesvol geregistreerd bij %s"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr ""
-
-#: kallithea/controllers/login.py:246
+msgstr "Een paswoordherstel bevestigingscode is verzonden"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
-msgstr ""
-
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+msgstr "Ongeldig paswoordherstel token"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:124
-#, fuzzy, python-format
+msgstr "Paswoord succesvol aangepast"
+
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:173
+msgstr "Changeset"
+
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:174
+msgstr "Bijzonder"
+
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:363
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:352
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-msgid "Error occurred while creating pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
 #, python-format
 msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
-msgid "Pull request updated"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:529
-msgid "Successfully deleted pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:595
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:597
-msgid "This pull request has been closed and can not be updated."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -469,149 +491,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -619,622 +643,618 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during repository forking %s"
+msgid "An error occurred during creation of field: %r"
+msgstr "Er is een fout opgetreden tijdens het forken van de repository %s"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
-#, fuzzy
 msgid "Repository has been locked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
-#, fuzzy
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "Selecteer de changeset"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1242,1158 +1262,652 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
-#, fuzzy
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
-#, fuzzy
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr ""
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2401,177 +1915,146 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
-#, fuzzy, python-format
+#: kallithea/templates/journal/journal.html:13
+#, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
 msgstr[0] ""
 msgstr[1] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2579,7 +2062,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2588,108 +2071,108 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid ""
-"Private repositories are only visible to people explicitly added as "
-"collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2698,45 +2181,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2745,7 +2228,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2768,16 +2252,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2786,45 +2270,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2834,182 +2318,197 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -3017,105 +2516,68 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr ""
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3129,167 +2591,173 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid ""
-"Note: This will also give all users API access to create repositories "
-"everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
-#, fuzzy, python-format
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3298,30 +2766,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3346,12 +2813,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3362,16 +2829,34 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "Geen antwoord"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3379,102 +2864,94 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
-#, fuzzy
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
+msgid "Repository group %s"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3503,26 +2980,26 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3531,63 +3008,61 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
+#, python-format
+msgid "Locked by %s on %s"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:60
-msgid "Confirm to lock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
-msgid "Lock Repository"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3595,33 +3070,32 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
-#, fuzzy
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
-#, fuzzy, python-format
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3632,105 +3106,107 @@
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Empty repository"
+msgid "Fork of repository"
+msgstr "Lege repository"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-msgid "Confirm to pull changes from remote repository."
-msgstr ""
-
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3740,33 +3216,38 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3782,7 +3263,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3803,7 +3284,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3815,276 +3296,266 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:64
-msgid "Location of repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:69
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4101,92 +3572,100 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repositories"
+msgid "Repository page size"
+msgstr "Repositories"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4205,13 +3684,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4220,15 +3699,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4242,17 +3721,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4271,12 +3750,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4285,7 +3764,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4294,24 +3773,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4319,222 +3790,219 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr ""
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4543,231 +4011,174 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-#, fuzzy
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-#, fuzzy
-msgid "Open New Pull Request from {0}"
-msgstr "Statuswijziging -> %s"
+msgid "Add Another Comment"
+msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-#, fuzzy
-msgid "Show Selected Changesets {0} &rarr; {1}"
-msgstr "Selecteer de changeset"
+msgid "Start following this repository"
+msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
+#, fuzzy
+msgid "Open New Pull Request from {0}"
+msgstr "Statuswijziging -> %s"
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr ""
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+#, fuzzy
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr "Selecteer de changeset"
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Selecteer een changeset"
+
+#: kallithea/templates/base/root.html:52
 #, fuzzy
 msgid "Specify changeset"
 msgstr "Selecteer de changeset"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4783,7 +4194,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4797,38 +4208,60 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Selecteer de changeset"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "No comments."
+msgid "%s comments"
+msgstr "Geen kommentaar."
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4836,86 +4269,78 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
-msgstr ""
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "No comments."
+msgid "comment"
+msgstr "Geen kommentaar."
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -4932,178 +4357,176 @@
 msgstr "Selecteer de changeset"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#, fuzzy
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Statuswijziging -> %s"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "Selecteer de changeset"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Statuswijziging -> %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(gesloten)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "Selecteer de changeset"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(gesloten)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Ongekende revisie %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Geen bestandsnaam"
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
-#, fuzzy, python-format
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -5114,127 +4537,177 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Set changeset status"
+msgid "Comment on Changeset \"%s\""
+msgstr "Selecteer de changeset"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset"
+msgid "Changeset on"
+msgstr "Changeset"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Branch"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Statuswijziging -> %s"
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "on pull request"
+msgid "Pull request"
+msgstr "Statuswijziging -> %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "on pull request"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Statuswijziging -> %s"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5253,7 +4726,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5263,73 +4736,74 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "Selecteer de changeset"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5339,7 +4813,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5352,24 +4826,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5377,50 +4847,58 @@
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5441,12 +4919,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5455,35 +4933,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5493,40 +4971,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5535,31 +5013,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5571,6 +5054,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5594,7 +5081,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5607,7 +5094,7 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
 msgstr ""
 
@@ -5621,107 +5108,96 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
-#, fuzzy
-msgid "Remove reviewer"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
-msgid "Type name of reviewer to add"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
-#, fuzzy
-msgid "Potential Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
-msgid "Click to add the repository owner as reviewer:"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
 msgid "Save Changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
 #, fuzzy
 msgid "Cancel Changes"
 msgstr "Selecteer de changeset"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5729,7 +5205,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Open New Pull Request from {0}"
 msgid "Pull Requests from '%s'"
 msgstr "Statuswijziging -> %s"
 
@@ -5738,35 +5213,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5780,29 +5259,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5812,80 +5291,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5893,450 +5372,83 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "Statuswijziging -> %s"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
-#~ msgid "No comments."
-#~ msgstr "Geen kommentaar."
-
-#~ msgid "public journal"
-#~ msgstr ""
-
-#~ msgid "journal"
-#~ msgstr ""
-
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Cannot delete %s it still contains attached forks"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked"
-#~ msgstr ""
-
-#~ msgid "Locked"
-#~ msgstr ""
-
-#~ msgid "Repository has been %s"
-#~ msgstr ""
-
-#~ msgid "You can't edit this user"
-#~ msgstr ""
-
-#~ msgid "compare view"
-#~ msgstr ""
-
-#~ msgid "fork name %s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s"
-#~ msgstr ""
-
-#~ msgid "No Files"
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
-#~ msgid "You can't remove this user since it's crucial for entire application"
-#~ msgstr ""
-
-#~ msgid "Username \"%(username)s\" is forbidden"
-#~ msgstr ""
-
-#~ msgid "invalid password"
-#~ msgstr ""
-
-#~ msgid "invalid user name"
-#~ msgstr ""
-
-#~ msgid "Your account is disabled"
-#~ msgstr ""
-
-#~ msgid "Repository name %(repo)s is disallowed"
-#~ msgstr ""
-
-#~ msgid "invalid clone URL"
-#~ msgstr ""
-
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "This email address is already taken"
-#~ msgstr ""
-
-#~ msgid "email \"%(email)s\" does not exist."
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
-#~ msgid "Defaults"
-#~ msgstr ""
-
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
-#~ msgid "Watched"
-#~ msgstr ""
-
-#~ msgid "My Permissions"
-#~ msgstr ""
-
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
-#~ msgid "delete"
-#~ msgstr ""
-
-#~ msgid "current IP"
-#~ msgstr ""
-
-#~ msgid "Permissions Administration"
-#~ msgstr ""
-
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Repository creation"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
-#~ msgid "Default User Permissions Overview"
-#~ msgstr ""
-
-#~ msgid "none"
-#~ msgstr ""
-
-#~ msgid "read"
-#~ msgstr ""
-
-#~ msgid "write"
-#~ msgstr ""
-
-#~ msgid "admin"
-#~ msgstr ""
-
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "default"
-#~ msgstr ""
-
-#~ msgid "revoke"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr ""
-
-#~ msgid "private repository"
-#~ msgstr ""
-
-#~ msgid "Remote URL"
-#~ msgstr ""
-
-#~ msgid "Pull Changes from Remote Location"
-#~ msgstr ""
-
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
-#~ msgid "edit"
-#~ msgstr ""
-
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
-#~ msgid "Default permissions"
-#~ msgstr ""
-
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "merge"
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Comment from pull request"
-#~ msgstr ""
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
-#~ msgid "Mimetype"
-#~ msgstr ""
-
-#~ msgid "My Repos"
-#~ msgstr ""
-
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
-#~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid "Your password reset link was sent"
-#~ msgstr ""
-
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
-#~ msgid "Created by"
-#~ msgstr ""
-
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "Changeset not found"
-#~ msgstr ""
-
-#~ msgid "Non-admins can can fork repositories"
-#~ msgstr ""
-
-#~ msgid "Pull Requests from %s'"
-#~ msgstr ""
-
+#, fuzzy
+#~| msgid "Empty repository"
+#~ msgid "Repository Group"
+#~ msgstr "Lege repository"
--- a/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/pl/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -11,471 +11,483 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2015-04-04 09:08+0200\n"
-"Last-Translator: Andrew Shadura <andrew@shadura.me>\n"
-"Language-Team: Polish "
-"<https://hosted.weblate.org/projects/kallithea/kallithea/pl/>\n"
+"Last-Translator: Andrej Shadura <andrew@shadura.me>\n"
+"Language-Team: Polish <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/pl/>\n"
 "Language: pl\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==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
-"|| n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n"
+"%100<10 || n%100>=20) ? 1 : 2;\n"
 "X-Generator: Weblate 2.3-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Brak zestawienia zmian"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Brak"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(zamknięty)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "pokazuj spacje"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignoruj pokazywanie spacji"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "Zagłosuj na żądanie na grupę zmian"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-"Zmiana statusu na grupy zmian powiązania łączy zamkniętego wniosku jest "
-"niedozwolona"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Wybrane zmiany"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Potwierdź blokowanie repozytorium"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "rewizja"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr ""
 "Wniosek nie może być rozumiany przez serwer z powodu zniekształconej "
 "składni."
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Nieautoryzowany dostęp do zasobów"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nie masz uprawnień do przeglądania tej strony"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Zasób nie został znaleziony"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
-"Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie"
-" żądania."
-
-#: kallithea/controllers/feed.py:55
+"Serwer napotkał niespodziewany warunek, który uniemożliwia jej spełnienie "
+"żądania."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s zakomitowal w %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Lista zmian była zbyt duża i została ucięta..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr "%s %s zasilać"
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Zmiany w %s repozytorium"
 
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr "%s %s zasilać"
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Lista zmian była zbyt duża i została ucięta..."
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s zakomitowal w %s"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Kliknij tutaj, by dodać nowy plik"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, fuzzy, python-format
 msgid "%s at %s"
 msgstr "w %s i %s"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Repozytorium zostało zablokowane przez %s na %s"
 
-#: kallithea/controllers/files.py:317
-#, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
+#: kallithea/controllers/files.py:312
+#, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Można tylko edytować pliki z rewizji obecnej gałęzi "
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Wystąpił błąd w trakcie zatwierdzania"
 
-#: kallithea/controllers/files.py:377
-#, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
+#: kallithea/controllers/files.py:372
+#, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Można tylko edytować pliki z rewizji obecnej gałęzi "
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Edytowanie %s w Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Bez zmian"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Committ wykonany do %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Dodano %s poprzez Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Brak treści"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Brak nazwy pliku"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
-msgstr "Lokalizacja musi być ścieżką względną i nie może zawierać .. ścieżki"
-
-#: kallithea/controllers/files.py:526
+msgstr ""
+"Lokalizacja musi być ścieżką względną i nie może zawierać .. ścieżki"
+
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Pobieranie wyłączone"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Nieznana wersja %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Puste repozytorium"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Nieznany typ archiwum"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Różnice"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Gałęzie"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Etykiety"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "Wystąpił błąd podczas rozgałęzienia %s repozytorium"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Repozytoria"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "gałąź"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Zamknięte Gałęzie"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Tag"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Bookmark"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Dziennik Publiczny"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Dziennik"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Udało Ci się zarejestrować na stronie"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Udało Ci się zarejestrować w %s"
+
+#: kallithea/controllers/login.py:189
 #, fuzzy
 msgid "A password reset confirmation code has been sent"
 msgstr "Twój link zresetowania hasła został wysłany"
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 #, fuzzy
 msgid "Invalid password reset token"
 msgstr "łącze resetowania hasła"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zamknięty)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Grupy zmian"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "Specjalne"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "gałęzie"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Zakładki"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Brak opisu"
-
-#: kallithea/controllers/pullrequests.py:363
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr "Wystąpił błąd podczas prośby o połączenie gałęzi"
+
+#: kallithea/controllers/pullrequests.py:352
 msgid "Successfully opened new pull request"
 msgstr "Prośba o wykonanie połączenia gałęzi została wykonana prawidłowo"
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-msgid "Error occurred while creating pull request"
-msgstr "Wystąpił błąd podczas prośby o połączenie gałęzi"
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "Recenzje wniosków połączenia gałęzi"
+
+#: kallithea/controllers/pullrequests.py:403
 #, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
 #, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr "Recenzje wniosków połączenia gałęzi"
-
-#: kallithea/controllers/pullrequests.py:514
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Brak opisu"
+
+#: kallithea/controllers/pullrequests.py:432
 #, fuzzy
 msgid "Pull request updated"
 msgstr "Połączone gałęzie"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Prośba o skasowanie połączenia gałęzi została wykonana prawidłowo"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:615
+#: kallithea/controllers/pullrequests.py:543
 #, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-#, fuzzy
-msgid "No permission to change pull request status"
-msgstr "Zagłosuj na żądanie na grupę zmian"
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Zamknięcie."
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "Nieprawidłowe zapytania. Spróbuj zacytować go."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Nie ma szukanego indeksu. Proszę uruchomić indeksowanie whoosh"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Wystąpił błąd podczas operacji wyszukiwania."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 #, fuzzy
 msgid "No data ready yet"
 msgstr "Żadne dane nie zostały załadowane"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "Statystyki są wyłączone dla tego repozytorium"
 
@@ -487,153 +499,155 @@
 msgid "error occurred during update of auth settings"
 msgstr "wystapił błąd podczas uaktualniania ustawień autentykacji"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Domyślne ustawienia zostały pomyślnie zaktualizowane"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "wystąpił błąd podczas aktualizacji wartości domyślnych"
 
-#: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
 #, fuzzy
 msgid "Forever"
 msgstr "na zawsze"
 
-#: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
 msgid "5 minutes"
 msgstr "5 minut"
 
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 godzina"
+
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "1 godzina"
-
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 dzień"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 miesiąc"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Czas życia"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Wystąpił błąd podczas tworzenia git"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Usuń gist %s"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 #, fuzzy
 msgid "Unmodified"
 msgstr "Ostatnio modyfikowany"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Nie możesz edytować tego użytkownika ponieważ jest kluczowy dla całej "
 "aplikacji"
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Twoje konto zostało pomyślnie zaktualizowane"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "wystąpił błąd podczas aktualizacji użytkownika %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "Dodano e-mail %s do użytkownika"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Wystąpił błąd podczas zapisywania e-maila"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "Usunięto e-mail użytkownikowi"
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Odczyt"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Zapis"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -641,629 +655,629 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Administracja"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Wyłączone"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Dozwolona z ręczną aktywacją konta"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Dozwolona z automatyczną aktywacją konta"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Ręczna aktywacja nowego konta"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Automatyczna aktywacja nowego konta"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Wyłączone"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Dozwolona z ręczną aktywacją konta"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Dozwolona z automatyczną aktywacją konta"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Ręczna aktywacja nowego konta"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Automatyczna aktywacja nowego konta"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Włączone"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr "Globalne uprawnienia zaktualizowane poprawnie"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Wystąpił błąd podczas aktualizacji uprawnień"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Wystąpił błąd podczas tworzenia grupy repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Utworzono grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Zaktualizowano grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Wystąpił błąd podczas aktualizacji grupy repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Ta grupa zawiera %s repozytorium i nie może być usunięta"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Usunięto grupę repo %s"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Wystąpił błąd podczas usuwania z repozytorium grupy %s"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Nie można cofnąć zezwolenia dla admina jako admin"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Aktualizacja uprawnień grup repozytorium"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "Wystąpił błąd podczas cofania zezwolenia"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "utworzone repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "utworzone repozytorium %s z %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "Gałęzi %s w repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Utworzone repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Repozytorium %s zostało pomyślnie zaktualizowane"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Wystąpił błąd podczas aktualizacji repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "Oderwane rozgałęzienie %s"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "Usunięte repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Usunięte repozytorium %s"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, fuzzy, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "Nie można usunąć %s nadal zawiera załączniki rozgałęzienia"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Wystąpił błąd podczas usuwania %s"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Uprawnienia repozytorium zostały zaktualizowane"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
 msgstr "Wystąpił błąd podczas tworzenia użytkownika %s"
 
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Wystąpił błąd podczas zapisywania e-maila"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Brak rozgalezienia --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Zaktualizowano widoczność stron w publicznym dzienniku"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
-msgstr "Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym"
-
-#: kallithea/controllers/admin/repos.py:512
+msgstr ""
+"Wystąpił błąd podczas ustawiania tego repozytorium w dzienniku publicznym"
+
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Brak"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Oznaczono %s repo jako rozwidlenie %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "Wystąpił błąd podczas tej operacji"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 #, fuzzy
 msgid "Repository has been locked"
 msgstr "Repozytorium nie jest zablokowane"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 #, fuzzy
 msgid "Repository has been unlocked"
 msgstr "Repozytorium nie jest zablokowane"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "Wystąpił błąd podczas odblokowywania"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "Cache wyczyszczony poprawnie"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "Wystąpił błąd podczas unieważniania cache"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "Pobieranie z lokalizacji zdalnej"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "Wystąpił błąd podczas pobierania z lokalizacji zdalnej"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "Wystąpił błąd podczas usuwania z repozytorium statystyk"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "Aktualizacja ustawień VCS"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr "Wystąpił błąd podczas aktualizacji ustawień aplikacji"
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 "Repozytoria z powodzeniem zostały ponownie zeskanowane dodano: %s, "
 "usunięto: %s."
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Invalidate cache for all repositories"
 msgid "Invalidated %s repositories"
 msgstr "Unieważnia cache dla wszystkich repozytoriów"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "Aktualizacja ustawień aplikacji"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Aktualizacja ustawień wizualizacji"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr "Wystąpił błąd podczas aktualizacji ustawień wizualizacji"
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr "Proszę podać adres email"
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+msgid "Hook already exists"
+msgstr "Żadne dane nie zostały załadowane"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Dodano nowy hook"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Aktualizacja hooku"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "Wystąpił błąd podczas tworzenia hooku"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Zadanie ponownej indeksacji whoosh zostało zaplanowane"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "Utworzono grupę użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Wystąpił błąd podczas tworzenia grupy użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "Zaktualizowano grupę użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Wystąpił błąd podczas aktualizacji grupy użytkowników %s"
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "Grupa użytkowników została usunięta z powodzeniem"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr "Wystąpił błąd podczas usuwania grupy użytkowników"
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "Grupa docelowa nie może być taka sama"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "Aktualizacja uprawnień grupy użytkowników"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "Aktualizacja uprawnień"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "Wystąpił błąd podczas zapisywania uprawnień"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "Utworzono użytkownika %s"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Wystąpił błąd podczas tworzenia użytkownika %s"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "Użytkownik został zaktualizowany"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "Użytkownik został usunięty"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "Wystąpił błąd podczas usuwania użytkownika"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Dodano ip %s do listy dozwolonych adresów użytkownia"
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Wystąpił błąd podczas zapisywania e-maila"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr "Usunięto adres ip z listy dozwolonych adresów dla użytkownika"
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "Obserwatorzy %s"
-
-#: kallithea/lib/auth.py:757
-msgid "Invalid API key"
-msgstr ""
-
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
+#: kallithea/lib/auth.py:824
 msgid "You need to be a registered user to perform this action"
 msgstr "Musisz być zarejestrowanym użytkownikiem, żeby wykonać to działanie"
 
-#: kallithea/lib/auth.py:864
+#: kallithea/lib/auth.py:852
 msgid "You need to be signed in to view this page"
 msgstr "Musisz być zalogowany, żeby oglądać stronę"
 
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "Nie znaleziono changeset"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Plik binarny"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Lista zmian była zbyt duża i została obcięta, użyj menu porównań żeby "
 "wyświetlić różnice"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Nie wykryto zmian"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Usunięta gałąź: %s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Utworzony tag: %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Nie znaleziono changeset"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Pokaż wszystkie zestawienia zmian changesets %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 #, fuzzy
 msgid "Compare view"
 msgstr "Wyświetl porównanie"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "i"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s więcej"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "rewizja"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "nazwa rozgałęzienia %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "Połączonych gałęzi #%s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[usunięte] repozytorium"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[utworzone] repozytorium"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[utworzone] repozytorium jako rozgałęzienie"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[rozgałęzione] repozytorium"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[zaktualizowane] repozytorium"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "[pobierz] archiwum z repozytorium"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[skasowane] repozytorium"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "[utworzony] użytkownik"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "[zaktualizowany] użytkownik"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "[utworzona] grupa użytkowników"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "[zaktualizowana] grupa użytkowników"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[komentarz] do zmiany w repozytorium"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "[komentarz] wniosek o połączenie gałęzi"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "[zamknięty] wniosek o połączenie gałęzi"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[wysłane zmiany] w"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[synchronizacja przez Kallithea] z repozytorium"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[pobieranie z zdalnego] do repozytorium"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[pobrano]"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[start następnego] repozytorium"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[zatrzymany po] repozytorium"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " i %s więcej"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Brak plików"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "nowy plik"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "modyfikuj"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "kasuj"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "zmień nazwę"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1274,7 +1288,7 @@
 "zmienione z systemie plików proszę uruchomić aplikację ponownie, aby "
 "ponownie przeskanować repozytoria"
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1282,7 +1296,7 @@
 msgstr[1] "%d lata"
 msgstr[2] "%d lat"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1290,7 +1304,7 @@
 msgstr[1] "%d miesięcy"
 msgstr[2] "%d miesięcy"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1298,7 +1312,7 @@
 msgstr[1] "%d dni"
 msgstr[2] "%d dni"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1306,7 +1320,7 @@
 msgstr[1] "%d godziny"
 msgstr[2] "%d godzin"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1314,7 +1328,7 @@
 msgstr[1] "%d minuty"
 msgstr[2] "%d minut"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1322,707 +1336,305 @@
 msgstr[1] "%d sekund"
 msgstr[2] "%d sekund"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "w %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s temu"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "w %s i %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s i %s temu"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "przed chwilą"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Brak dostępu do repozytorium"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Repozytorium do odczytu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Repozytorium do zapisu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Administracja dostępu do repozytorium"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Grupy repozytoriów brak dostępu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Grupy repozytoriów dostęp do odczytu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Grupy repozytoriów dostęp do zapisu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Repozytoria Grupy dostęp administratora"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Administrator Repo"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Tworzenie repozytorium jest wyłączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Tworzenie repozytorium jest włączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Rozwidlenie repozytorium wyłączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Rozwidlenie repozytorium włączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Rejestracja wyłączona"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Rejestracja nowego użytkownika na stronie z ręczną aktywacją"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Rejestracja nowego użytkownika na stronie z automatyczną aktywacją"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Brak Korekty"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Zaakceptowano"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Odrzucono"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "Objęty Przeglądem"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "najwyższy poziom"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Grupy repozytoriów brak dostępu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Grupy repozytoriów dostęp do odczytu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Grupy repozytoriów dostęp do zapisu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Repozytoria Grupy dostęp administratora"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Ta grupa użytkowników nie ma dostępu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Dostęp do grupy parametrów użytkownika"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Ta grupa użytkowników ma prawo do zapisu"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Ta grupa użytkowników ma uprawnienia administratora"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Tworzenie grup repozytoriów wyłączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Tworzenie grup repozytoriów włączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Tworzenie grup użytkowników wyłączone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Tworzenie grup użytkowników właczone"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Rejestracja wyłączona"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Rejestracja użytkownika z ręczną aktywacją konta"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Rejestracja użytkownika z automatyczną aktywacją konta"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "widziany %s"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Wymieniony]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "najwyższy poziom"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Administrator Repo"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "Nieautoryzowany dostęp do zasobów"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "Nieautoryzowany dostęp do zasobów"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "Utworzono grupę repo %s"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "Utworzono grupę repo %s"
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 #, fuzzy
 msgid "Only admins can create user groups"
 msgstr "Tworzenie grup użytkowników"
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 #, fuzzy
 msgid "Non-admins can create user groups"
 msgstr "Tworzenie grup użytkowników"
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Ogólna liczba repozytoriów"
 
-#: kallithea/model/db.py:1696
-#, fuzzy
-#| msgid "Non-admins can can fork repositories"
+#: kallithea/model/db.py:1733
+#, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "Unieważnia cache dla wszystkich repozytoriów"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Rejestracja wyłączona"
+
+#: kallithea/model/db.py:1736
 #, fuzzy
 msgid "User registration with manual account activation"
 msgstr "Rejestracja użytkownika z ręczną aktywacją konta"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 #, fuzzy
 msgid "User registration with automatic account activation"
 msgstr "Rejestracja użytkownika z automatyczną aktywacją konta"
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 #, fuzzy
 msgid "Not reviewed"
 msgstr "Brak Korekty"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 #, fuzzy
 msgid "Under review"
 msgstr "Objęty Przeglądem"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "Zaakceptowano"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Zaakceptowano"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Wpisz login"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Wprowadź wartość %(min)i znaków lub więcej"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Wpisz hasło"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Wpisz %(min)i lub więcej znaków"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, fuzzy, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s skomentował zatwierdzenie %(when)s"
-
-#: kallithea/model/notification.py:256
-#, fuzzy, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s wysłał wiadomość do %(when)s"
-
-#: kallithea/model/notification.py:257
-#, fuzzy, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s wspomniał o Tobie w %(when)s"
-
-#: kallithea/model/notification.py:258
-#, fuzzy, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s zarejestrował na stronie w %(when)s"
-
-#: kallithea/model/notification.py:259
-#, fuzzy, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s otworzył nowe połączenie gałęzi w %(when)s"
-
-#: kallithea/model/notification.py:260
+#: kallithea/model/notification.py:165
 #, fuzzy, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s skomentował nowe połączenie gałęzi w %(when)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s skomentował zatwierdzenie %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s wysłał wiadomość do %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s wspomniał o Tobie w %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s zarejestrował na stronie w %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s otworzył nowe połączenie gałęzi w %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s skomentował nowe połączenie gałęzi w %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "[komentarz] wniosek o połączenie gałęzi"
+
+#: kallithea/model/notification.py:168
 #, fuzzy, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Użytkownik %(new_username)s zarejestrował"
 
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "%(user)s chce żeby przejrzeć nowe gałęzie #%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "[komentarz] wniosek o połączenie gałęzi"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 #, fuzzy
 msgid "Closing"
 msgstr "Używa:"
 
-#: kallithea/model/pull_request.py:137
+#: kallithea/model/pull_request.py:76
 #, fuzzy, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
 msgstr "%(user)s chce żeby przejrzeć nowe gałęzie #%(pr_id)s: %(pr_title)s"
 
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Create Pull Request"
+msgid "Cannot create empty pull request"
+msgstr "Stwórz nowe żądanie połączenia gałęzi"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "Potwierdź usunięcie połączenia gałęzi"
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "ostatni tip"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "nowy użytkownik się zarejestrował"
 
-#: kallithea/model/user.py:256
-#, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
+#: kallithea/model/user.py:253
+#, fuzzy
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Nie możesz usunąć tego użytkownika ponieważ jest kluczowy dla całej "
 "aplikacji"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -2031,16 +1643,16 @@
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s"
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, fuzzy, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -2049,65 +1661,65 @@
 "użytkownik \"%s\" wciąż posiada repozytoria następujące %s i nie może "
 "zostać usunięty. Zmień właściciela lub usuń te repozytoria. %s"
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "łącze resetowania hasła"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 #, fuzzy
 msgid "Password reset notification"
 msgstr "łącze resetowania hasła"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "Wartość listy nie może być pusta"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "Użytkownik \"%(username)s\" już istnieje"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, fuzzy, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "Nazwa użytkownika %(username)s jest nieprawidłowa"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 #, fuzzy
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-"Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia,"
-" kropki lub myślniki i muszą zaczynać się znakiem alfanumerycznym lub "
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+"Nazwa użytkownika może zawierać tylko znaki alfanumeryczne, podkreślenia, "
+"kropki lub myślniki i muszą zaczynać się znakiem alfanumerycznym lub "
 "podkreśleniem"
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "Nazwa użytkownika %(username)s jest nieprawidłowa"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "Niewłaściwa nazwa grupy"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "Nazwa grupy \"%(usergroup)s\" już istnieje"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
@@ -2115,107 +1727,107 @@
 "nazwa grupy może zawierać tylko znaki alfanumeryczne, podkreślenia, "
 "kropki lub myślniki i musi zaczynać się znakiem alfanumerycznym"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Nie można przypisać do tej grupy jako rodzic"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "Nazwa grupy \"%(group_name)s\" już istnieje"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Repozytorium o nazwie \"%(group_name)s\" już istnieje"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Nieprawidłowe znaki (nie-ascii) w haśle"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Hasła różnią się"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 #, fuzzy
 msgid "Invalid username or password"
 msgstr "nieprawidłowe hasło"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "Niezgodność tokenu"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, fuzzy, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "Nazwa repozytorium  %(repo)s jest zabroniona"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Repozytorium o nazwie %(repo)s już istnieje"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "Repozytorium  \"%(repo)s\" już istnieje w grupie \"%(group)s\""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Grupa repozytoriów z nazwą \"%(repo)s\" już istnieje"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "prywatne repozytorium"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "Fork musi być tego samego typu, jak rodzic"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "Nie masz uprawnień do tworzenia repozytorium w tej grupie"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "nie masz uprawnień do tworzenia repozytorium w tej grupie"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr "Nie masz uprawnień do tworzenia repozytorium w tej grupie"
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr "Ta nazwa użytkownika lub grupy użytkowników nie jest prawidłowa"
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "To nie jest prawidłowa ścieżka"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "Ten adres e-mail jest już zajęty"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "e-mail \"%(email)s\" nie istnieje."
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
@@ -2223,250 +1835,155 @@
 "Atrybut logowania CN do LDAP należy określić, jest to nazwa atrybutu, "
 "który jest odpowiednikiem  \"username\""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Proszę podać poprawny adres IPv4 lub IPv6"
 
-#: kallithea/model/validators.py:775
+#: kallithea/model/validators.py:732
 #, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr "Rozmiar sieci (bits) może mieścić się w zakresie od 0-32 (nie %(bits)r)"
-
-#: kallithea/model/validators.py:808
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+"Rozmiar sieci (bits) może mieścić się w zakresie od 0-32 (nie %(bits)r)"
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 "Klucz nazwy może składać się tylko z liter, podkreślenia, myślnika lub "
 "numerów"
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "Nazwa pliku nie może znajdować się w katalogu"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Repozytorium"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "szybki filtr..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "repozytoria"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "Dodaj Repozytorium"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "Dodaj Grupę Repozytoriów"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr "Jako administrator uprawnienia tej grupy, i możesz je edytować"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "Edytuj Grupę Repozytoriów"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "Nazwa Grupy"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Repozytorium"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Opis"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Nazwa"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Ostatnia akytwność"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Ostatnia zmiana"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Właściciel"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "Kliknij, aby posortować rosnąco"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "Kliknij, aby posortować malejąco"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Nie znaleziono repozytorium."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Błąd danych."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Wczytywanie..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Zaloguj się"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Zaloguj się do %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "Nazwa użytkownika"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Hasło"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Zapamiętaj mnie"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "Zapomniałeś hasła?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Nie masz konta?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Zaloguj się"
 
@@ -2474,153 +1991,136 @@
 msgid "Password Reset"
 msgstr "Łącze resetowania hasła"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, fuzzy, python-format
 msgid "Reset Your Password to %s"
 msgstr "Zapomniałeś hasła do %s?"
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 #, fuzzy
 msgid "Reset Your Password"
 msgstr "Zapomniałeś hasła?"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "Adres Email"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 #, fuzzy
 msgid "Send Password Reset Email"
 msgstr "łącze resetowania hasła"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr "Link do zresetowania hasła zostanie wysłany na adres e-mail"
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
 msgid "New Password"
 msgstr "Nowe hasło"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
 msgid "Confirm New Password"
 msgstr "Nowe hasło"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Zarejestruj się"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "Ponownie wprowadź hasło"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Imię"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Nazwisko"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Nie ma jeszcze gałęzi"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Zamknięte Gałęzie"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Nie ma jeszcze tagów"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Nie ma jeszcze zakładek"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 #, fuzzy
 msgid "Admin Journal"
 msgstr "Dziennik administratora"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "szybkie wyszukiwanie..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 #, fuzzy
 msgid "Filter"
 msgstr "filtr"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, fuzzy, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2629,30 +2129,16 @@
 msgstr[2] "%s wejść"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Działanie"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Repozytorium"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Data"
 
@@ -2660,7 +2146,7 @@
 msgid "From IP"
 msgstr "Z IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "Brak akcji"
 
@@ -2669,76 +2155,76 @@
 msgstr "Ustawienia Autentykacji"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "Autentykacja"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr "Wtyczki Autentykacji"
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr "Włączone Wtyczki"
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr "Wtyczka"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Zapisz"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "Repozytoria domyślne"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Typ"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Prywatne repozytorium"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
@@ -2746,34 +2232,34 @@
 "Prywatne repozytoria są widoczne tylko dla osób bezpośrednio dodanych "
 "jako współpracownicy."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Włącz statystyki"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Włącz okno statystyk na stronie podsumowania."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Włącz pobieranie"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Włącz menu pobierania na stronie podsumowania."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Włącz blokowanie"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "Włącz blokowanie pobierania w repozytorium."
 
@@ -2782,45 +2268,45 @@
 msgid "Edit Gist"
 msgstr "Edytuj Gist"
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Opis Gist ..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr "Trwałość Gist"
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Wygasa"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "nigdy"
@@ -2830,7 +2316,8 @@
 msgstr "Zaktualizuj Gist"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Anuluj"
 
@@ -2853,16 +2340,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr "Utwórz Nowy Gist"
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "Utworzono"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Nie ma jeszcze żadnych gists"
 
@@ -2871,45 +2358,45 @@
 msgid "New Gist"
 msgstr "Nowy Gist"
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr "Utwórz Prywatny Gist"
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr "Utwórz Publiczny Gist"
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "Zresetuj"
 
@@ -2919,113 +2406,111 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "Adres URL"
 
-#: kallithea/templates/admin/gists/show.html:37
+#: kallithea/templates/admin/gists/show.html:35
 msgid "Public Gist"
 msgstr "Publiczny Gist"
 
-#: kallithea/templates/admin/gists/show.html:39
+#: kallithea/templates/admin/gists/show.html:37
 msgid "Private Gist"
 msgstr "Prywatny Gist"
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Usuń"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr "Potwierdź aby usunąć ten Gist"
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Edycja"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "utworzono"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "wyświetl jako raw"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Moje konto"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "Profil"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 #, fuzzy
 msgid "Email Addresses"
 msgstr "Nowy adres e-mail"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "repozytoria"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "Utwórz repozytorium"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
@@ -3033,77 +2518,94 @@
 msgid "Show Permissions"
 msgstr "Skopiuj uprawnienia"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, fuzzy, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "Potwierdź, aby usunąć to ip: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 #, fuzzy
 msgid "Expired"
 msgstr "Wygasa"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "Potwierdź, aby usunąć to ip: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 #, fuzzy
 msgid "Remove"
 msgstr "Usunięto"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 #, fuzzy
 msgid "No additional API keys specified"
 msgstr "Brak dodatkowych emaili"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 #, fuzzy
 msgid "New API key"
 msgstr "Nowe pole klucza"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Dodaj"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "Główny"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Potwierdź, aby usunąć ten e-mail: %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 #, fuzzy
 msgid "No additional emails specified."
 msgstr "Brak dodatkowych emaili"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "Nowy adres e-mail"
 
@@ -3112,109 +2614,71 @@
 msgid "Change Your Account Password"
 msgstr "Nowe hasło"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Nowe hasło"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
+#: kallithea/templates/admin/my_account/my_account_password.html:39
 #, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
 msgstr "Zmiana awataru na"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "Używa"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr "Avatary są wyłączone"
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 #, fuzzy
 msgid "Repositories You Own"
 msgstr "Nie znaleziono repozytorium."
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Nie znaleziono rekordów."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Nazwa"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 #, fuzzy
 msgid "Repositories You are Watching"
 msgstr "Położenie repozytorium"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Opcje powiadomień"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Wszystkie"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Komentarze"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Połączone gałęzie"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-#, fuzzy
-msgid "Mark All Read"
-msgstr "Oznacz wszystko jako przeczytane"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Brak powiadomień"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-#, fuzzy
-msgid "Show Notification"
-msgstr "Pokaż powiadomienia"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Powiadomienia"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 #, fuzzy
 msgid "Default Permissions"
 msgstr "Domyślne uprawnienia"
@@ -3230,18 +2694,24 @@
 msgid "IP Whitelist"
 msgstr "Lista dozwolonych adresów IP"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Dostęp anonimowy"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "Dostęp anonimowy"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
@@ -3251,24 +2721,24 @@
 "Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie "
 "niestandardowe uprawnienia w repozytoriach zostaną utracone"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Istniejące repozytorium?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "Repozytorium grupy"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "Repozytorium grupy"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
@@ -3278,21 +2748,21 @@
 "Wybrane uprawnienie zostaną skasowane. Pamiętaj, że wszystkie "
 "niestandardowe uprawnienia w repozytoriach zostaną utracone"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "Usuń ta grupę repozytoriów"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "Grupa użytkownika"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 #, fuzzy
 msgid ""
 "All default permissions on each user group will be reset to chosen "
@@ -3301,143 +2771,143 @@
 msgstr ""
 "Wszystkie Uprawnienia domyślne każdej grupy użytkowników zostaną "
 "przywrócone do wybranego zezwolenia, trzeba pamiętać, że wszystkie "
-"niestandardowe uprawnienia domyślne dla grup repozytorium zostaną "
-"utracone"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+"niestandardowe uprawnienia domyślne dla grup repozytorium zostaną utracone"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "Tworzenie repozytorium"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "Tworzenie grupy użytkowników"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Rozwidlanie repozytorium"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Rejestracja"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "Autoryzacja aktywacji zewnętrznego konta"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Potwierdź, aby usunąć to ip: %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 #, fuzzy
 msgid "All IP addresses are allowed."
 msgstr "Wszystkie adresy IP są dozwolone"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Nowy adres ip"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "Grupy Repozytoriów"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Nazwa grupy"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Rodzic gropy"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Ustawienia"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Zaawansowane"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Uprawnienia"
@@ -3462,12 +2932,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Utworzono"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3479,8 +2949,30 @@
 msgid "Delete this repository group"
 msgstr "Usuń ta grupę repozytoriów"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "Disabled"
+msgid "Visible"
+msgstr "Wyłączone"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "Add Repository"
+msgid "Add repos"
+msgstr "Dodaj Repozytorium"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#, fuzzy
+#| msgid "Add user group"
+msgid "Add/Edit groups"
+msgstr "Dodaj grupę użytkowników"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 #, fuzzy
 msgid "User/User Group"
@@ -3488,8 +2980,8 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3498,110 +2990,105 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 #, fuzzy
 msgid "Revoke"
 msgstr "odwołane"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "Dodaj nowe"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 #, fuzzy
 msgid "Apply to children"
 msgstr "dotyczy dzieci"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr "Oba"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
 msgstr ""
 "Włącz blokowanie pobierania przez grupy. Opcja ta będzie stosowana do "
 "wszystkich innych grup i repozytoriów wewnątrz"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 #, fuzzy
 msgid "Remove this group"
 msgstr "Tworzenie grup użytkowników"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 #, fuzzy
 msgid "Confirm to delete this group"
 msgstr "Potwierdź usunięcie grupy użytkowników: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "Strona Główna"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "używając"
+#, fuzzy, python-format
+#| msgid "Repository Group: %s"
+msgid "Repository group %s"
+msgstr "Grupa Repozytoriów: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 #, fuzzy
 msgid "Repository Groups Administration"
 msgstr "Repozytoria grup administracyjnych"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 #, fuzzy
 msgid "Number of Top-level Repositories"
 msgstr "Liczba najwyższego poziomu repozytorium"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "[utworzone] repozytorium"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr "Powinna być krótka i na temat. Użyj pliku README dla dłuższych opisów."
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+"Powinna być krótka i na temat. Użyj pliku README dla dłuższych opisów."
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "Opcjonalnie wybierz grupę do wprowadzenia tego repozytorium."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Rodzaj repozytorium do stworzenia."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Docelowa rewizja"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3630,27 +3117,27 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 #, fuzzy
 msgid "Extra Fields"
 msgstr "Dodatkowe pola"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Zdalnie"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Statystyki"
 
@@ -3660,31 +3147,29 @@
 msgstr "Rodzic gropy"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr "Ustaw"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 #, fuzzy
 msgid "Manually set this repository as a fork of another from the list."
 msgstr "Ręczne ustawienie rozwidlenia z listy"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 #, fuzzy
 msgid "Public Journal Visibility"
 msgstr "Dziennik publiczny"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "Usuń z dziennika publicznego"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 #, fuzzy
 msgid "Add to Public Journal"
 msgstr "Dziennik publiczny"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 #, fuzzy
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
@@ -3693,41 +3178,41 @@
 "Wszystkie działania wykonywane na tym repozytorium będą dostępne dla "
 "wszystkich w dzienniku publicznym"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 #, fuzzy
 msgid "Change Locking"
 msgstr "Włącz blokowanie"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 #, fuzzy
 msgid "Confirm to unlock repository."
 msgstr "Potwierdź odblokowanie repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 #, fuzzy
 msgid "Unlock Repository"
 msgstr "Odblokowane repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, fuzzy
 msgid "Confirm to lock repository."
 msgstr "Potwierdź blokowanie repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 #, fuzzy
 msgid "Lock Repository"
 msgstr "Odblokowane repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Repozytorium nie jest zablokowane"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3735,18 +3220,18 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Potwierdź usunięcie repozytorium: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 #, fuzzy
 msgid "Delete this Repository"
 msgstr "Usuń ta grupę repozytoriów"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, fuzzy, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
@@ -3754,15 +3239,15 @@
 msgstr[1] "to repozytorium ma %s forki"
 msgstr[2] "to repozytorium ma %s forków"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "Ustaw jako rozwidlenie"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "Usuń rozwidlenie"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3774,12 +3259,7 @@
 msgid "Invalidate Repository Cache"
 msgstr "Unieważnij pamięć podręczną repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-#, fuzzy
-msgid "Confirm to invalidate repository cache."
-msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 #, fuzzy
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
@@ -3788,102 +3268,108 @@
 "Ręcznie unieważnienie cache dla tego repozytorium. Przy pierwszym "
 "dostępie do repozytorium zostanie dodany do bufora ponownie"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 #, fuzzy
 msgid "List of Cached Values"
 msgstr "Lista buforowanych wartości"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "Prefiks"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Klucz"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Aktywny"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Potwierdź, aby usunąć to pole: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "Nowe pole klucza"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "Nowa etykieta pola"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "Wpisz krótką etykietę"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "Nowy opis pola"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "Wprowadź opis pola"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 #, fuzzy
 msgid "Extra fields are disabled."
 msgstr "Avatary są wyłączone"
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "prywatne repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "[rozgałęzione] repozytorium"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Utworzone repozytorium %s"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "[pobieranie z zdalnego] do repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "Potwierdź pull z zdalnej strony"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "prywatne repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3893,35 +3379,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "[utworzone] repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "Repozytorium"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr "Wersja domyślna dla plików stronicowania, pobierania plików, readme"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Zmiana właściciela tego repozytorium."
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
 msgid "Processed commits"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 msgid "Processed progress"
 msgstr ""
 
@@ -3940,7 +3431,7 @@
 msgid "Repositories Administration"
 msgstr "Administracja repozytoriami"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3962,7 +3453,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Aktualizacja"
 
@@ -3974,285 +3465,278 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Wyślij"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "Nazwa strony"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "Sfera uwierzytelniania HTTP"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 #, fuzzy
 msgid "Save Settings"
 msgstr "Zapisz ustawienia"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+#, fuzzy
+msgid "Custom Hooks"
+msgstr "Niestandardowa aktualizacja"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-#, fuzzy
-msgid "Custom Hooks"
-msgstr "Niestandardowa aktualizacja"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "Nie udało się usunąć hooka"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Rescan option"
+msgid "Rescan options"
 msgstr "ponowne skanowanie opcji"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 #, fuzzy
 msgid "Delete records of missing repositories"
 msgstr "Szukaj we wszystkich repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "Unieważnia cache dla wszystkich repozytoriów"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 #, fuzzy
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr "Unieważnia cache dla wszystkich repozytoriów"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "Opcja odbudowy indeksowania"
 
-#: kallithea/templates/admin/settings/settings_search.html:12
+#: kallithea/templates/admin/settings/settings_search.html:9
 msgid "Build from scratch"
 msgstr "Buduj od podstaw"
 
-#: kallithea/templates/admin/settings/settings_search.html:15
+#: kallithea/templates/admin/settings/settings_search.html:12
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Indeksuj ponownie"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
 #, fuzzy
 msgid "Git version"
 msgstr "Edycja Uprawnień"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "www"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "Wymagaj ssl dla operacji vcs"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "Pokaż rozmiar repozytorium po wysłaniu zmian"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "Logi poleceń wysłania zmian użytkowników"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "Logi poleceń połączenia gałęzi użytkowników"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "Aktualizacja repozytorium po wysłaniu zmian (aktualizacja hg)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 #, fuzzy
 msgid "Mercurial extensions"
 msgstr "Rozszerzenia Mercurial"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Rozszerzenia dużych pliów"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Rozszerzenia hgsubversion"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "Ogólna liczba repozytoriów"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
 msgstr ""
 "Kliknij, aby odblokować. Musisz ponownie uruchomić Kallithea żeby "
 "wprowadzić to ustawienie w życie."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Główne"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "Używaj w repozytorium dodatkowych pól"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
-msgstr "Umożliwia przechowywanie dodatkowych niestandardowych pól w repozytorium."
-
-#: kallithea/templates/admin/settings/settings_visual.html:18
+msgstr ""
+"Umożliwia przechowywanie dodatkowych niestandardowych pól w repozytorium."
+
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Pokaż wersję Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4269,94 +3753,103 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+#, fuzzy
+msgid "Clone URL"
+msgstr "Url klonowania"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "Pozycja panelu"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "Rozmiar Repozytorium"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Ikony"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "Pokazuj w publicznym repo ikonę w repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "Pokazuj w prywatnym repo ikonę w repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "Pokazuj w publicznym repo ikonę w repozytoriach"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Tagowanie meta"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "Dodaj grupę użytkowników"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4376,13 +3869,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Użytkownik"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr "Potwierdź usunięcie grupy użytkowników: %s"
@@ -4391,15 +3884,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Nie ma jeszcze żadnego użytkownika"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Wybrane grupy użytkowników"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Dostępni użytkownicy"
 
@@ -4414,17 +3907,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "Użytkownicy"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "Potwierdzenie hasła"
 
@@ -4443,12 +3936,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4457,7 +3950,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "Potwierdź usunięcie tego użytkownika: %s"
@@ -4466,24 +3959,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "Potwierdzenie nowego hasła"
 
@@ -4492,237 +3977,238 @@
 msgid "Users Administration"
 msgstr "Administracja użytkownikami"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "Wystąpienia serwera: %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Repozytorium mercurial"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Repozytorium git"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 #, fuzzy
 msgid "Create Fork"
 msgstr "Usuń rozwidlenie"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Podsumowanie"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "Dziennik zmian"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Pliki"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "Przełącz do"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr "Pokaż Prośby Pobrania %s"
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Połączone gałęzie"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "Opcje"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 #, fuzzy
 msgid "Compare Fork"
 msgstr "Porównaj rozwidlenie"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "Porównaj"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "Szukaj"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr "Odblokowany"
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr "zablokowane"
 
-#: kallithea/templates/base/base.html:173
+#: kallithea/templates/base/base.html:174
 msgid "Follow"
 msgstr "Obserwuj"
 
-#: kallithea/templates/base/base.html:174
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr "Nie obserwuj"
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "Gałąź"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr "Stwórz nowe żądanie połączenia gałęzi"
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr "Pokaż Prośby Pobrania %s"
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "Przełącz do"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "Pokaż ostatnią aktywność"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "Dziennik publiczny"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "Wyświetl publiczne gists"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gists"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 #, fuzzy
 msgid "All Public Gists"
 msgstr "Wszystkie publiczne gists"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 #, fuzzy
 msgid "My Public Gists"
 msgstr "Moje publiczne gists"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 #, fuzzy
 msgid "My Private Gists"
 msgstr "Moje prywatne gists"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "Szukaj we wszystkich repozytoriach"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 #, fuzzy
 msgid "My Pull Requests"
 msgstr "Połączone gałęzie"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 #, fuzzy
 msgid "Not Logged In"
 msgstr "Zaloguj się"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 #, fuzzy
 msgid "Login to Your Account"
 msgstr "Zaloguj się do swojego konta"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "Nie pamiętasz hasła?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "Nie masz konta?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "Wyloguj się"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Repozytoria domyślne"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "Utwórz repozytorium"
 
-#: kallithea/templates/base/default_perms_box.html:33
+#: kallithea/templates/base/default_perms_box.html:27
 msgid "Select this option to allow repository creation for this user"
 msgstr ""
 "Wybierz tę opcję, aby umożliwić stworzenie repozytorium dla tego "
 "użytkownika"
 
-#: kallithea/templates/base/default_perms_box.html:40
+#: kallithea/templates/base/default_perms_box.html:33
 msgid "Create user groups"
 msgstr "Tworzenie grup użytkowników"
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 "Wybierz tę opcję, aby umożliwić utworzenie grupy użytkowników dla tego "
 "użytkownika"
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "Rozwidlenie repozytorium"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 "Wybierz tę opcję, aby umożliwić rozwidlania repozytorium dla tego "
 "użytkownika"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "Wyświetl"
 
@@ -4731,156 +4217,152 @@
 msgstr "Nie ma jeszcze ustawionych uprawnień"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Uprawnienia"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Edycja Uprawnień"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+#, fuzzy
+msgid "Submitting ..."
+msgstr "Przesyłanie..."
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "Włącz pobieranie"
+
+#: kallithea/templates/base/root.html:31
 #, fuzzy
 msgid "Add Another Comment"
 msgstr "Dodaj kolejny komentarz"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "Zakończyć obserwację tego repozytorium"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "Zacznij obserwację tego repozytorium"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "Grupa"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "użytkownik"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "Ładuję..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "ładuję ..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "Szukaj obcięte"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "Nie ma plików pasujących"
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 #, fuzzy
 msgid "Open New Pull Request from {0}"
 msgstr "Komentarz połączenia gałęzi %s"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr ""
 
-#: kallithea/templates/base/root.html:33
+#: kallithea/templates/base/root.html:41
 #, fuzzy
 msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr "Pokaż wybrane zmiany __S -> __E"
 
-#: kallithea/templates/base/root.html:34
+#: kallithea/templates/base/root.html:42
 #, fuzzy
 msgid "Selection Link"
 msgstr "Wybór linku"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 #, fuzzy
 msgid "Collapse Diff"
 msgstr "Pliki różnic"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 #, fuzzy
 msgid "Expand Diff"
 msgstr "poprawka różnic"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "rewizja"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "Nie udało się cofnąć uprawnienia"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 #, fuzzy
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "potwierdzić odwołanie pozwolenie na {0}: {1} ?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Wybrane zmiany"
+
+#: kallithea/templates/base/root.html:52
 #, fuzzy
 msgid "Specify changeset"
 msgstr "Wybrane zmiany"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "%s Zakładki"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Autor"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Rewizja"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "%s Gałęzie"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "Kliknij, aby posortować rosnąco"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "Kliknij, aby posortować malejąco"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Nie znaleziono rekordów."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Błąd danych."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Wczytywanie..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "%s Dziennik zmian"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4888,81 +4370,33 @@
 msgstr[1] "pokazano %d z  %d rewizji"
 msgstr[2] "pokazano %d z  %d rewizji"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "Wyczyść zaznaczenie"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "Potwierdź blokowanie repozytorium"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "porównaj gałęzie %s"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, fuzzy, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "porównaj gałęzie %s"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 #, fuzzy
 msgid "Branch filter:"
 msgstr "filtr"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, fuzzy, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr "Status grupy zmian: %s⏎ Kliknij, aby otworzyć prośby pobrania #%s"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "Status grupy zmian: %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "Komentarze Grupy zmian"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "Zakładki %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "Tagi %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Gałęzie %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Nie ma jeszcze zmian"
 
@@ -4978,7 +4412,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "Dodana"
 
@@ -4992,104 +4426,116 @@
 msgid "Affected %s files"
 msgstr "Zarażone pliki %s"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Ostatnia zmiana"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Gałąź/Etykieta"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Dodaj lub prześlij pliki bezpośrednio przez stronę"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Wyślij zmiany do nowego repo"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "Istniejące repozytorium?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, fuzzy, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr "Status grupy zmian: %s⏎ Kliknij, aby otworzyć prośby pobrania #%s"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Status grupy zmian: %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d komentarz"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "Zakładki %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "Tagi %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Gałęzie %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "%s Grupy zmian"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "Status grupy zmian"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "Raw różnic"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr "Poprawka różnic"
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr "Pobierz różnice"
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "połącz"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 #, fuzzy
 msgid "Grafted from:"
 msgstr "Utworzono"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "utworzono"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "utworzono"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -5097,9 +4543,9 @@
 msgstr[1] "%s pliki zostały zmienione"
 msgstr[2] "%s plików zostało zmienionych"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -5107,19 +4553,20 @@
 msgstr[1] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
 msgstr[2] "%s plików zostało zmienionych z %s inercjami i %s usunięciami"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 #, fuzzy
 msgid "Show full diff anyway"
 msgstr "Pokaż pełną historię"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-#, fuzzy
-msgid "No revisions"
-msgstr "rewizja"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "Comment"
+msgid "comment"
+msgstr "Komentarz"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -5141,61 +4588,61 @@
 msgid "Delete comment?"
 msgstr "%d komentarz"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Ostatnia aktywność"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "Komentując linię {1}."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr "Komentarze analizowane za pomocą %s składni od %s wsparcia."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-#, fuzzy
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
 msgstr ""
 "Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do "
 "użytkownika strony"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Podgląd komentarza"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-#, fuzzy
-msgid "Submitting ..."
-msgstr "Przesyłanie..."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "Zmiana statusu grupy zmian"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr "Zagłosuj na żądanie na grupę zmian"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "Bez zmian"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Komentarz połączenia gałęzi %s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(zamknięty)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Komentarz"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Podgląd"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "Musisz być zalogowany żeby komentarz."
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "Zaloguj się teraz"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "Ukryj"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
@@ -5203,7 +4650,7 @@
 msgstr[1] "%d komentarzy"
 msgstr[2] "%d komentarzy"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, fuzzy, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
@@ -5211,120 +4658,122 @@
 msgstr[1] "(%d linii)"
 msgstr[2] "(%d linii)"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-#, fuzzy
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-"Użyj @username wewnątrz tego tekstu, aby wysłać powiadomienie do "
-"użytkownika strony"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr "Zagłosuj na żądanie na grupę zmian"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "Zmiana statusu grupy zmian"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "Bez zmian"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(zamknięty)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "%s Zestawienie zmian"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "Status grupy zmian: %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "Pliki naruszone"
 
-#: kallithea/templates/changeset/diff_block.html:21
-#: kallithea/templates/files/diff_2way.html:43
-msgid "Show full diff for this file"
-msgstr "Pokaż pełną edycję tego pliku"
-
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
-msgid "Show full side-by-side diff for this file"
-msgstr "Pokaż pełną listę zmian i różnic obok siebie"
-
-#: kallithea/templates/changeset/diff_block.html:38
-msgid "Show inline comments"
-msgstr "Pokaż online komentarz"
-
-#: kallithea/templates/changeset/diff_block.html:86
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "plik usunięty"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Ostatnio modyfikowany"
+
+#: kallithea/templates/changeset/diff_block.html:42
 #, fuzzy
 msgid "Deleted"
 msgstr "usuń"
 
-#: kallithea/templates/changeset/diff_block.html:89
+#: kallithea/templates/changeset/diff_block.html:44
 #, fuzzy
 msgid "Renamed"
 msgstr "zmień nazwę"
 
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Nieznana wersja %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Brak nazwy pliku"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "plik dodany"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr "Pokaż pełną edycję tego pliku"
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr "Pokaż pełną listę zmian i różnic obok siebie"
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr "Pokaż online komentarz"
+
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "Brak zestawienia zmian"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "Przodek"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+#, fuzzy
+#| msgid "Common ancestor"
+msgid "Merge Ancestor"
+msgstr "Skomentuj grupę zmian"
+
+#: kallithea/templates/compare/compare_cs.html:40
 #, fuzzy
 msgid "Show merge diff"
 msgstr "Pokaż pełną historię"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-#, fuzzy
-msgid "Common ancestor"
-msgstr "Skomentuj grupę zmian"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 #, fuzzy
 msgid "is"
 msgstr "Gist"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, fuzzy, python-format
 msgid "%s changesets"
 msgstr "%s Zestawienie zmian"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 #, fuzzy
 msgid "behind"
 msgstr "Indeksuj ponownie"
@@ -5336,20 +4785,20 @@
 msgstr "%s Porównaj"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -5357,111 +4806,166 @@
 msgstr[1] "Pokaż %s komentarze"
 msgstr[2] "Pokaż %s komentarze"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "Pokaż pełną historię"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Publiczne repozytorium"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "Nie ma jeszcze zestawienia zmian"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Subskrybuj %s kanał rss"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Subskrybuj %s kanał atom"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s skomentował zatwierdzenie %(when)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
 #, fuzzy, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "Status zestawienia zmian został zmieniony na"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Comment on Changeset \"%s\""
+msgstr "%(user)s skomentował zatwierdzenie %(when)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset flow"
+msgid "Changeset on"
+msgstr "Przepływ zestawienia zmian"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "gałąź"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Ostatnia aktywność"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "Repozytorium nie jest zablokowane"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Witaj %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 #, fuzzy
 msgid "We have received a request to reset the password for your account."
 msgstr "Otrzymaliśmy prośbę o utworzenie nowego hasła do twojego konta."
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
+#: kallithea/templates/email_templates/pull_request.html:4
 #, fuzzy, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, fuzzy, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#| msgid "[commented] on pull request for"
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr "[komentarz] wniosek o połączenie gałęzi"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+msgid "Pull request"
+msgstr "Połączonych gałęzi #%s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, fuzzy, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-#, fuzzy
-msgid "The comment closed the pull request with status"
-msgstr "%s skomentował nowe połączenie gałęzi \"%s\""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-#, fuzzy
-msgid "The comment was made with status"
-msgstr "Wniosek połączenia został zamknięty ze statusem"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "Zobacz tego użytkownika tutaj"
+#| msgid "%(user)s commented on pull request %(age)s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%(user)s skomentował nowe połączenie gałęzi w %(when)s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Połączonych gałęzi #%s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "[komentarz] wniosek o połączenie gałęzi"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "Nazwa grupy"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5479,7 +4983,7 @@
 msgstr "%s Pliki różnic"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "Pliki %s"
@@ -5489,74 +4993,76 @@
 msgid "%s Files Add"
 msgstr "Pliki %s"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Położenie"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "lub"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr "Nowy tryb pliku"
+#, fuzzy
+msgid "New file type"
+msgstr "nowy plik"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "Zatwierdź zmiany"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "poprzednia wersja"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "następna wersja"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "Obserwuj aktualną gałąź"
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "Wczytywanie listy plików..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Rozmiar"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "Rewizja"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 #, fuzzy
 msgid "Last Modified"
 msgstr "Ostatnio modyfikowany"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 #, fuzzy
 msgid "Last Committer"
 msgstr "Autor"
@@ -5567,7 +5073,7 @@
 msgstr "%s Usuń Plik"
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr "Usuń plik"
 
@@ -5580,24 +5086,20 @@
 msgid "Edit file"
 msgstr "Edytuj plik"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "Źródło"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "Edycja pliku"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5606,53 +5108,65 @@
 msgstr[1] "%s autorzy"
 msgstr[2] "%s autorzy"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr "Pokaż źródło"
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "Usunięta gałąź: %s"
+
+#: kallithea/templates/files/files_source.html:37
 msgid "Editing binary files not allowed"
 msgstr "Edycja plików binarnych jest zabroniona"
 
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:40
 msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 "Edycja plików dozwolona tylko wtedy, gdy rewizja jest w trakcie rewizji "
 "głównej gałęzi"
 
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/files/files_source.html:41
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "Plik binarny (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "Plik jest za duży do wyświetlenia"
+
+#: kallithea/templates/files/files_source.html:71
+#, fuzzy
+msgid "Show full annotation anyway."
+msgstr "Pokaż pełną historię"
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Plik jest za duży do wyświetlenia"
+#, fuzzy
+msgid "Show as raw."
+msgstr "wyświetl jako raw"
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5672,12 +5186,12 @@
 msgstr "Obserwatorzy %s"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "Obserwuje"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "Rozpoczęto obserwację -"
 
@@ -5686,36 +5200,36 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "Nazwa rozgałęzienia"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 #, fuzzy
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr "Wersja domyślna dla plików stronicowania, pobierania plików, readme"
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Prywatny"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Skopiuj uprawnienia"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "Skopiuj zezwolenia z rozwidlenia repozytorium"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "Aktualizuj po klonowaniu"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "Sprawdź źródło po wykonaniu klonowania"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5725,40 +5239,40 @@
 msgstr "Gałąź %s"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "Gałęzie"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "Rozgałęziony"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "Nie ma jeszcze gałęzi"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "Dziennik kanału ATOM"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "Dziennik kanału RSS"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "Moje Repozytoria"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "Brak wpisów jeszcze"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "Publiczny dziennik kanału ATOM"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "Publiczny dziennik kanału RSS"
 
@@ -5768,31 +5282,36 @@
 msgid "New Pull Request"
 msgstr "Nowa prośba o połączenie gałęzi"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Tytuł"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "Napisz krótki opis tego tego połączenia gałęzi"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "Przepływ zestawienia zmian"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "Repozytorium git"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Rewizja"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "Repozytorium docelowe"
 
@@ -5806,6 +5325,10 @@
 msgid "Vote"
 msgstr "odwołane"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Ostatnia zmiana"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5829,7 +5352,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "Zamknięte"
 
@@ -5857,115 +5380,106 @@
 msgid "Pull request %s from %s#%s"
 msgstr "Połączonych gałęzi #%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 #, fuzzy
 msgid "Summarize the changes"
 msgstr "Zatwierdź zmiany"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "Nie ma jeszcze recenzenta"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d recenzent"
-msgstr[1] "%d recenzenci"
-msgstr[2] "%d recenzentów"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "Połączenie gałęzi zostało zweryfikowane przez wszystkich recenzentów"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "Nie ma jeszcze gałęzi"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 #, fuzzy
 msgid "on"
 msgstr "brak"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "Pobierz zmiany"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-#, fuzzy
-msgid "Update"
-msgstr "[zaktualizowany] użytkownik"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "Rejestracja"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-#, fuzzy
-msgid "Pull Request Reviewers"
-msgstr "Recenzje wniosków połączenia gałęzi"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+#, fuzzy
+msgid "Cancel Changes"
+msgstr "Ostatnia aktywność"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "reviewer"
+msgid "Reviewers"
+msgstr "recenzent"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 #, fuzzy
 msgid "Remove reviewer"
 msgstr "recenzent"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 #, fuzzy
 msgid "Potential Reviewers"
 msgstr "Podgląd komentarza"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-#, fuzzy
-msgid "Save as New Pull Request"
-msgstr "Otwórz nową prośbę o połączenie gałęzi"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-#, fuzzy
-msgid "Cancel Changes"
-msgstr "Ostatnia aktywność"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 #, fuzzy
 msgid "Pull Request Content"
 msgstr "Wniosek połączenia zmienił status"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+#, fuzzy
+msgid "Common ancestor"
+msgstr "Skomentuj grupę zmian"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5973,7 +5487,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "Połączonych gałęzi #%s"
 
@@ -5982,37 +5495,43 @@
 msgid "Pull Requests to '%s'"
 msgstr "Połączonych gałęzi #%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 #, fuzzy
 msgid "Open New Pull Request"
 msgstr "Otwórz nową prośbę o połączenie gałęzi"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, fuzzy, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Pokaż Prośby Pobrania %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, fuzzy, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Pokaż Prośby Pobrania %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 #, fuzzy
 msgid "Pull Requests Created by Me"
 msgstr "Recenzje wniosków połączenia gałęzi"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "Recenzje wniosków połączenia gałęzi"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 #, fuzzy
 msgid "Pull Requests I Participate In"
 msgstr "Biorę udział w"
@@ -6028,29 +5547,29 @@
 msgid "Search in All Repositories"
 msgstr "Szukaj we wszystkich repozytoriach"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "Szukana fraza"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "Szukaj w"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "Zawartość pliku"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "Komunikaty"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "Nazwy plików"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "Brak uprawnień"
 
@@ -6060,80 +5579,80 @@
 msgstr "%s Statystyki"
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr "%s ATOM"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "%s RSS"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "Włącz"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "Statystyki zebrane: "
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "pliki"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Pokaż więcej"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "komunikaty"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "pliki dodane"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "pliki zmienione"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "pliki usunięte"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "komunikaty"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "plik dodany"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "plik zmieniony"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "plik usunięty"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "Podsumowanie %s"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "Repozytorium zablokowane przez %s"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "Repozytorium odblokowane"
 
@@ -6141,93 +5660,280 @@
 msgid "Fork of"
 msgstr "Gałąź z"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Klonuj z"
 
-#: kallithea/templates/summary/summary.html:72
-#, fuzzy
-msgid "Clone URL"
-msgstr "Url klonowania"
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr "Pokaż ID"
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr "Pokaż nazwę"
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr "Pokaż ID"
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "Statystyki"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "Pobierz"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "Nie pobrano jeszcze plików"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "Pliki do pobrania są zostały wyłączone dla tego repozytorium"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "Pobierz jako zip"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "Zaznacz tu żeby pobrać archiwum z subrepozytorium"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 #, fuzzy
 msgid "With subrepos"
 msgstr "z subrepozytorium"
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "Rozmiar Repozytorium"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "Kanał RSS"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "Ostatnia aktywność"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 #, fuzzy
 msgid "Quick Start"
 msgstr "Szybki start"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr "Dodaj lub prześlij pliki bezpośrednio przez stronę"
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "Wyślij zmiany do nowego repo"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "Istniejące repozytorium?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "Pobierz %s jak %s"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "Etykiety pliku %s"
-
-#: kallithea/templates/tags/tags.html:26
-#, fuzzy
-msgid "Compare Tags"
-msgstr "Porównaj tagi"
-
-#~ msgid "increase diff context to %(num)s lines"
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "Nie ma szukanego indeksu. Proszę uruchomić indeksowanie whoosh"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "Obserwatorzy %s"
+
+#, fuzzy
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s skomentował zatwierdzenie %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s wysłał wiadomość do %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s wspomniał o Tobie w %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s zarejestrował na stronie w %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s otworzył nowe połączenie gałęzi w %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s skomentował nowe połączenie gałęzi w %(when)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s skomentował zatwierdzenie %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s wysłał wiadomość do %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s wspomniał o Tobie w %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s zarejestrował na stronie w %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s otworzył nowe połączenie gałęzi w %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s skomentował nowe połączenie gałęzi w %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "Repozytorium grupy"
+
+#~ msgid "My Notifications"
+#~ msgstr "Opcje powiadomień"
+
+#~ msgid "All"
+#~ msgstr "Wszystkie"
+
+#~ msgid "Comments"
+#~ msgstr "Komentarze"
+
+#, fuzzy
+#~ msgid "Mark All Read"
+#~ msgstr "Oznacz wszystko jako przeczytane"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "Oznacz wszystko jako przeczytane"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Brak powiadomień"
+
+#, fuzzy
+#~ msgid "Show Notification"
+#~ msgstr "Pokaż powiadomienia"
+
+#~ msgid "Notifications"
+#~ msgstr "Powiadomienia"
+
+#~ msgid "Home"
+#~ msgstr "Strona Główna"
+
+#~ msgid "with"
+#~ msgstr "używając"
+
+#~ msgid "members"
+#~ msgstr "użytkownik"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "Komentarze Grupy zmian"
+
+#~ msgid "Author"
+#~ msgstr "Autor"
+
+#~ msgid "Refs"
+#~ msgstr "Gałąź/Etykieta"
+
+#, fuzzy
+#~ msgid "Commenting on line."
+#~ msgstr "Komentując linię {1}."
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "Komentarz połączenia gałęzi %s"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "Data"
+
+#~ msgid "Previous revision"
+#~ msgstr "poprzednia wersja"
+
+#~ msgid "Next revision"
+#~ msgstr "następna wersja"
+
+#~ msgid "Follow current branch"
+#~ msgstr "Obserwuj aktualną gałąź"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "Nie ma jeszcze recenzenta"
+
+#~ msgid "%d reviewer"
+#~ msgid_plural "%d reviewers"
+#~ msgstr[0] "%d recenzent"
+#~ msgstr[1] "%d recenzenci"
+#~ msgstr[2] "%d recenzentów"
+
+#~ msgid "Pull request was reviewed by all reviewers"
 #~ msgstr ""
+#~ "Połączenie gałęzi zostało zweryfikowane przez wszystkich recenzentów"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "Nie ma jeszcze gałęzi"
+
+#, fuzzy
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Recenzje wniosków połączenia gałęzi"
+
+#~ msgid "Dashboard"
+#~ msgstr "Repozytorium"
+
+#~ msgid "Group Name"
+#~ msgstr "Nazwa Grupy"
+
+#~ msgid "Remember me"
+#~ msgstr "Zapamiętaj mnie"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Zmiana awataru na"
+
+#~ msgid "Using"
+#~ msgstr "Używa"
+
+#~ msgid "Rescan option"
+#~ msgstr "ponowne skanowanie opcji"
+
+#~ msgid "Web"
+#~ msgstr "www"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "Wymagaj ssl dla operacji vcs"
+
+#~ msgid "Dashboard items"
+#~ msgstr "Pozycja panelu"
+
+#~ msgid "quick filter..."
+#~ msgstr "szybki filtr..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "Nie pamiętasz hasła?"
+
+#~ msgid "Ancestor"
+#~ msgstr "Przodek"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "Status zestawienia zmian został zmieniony na"
+
+#, fuzzy
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "%s skomentował nowe połączenie gałęzi \"%s\""
+
+#, fuzzy
+#~ msgid "The comment was made with status"
+#~ msgstr "Wniosek połączenia został zamknięty ze statusem"
+
+#~ msgid "View this user here"
+#~ msgstr "Zobacz tego użytkownika tutaj"
+
+#~ msgid "Repository Size"
+#~ msgstr "Rozmiar Repozytorium"
 
 #~ msgid "No comments."
 #~ msgstr "Brak komentarzy."
@@ -6238,12 +5944,6 @@
 #~ msgid "journal"
 #~ msgstr "dziennik"
 
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
 #~ msgid "Locked repository"
 #~ msgstr "Zablokowane repozytorium"
 
@@ -6265,9 +5965,6 @@
 #~ msgid "No Files"
 #~ msgstr "Brak Plików"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "Nazwa użytkownika \"%(username)s\" jest zabroniona"
 
@@ -6280,11 +5977,10 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "nieprawidłowe url klonowania"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr "Nieprawidłowe url klonowania, wstaw prawidłowy url http(s)/svn+http(s)"
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
+#~ msgid ""
+#~ "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
 #~ msgstr ""
+#~ "Nieprawidłowe url klonowania, wstaw prawidłowy url http(s)/svn+http(s)"
 
 #~ msgid "Defaults"
 #~ msgstr "Domyślne"
@@ -6298,36 +5994,9 @@
 #~ msgid "My Permissions"
 #~ msgstr "Moje uprawnienia"
 
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
 #~ msgid "delete"
 #~ msgstr "usuń"
 
-#~ msgid "current IP"
-#~ msgstr ""
-
 #~ msgid "Permissions Administration"
 #~ msgstr "Uprawnienia administracji"
 
@@ -6337,9 +6006,6 @@
 #~ msgid "Overwrite existing settings"
 #~ msgstr "Nadpisz ustawienia"
 
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
 #~ msgid "Default User Permissions Overview"
 #~ msgstr "Domyślne uprawnienia"
 
@@ -6355,14 +6021,9 @@
 #~ msgid "admin"
 #~ msgstr "administracja"
 
-#~ msgid "delegated admin"
+#~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr "Opcjonalnie http[s] url z którego repozytorium powinno być klonowane."
+#~ "Opcjonalnie http[s] url z którego repozytorium powinno być klonowane."
 
 #~ msgid "Remote URL"
 #~ msgstr "Url klonowania"
@@ -6370,81 +6031,18 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Pobierz z zdalnej lokalizacji"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
 #~ msgid "Non-changeable id"
 #~ msgstr "Brak zmiennej id"
 
 #~ msgid "edit"
 #~ msgstr "edycja"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
 #~ msgid "Destroy old data"
 #~ msgstr "Zniszcz stare dane"
 
-#~ msgid "check for updates"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "Domyślne uprawnienia"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "Zmiana statusu w grupie zmian"
 
@@ -6463,21 +6061,9 @@
 #~ msgid "My Repos"
 #~ msgstr "Moje repo"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "właściciel"
 
-#~ msgid "reviewer"
-#~ msgstr "recenzent"
-
 #~ msgid "Your new Kallithea password:%s"
 #~ msgstr "Nowe hasło do strony: %s"
 
@@ -6496,6 +6082,157 @@
 #~ msgid "Created by"
 #~ msgstr "utworzono"
 
-#~ msgid "You can only delete files with revision being a valid branch "
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "Potwierdź unieważnienie pamięci podręcznej repozytorium"
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr "Komentarze analizowane za pomocą %s składni od %s wsparcia."
+
+#~ msgid "Comment preview"
+#~ msgstr "Podgląd komentarza"
+
+#~ msgid "Preview"
+#~ msgstr "Podgląd"
+
+#~ msgid "New file mode"
+#~ msgstr "Nowy tryb pliku"
+
+#~ msgid "Closing."
+#~ msgstr "Zamknięcie."
+
+#~ msgid "Repository no access"
+#~ msgstr "Brak dostępu do repozytorium"
+
+#~ msgid "Repository read access"
+#~ msgstr "Repozytorium do odczytu"
+
+#~ msgid "Repository write access"
+#~ msgstr "Repozytorium do zapisu"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Administracja dostępu do repozytorium"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Grupy repozytoriów brak dostępu"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Grupy repozytoriów dostęp do odczytu"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Grupy repozytoriów dostęp do zapisu"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Repozytoria Grupy dostęp administratora"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Tworzenie repozytorium jest wyłączone"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Tworzenie repozytorium jest włączone"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Rozwidlenie repozytorium wyłączone"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Rozwidlenie repozytorium włączone"
+
+#~ msgid "Register disabled"
+#~ msgstr "Rejestracja wyłączona"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Rejestracja nowego użytkownika na stronie z ręczną aktywacją"
+
+#~ msgid "Register new user with Kallithea with auto activation"
 #~ msgstr ""
-
+#~ "Rejestracja nowego użytkownika na stronie z automatyczną aktywacją"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "Brak Korekty"
+
+#~ msgid "Rejected"
+#~ msgstr "Odrzucono"
+
+#~ msgid "Under Review"
+#~ msgstr "Objęty Przeglądem"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Grupy repozytoriów brak dostępu"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Grupy repozytoriów dostęp do odczytu"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Grupy repozytoriów dostęp do zapisu"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Repozytoria Grupy dostęp administratora"
+
+#~ msgid "User group no access"
+#~ msgstr "Ta grupa użytkowników nie ma dostępu"
+
+#~ msgid "User group read access"
+#~ msgstr "Dostęp do grupy parametrów użytkownika"
+
+#~ msgid "User group write access"
+#~ msgstr "Ta grupa użytkowników ma prawo do zapisu"
+
+#~ msgid "User group admin access"
+#~ msgstr "Ta grupa użytkowników ma uprawnienia administratora"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Tworzenie grup repozytoriów wyłączone"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Tworzenie grup repozytoriów włączone"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Tworzenie grup użytkowników wyłączone"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Tworzenie grup użytkowników właczone"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "Rejestracja użytkownika z ręczną aktywacją konta"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "Rejestracja użytkownika z automatyczną aktywacją konta"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "%(user)s chce żeby przejrzeć nowe gałęzie #%(pr_id)s: %(pr_title)s"
+
+#~ msgid "repositories"
+#~ msgstr "repozytoria"
+
+#~ msgid "No repositories found."
+#~ msgstr "Nie znaleziono repozytorium."
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Nie ma jeszcze gałęzi"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Nie ma jeszcze tagów"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Nie ma jeszcze zakładek"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "%s Zakładki"
+
+#~ msgid "%s Branches"
+#~ msgstr "%s Gałęzie"
+
+#~ msgid "Editing file"
+#~ msgstr "Edycja pliku"
+
+#~ msgid "Update"
+#~ msgstr "[zaktualizowany] użytkownik"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Otwórz nową prośbę o połączenie gałęzi"
+
+#~ msgid "%s Tags"
+#~ msgstr "Etykiety pliku %s"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Porównaj tagi"
--- a/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -8,98 +8,123 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2014-02-13 14:34+0000\n"
 "Last-Translator: marcinkuzminski <marcin@python-blog.com>\n"
-"Language-Team: Portuguese (Brazil) "
-"<https://hosted.weblate.org/projects/kallithea/kallithea/pt_BR/>\n"
-"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/"
+"kallithea/kallithea/pt_BR/>\n"
+"Language: pt_BR\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Não há nenhum changeset ainda"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Nenhum"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(fechado)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Mostrar espaços em branco"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorar espaços em branco"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "Vote para estado do pull request"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Pull request excluído com sucesso"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr "Mudar o estado de um changeset associado a um pull request não é permitido"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Confirme para travar repositório"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "revisões"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-"A requisição não pôde ser compreendida pelo servidor devido à sintaxe mal"
-" formada."
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"A requisição não pôde ser compreendida pelo servidor devido à sintaxe mal "
+"formada."
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Acesso não autorizado ao recurso"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Você não tem permissão para ver esta página"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "O recurso não pôde ser encontrado"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
@@ -107,372 +132,359 @@
 "O servidor encontrou uma condição inesperada que o impediu de satisfazer "
 "a requisição."
 
-#: kallithea/controllers/feed.py:55
+#: kallithea/controllers/feed.py:63
 #, python-format
-msgid "Changes on %s repository"
-msgstr "Modificações no repositório %s"
-
-#: kallithea/controllers/feed.py:56
+msgid "%s committed on %s"
+msgstr "%s commitados em %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Conjunto de mudanças era grande demais e foi cortado..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "%s - feed %s"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Conjunto de mudanças era grande demais e foi cortado..."
-
-#: kallithea/controllers/feed.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
-msgid "%s committed on %s"
-msgstr "%s commitados em %s"
-
-#: kallithea/controllers/files.py:91
+msgid "Changes on %s repository"
+msgstr "Modificações no repositório %s"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Clique aqui para adicionar um novo arquivo"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, fuzzy, python-format
 msgid "%s at %s"
 msgstr "em %s e %s"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Este repositório foi travado por %s em %s"
 
-#: kallithea/controllers/files.py:317
-#, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
+#: kallithea/controllers/files.py:312
+#, fuzzy
 msgid "You can only delete files with revision being a valid branch"
 msgstr "Só é possível editar arquivos quando a revisão é um ramo válido"
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Ocorreu um erro ao realizar commit"
 
-#: kallithea/controllers/files.py:377
-#, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
+#: kallithea/controllers/files.py:372
+#, fuzzy
 msgid "You can only edit files with revision being a valid branch"
 msgstr "Só é possível editar arquivos quando a revisão é um ramo válido"
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Arquivo %s editado via Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Sem modificações"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Commit realizado com sucesso para %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Arquivo adicionado via Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Nenhum conteúdo"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Nenhum nome de arquivo"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr "O caminho deve ser relativo e não pode conter .."
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Downloads desabilitados"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Revisão desconhecida %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Repositório vazio"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Tipo de arquivo desconhecido"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Conjuntos de mudanças"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Ramos"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Etiquetas"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "Ocorreu um erro ao bifurcar o repositório %s"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Repositórios"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Ramo"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Ramos Fechados"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Diário Público"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Diário"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Você foi registrado no Kallithea com sucesso"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Você foi registrado no %s com sucesso"
+
+#: kallithea/controllers/login.py:189
 #, fuzzy
 msgid "A password reset confirmation code has been sent"
 msgstr "Seu link de reinicialização de senha foi enviado"
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 #, fuzzy
 msgid "Invalid password reset token"
 msgstr "Link para trocar senha"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
-#, fuzzy, python-format
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Conjunto de Mudanças"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "Especial"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "Ramos pares"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Marcadores"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+#, fuzzy
+msgid "Error occurred while creating pull request"
+msgstr "Ocorreu um erro durante o envio do pull request"
+
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Novo pull request criado com sucesso"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "Revisores do pull request"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
 #, fuzzy
 msgid "No description"
 msgstr "Descrição"
 
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "Novo pull request criado com sucesso"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-#, fuzzy
-msgid "Error occurred while creating pull request"
-msgstr "Ocorreu um erro durante o envio do pull request"
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-#, fuzzy
-msgid "Pull request update created"
-msgstr "Revisores do pull request"
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:432
 #, fuzzy
 msgid "Pull request updated"
 msgstr "Pull requests para %s"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Pull request excluído com sucesso"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:615
+#: kallithea/controllers/pullrequests.py:543
 #, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-#, fuzzy
-msgid "No permission to change pull request status"
-msgstr "Vote para estado do pull request"
-
-#: kallithea/controllers/pullrequests.py:729
-#, fuzzy
-msgid "Closing."
-msgstr "carregando ..."
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "Consulta de busca inválida. Tente usar aspas."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Não há índice onde pesquisa. Por favor execute o indexador whoosh"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 #, fuzzy
 msgid "An error occurred during search operation."
 msgstr "Ocorreu um erro durante essa operação de busca"
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 #, fuzzy
 msgid "No data ready yet"
 msgstr "Ainda não há dados carregados"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "As estatísticas estão desabillitadas para este repositório"
 
@@ -484,151 +496,154 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Configurações padrão atualizadas com sucesso"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "Ocorreu um erro durnge a atualização dos padrões"
 
-#: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
 #, fuzzy
 msgid "Forever"
 msgstr "para sempre"
 
-#: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
 msgid "5 minutes"
 msgstr "cinco minutos"
 
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "uma hora"
+
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "uma hora"
-
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "um dia"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "um mês"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Ocorreu um erro durante a criação de um gist"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist %s excluído"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 #, fuzzy
 msgid "Unmodified"
 msgstr "Última alteração"
 
-#: kallithea/controllers/admin/gists.py:262
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
-msgstr "Você não pode editar esse usuário pois ele é crucial para toda a aplicação"
-
-#: kallithea/controllers/admin/my_account.py:129
+msgstr ""
+"Você não pode editar esse usuário pois ele é crucial para toda a aplicação"
+
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Sua conta foi atualizada com sucesso"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Ocorreu um erro durante a atualização do usuário %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "Email %s adicionado ao usuário"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Ocorreu um erro durante o salvamento do email"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "Email removido do usuário"
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Ler"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Gravar"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -636,627 +651,629 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Administrador"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Desabilitado"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Permitido com ativação manual de conta"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Permitido com ativação automática de conta"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Ativação manual de conta externa"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Ativação automática de conta externa"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Desabilitado"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Permitido com ativação manual de conta"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Permitido com ativação automática de conta"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Ativação manual de conta externa"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Ativação automática de conta externa"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Habilitado"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Ocorreu um erro durante a atualização das permissões"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Ocorreu um erro durante a criação do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Grupo de repositórios %s atualizado"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Ocorreu um erro durante a atualização do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Esse grupo contém %s repositórios e não pode ser excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Este grupo contém %s subgrupos e não pode ser excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Grupo de repositórios %s excluído"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Ocorreu um erro durante a exclusão do grupo de repositórios %s"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Você não pode revocar sua própria permissão de administrador"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Permissões atualizadas do Grupo de Repositórios"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "Ocorreu um erro durante a revocação das permissões"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Erro ao criar repositório %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Repositório %s criado de %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "Repositório %s bifurcado como %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Repositório %s criado"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Repositório %s atualizado com sucesso"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Ocorreu um erro durante a atualização do repositório %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "%s bifurcações excluídas"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Repositório %s excluído"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, fuzzy, python-format
 msgid "Cannot delete repository %s which still has forks"
-msgstr "Nao é possível excluir %s pois ele ainda contém bifurcações vinculadas"
-
-#: kallithea/controllers/admin/repos.py:326
+msgstr ""
+"Nao é possível excluir %s pois ele ainda contém bifurcações vinculadas"
+
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Ocorreu um erro durante a exclusão de %s"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Permissões do repositório atualizadas"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
 msgstr "Ocorreu um erro durante a criação do campo"
 
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Ocorreu um erro durante a remoção do campo"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Atualizada a visibilidade do repositório no diário público"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr "Ocorreu um erro ao ajustar esse repositório no diário público"
 
-#: kallithea/controllers/admin/repos.py:512
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Nada"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Marcado repositório %s como bifurcação de %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "Ocorreu um erro durante essa operação"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 #, fuzzy
 msgid "Repository has been locked"
 msgstr "Repositório não está travado"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 #, fuzzy
 msgid "Repository has been unlocked"
 msgstr "Repositório não está travado"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "Ocorreu um erro durante o destravamento"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "Ocorreu um erro ao invalidar o cache"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "Realizado pull de localização remota"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "Ocorreu um erro ao realizar pull de localização remota"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "Ocorreu um erro ao excluir estatísticas de repositório"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "Configurações de VCS atualizadas"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
-msgstr "Ocorreu um erro durante a atualização das configurações da aplicação"
-
-#: kallithea/controllers/admin/settings.py:211
+msgstr ""
+"Ocorreu um erro durante a atualização das configurações da aplicação"
+
+#: kallithea/controllers/admin/settings.py:174
 #, fuzzy, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr "Repositórios varridos com sucesso adicionados: %s ; removidos: %s"
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Invalidate cache for all repositories"
 msgid "Invalidated %s repositories"
 msgstr "Invalidar o cache para todos os repositórios"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "Configurações da aplicação atualizadas"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Configurações de visualização atualizadas"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
-msgstr "Ocorreu um erro durante a atualização das configurações de visualização"
-
-#: kallithea/controllers/admin/settings.py:368
+msgstr ""
+"Ocorreu um erro durante a atualização das configurações de visualização"
+
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+msgid "Hook already exists"
+msgstr "Ainda não há dados carregados"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Adicionado novo gancho"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Atualizados os ganchos"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "Ocorreu um erro durante a criação do hook"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Tarefa de reindexação do whoosh agendada"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "Grupo de usuários %s criado"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Ocorreu um erro durante a criação do grupo de usuários %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "Grupo de usuários %s atualizado"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Ocorreu um erro durante a atualização do grupo de usuários %s"
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "Grupo de usuários excluído com sucesso"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr "Ocorreu um erro durante a exclusão do grupo de usuários"
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "O grupo destino não pode ser o mesmo"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "Permissões do Grupo de Usuários atualizadas"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "Permissões atualizadas"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "Ocorreu um erro durante o salvamento das permissões"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "Usuário %s criado"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Ocorreu um erro durante a criação do usuário %s"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "Usuário atualizado com sucesso"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "Usuário excluído com sucesso"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "Ocorreu um erro ao excluir o usuário"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Ocorreu um erro durante o salvamento do IP"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IP %s não permitido"
-
-#: kallithea/lib/auth.py:757
-msgid "Invalid API key"
-msgstr ""
-
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
+#: kallithea/lib/auth.py:824
 msgid "You need to be a registered user to perform this action"
 msgstr "Você precisa ser um usuário registrado para realizar essa ação"
 
-#: kallithea/lib/auth.py:864
+#: kallithea/lib/auth.py:852
 msgid "You need to be signed in to view this page"
 msgstr "Você precisa estar logado para ver essa página"
 
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "Conjunto de alterações não encontrado"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Arquivo binário"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Conjunto de mudanças é grande demais e foi cortado, use o menu de "
 "diferenças para ver as diferenças"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Nenhuma alteração detectada"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Excluído ramo: %s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Tag criada: %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Conjunto de alterações não encontrado"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Ver todos os conjuntos de mudanças combinados %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 #, fuzzy
 msgid "Compare view"
 msgstr "comparar exibir"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "e"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s mais"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "revisões"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "nome da bifurcação %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "Pull request #%s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "repositório [excluído]"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "repositório [criado]"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "repositório [criado] como uma bifurcação"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "repositório [bifurcado]"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "repositório [atualizado]"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "[baixado] archive do repositório"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[excluir] repositório"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "usuário [criado]"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "usuário [atualizado]"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "[criado] grupo de usuários"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "[atualizado] grupo de usuários"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[comentado] em revisão no repositório"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "[comentado] no pull request para"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "[fechado] pull request para"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[realizado push] para"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[commitado via Kallithea] no repositório"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[pulled do remote] no repositório"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[realizado pull] a partir de"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[passou a seguir] o repositório"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[parou de seguir] o repositório"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " e mais %s"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Nenhum arquivo"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "novo arquivo"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "mod"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "excluir"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "renomear"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1264,752 +1281,351 @@
 "repositories"
 msgstr ""
 "O repositório %s não está mapeado ao BD. Talvez ele tenha sido criado ou "
-"renomeado a partir do sistema de arquivos. Por favor, execute a aplicação"
-" outra vez para varrer novamente por repositórios"
-
-#: kallithea/lib/utils2.py:415
+"renomeado a partir do sistema de arquivos. Por favor, execute a aplicação "
+"outra vez para varrer novamente por repositórios"
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d ano"
 msgstr[1] "%d anos"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d mês"
 msgstr[1] "%d meses"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d dia"
 msgstr[1] "%d dias"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d hora"
 msgstr[1] "%d horas"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d minuto"
 msgstr[1] "%d minutos"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d segundo"
 msgstr[1] "%d segundos"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "em %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s atrás"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "em %s e %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s e %s atrás"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "agora há pouco"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Nenhum acesso ao repositório"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Acesso de leitura ao repositório"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Acesso de escrita ao repositório"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Acesso administrativo ao repositório"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Nenhum acesso ao Grupo de Repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Acesso de leitura ao Grupo de Repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Acesso de escrita ao Grupo de Repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Acesso administrativo ao Grupo de Repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Administrador do Kallithea"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Criação de repositórios desabilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Criação de repositórios habilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Bifurcação de repositórios desabilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Bifurcação de repositórios habilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Registro desabilitado"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Registro de novo usuário no Kallithea com ativação manual"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Registro de novo usuário no Kallithea com auto-ativação"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Não Revisado"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Aprovado"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Rejeitado"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "Sob Revisão"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "nível superior"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Sem acesso ao grupo de repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Acesso de leitura ao grupo de repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Acesso de escrita ao grupo de repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Acesso administrativo ao grupo de repositórios"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Sem acesso ao grupo de usuários"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Acesso de leitura ao grupo de usuários"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Acesso de escrita ao grupo de usuários"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Acesso administrativo ao grupo de usuários"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Criação de Grupo de Repositórios desatilibada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Criação de Grupo de Repositórios habilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Criação de Grupo de Usuários desabilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Criação de Grupo de Usuários habilitada"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Registro desatilitado"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Registro de Usuário com ativação manual de conta"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Registro de Usuário com ativação automática de conta"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "na linha %s"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Menção]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "nível superior"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Administrador do Kallithea"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "Acesso não autorizado ao recurso"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "Acesso não autorizado ao recurso"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "Grupo de repositórios %s criado"
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 #, fuzzy
 msgid "Only admins can create user groups"
 msgstr "Criar grupos de usuários"
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 #, fuzzy
 msgid "Non-admins can create user groups"
 msgstr "Criar grupos de usuários"
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Criar repositórios"
 
-#: kallithea/model/db.py:1696
-#, fuzzy
-#| msgid "Non-admins can can fork repositories"
+#: kallithea/model/db.py:1733
+#, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "Invalidar o cache para todos os repositórios"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Registro desatilitado"
+
+#: kallithea/model/db.py:1736
 #, fuzzy
 msgid "User registration with manual account activation"
 msgstr "Registro de Usuário com ativação manual de conta"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 #, fuzzy
 msgid "User registration with automatic account activation"
 msgstr "Registro de Usuário com ativação automática de conta"
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 #, fuzzy
 msgid "Not reviewed"
 msgstr "Não Revisado"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 #, fuzzy
 msgid "Under review"
 msgstr "Sob Revisão"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "Aprovado"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Aprovado"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Por favor entre um login"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Entre um valor com %(min)i caracteres ou mais"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Por favor entre com uma senha"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Entre com %(min)i caracteres ou mais"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, fuzzy, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s comentou no changeset em %(when)s"
-
-#: kallithea/model/notification.py:256
-#, fuzzy, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s enviou mensagem em %(when)s"
-
-#: kallithea/model/notification.py:257
-#, fuzzy, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s mencionou-o em %(when)s"
-
-#: kallithea/model/notification.py:258
-#, fuzzy, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s registrou-se no Kallithea em %(when)s"
-
-#: kallithea/model/notification.py:259
-#, fuzzy, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s abriu um novo pull request em %(when)s"
-
-#: kallithea/model/notification.py:260
+#: kallithea/model/notification.py:165
 #, fuzzy, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s comentou no pull request em %(when)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s comentou no changeset em %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s enviou mensagem em %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s mencionou-o em %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s registrou-se no Kallithea em %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s abriu um novo pull request em %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s comentou no pull request em %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "[comentado] no pull request para"
+
+#: kallithea/model/notification.py:168
 #, fuzzy, python-format
 msgid "New user %(new_username)s registered"
 msgstr "O username \"%(new_username)s\" não é válido"
 
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "[comentado] no pull request para"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 #, fuzzy
 msgid "Closing"
 msgstr "Usando"
 
-#: kallithea/model/pull_request.py:137
+#: kallithea/model/pull_request.py:76
 #, fuzzy, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/scm.py:812
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s"
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Create Pull Request"
+msgid "Cannot create empty pull request"
+msgstr "Criar Pull Request"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "Confirme para excluir este pull request"
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "tip mais recente"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "Novo registro de usuário"
 
-#: kallithea/model/user.py:256
-#, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
+#: kallithea/model/user.py:253
+#, fuzzy
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Você não pode remover esse usuário, pois ele é crucial para toda a "
 "aplicação"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
@@ -2018,16 +1634,16 @@
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, fuzzy, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -2036,64 +1652,64 @@
 "usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
 "Troque os donos ou remova esses repositórios. %s"
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "Link para trocar senha"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 #, fuzzy
 msgid "Password reset notification"
 msgstr "Link para trocar senha"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "O valor não pode ser uma lista vazia"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "O username \\\"%(username)s\\\" já existe"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, fuzzy, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "O username \"%(username)s\" não é válido"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 #, fuzzy
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
 "Nome de usuário pode conter somente caracteres alfanuméricos, sublinha, "
 "pontos e hífens e deve iniciar com caractere alfanumérico"
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "O username \"%(username)s\" não é válido"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "Nome inválido de grupo de usuários"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "O grupo de usuários \"%(usergroup)s\" já existe"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
@@ -2102,107 +1718,107 @@
 "underscores, pontos ou hífens, e deve começar om um caractere alfa-"
 "numérico"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Não é possível associar esse grupo como progenitor"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "O grupo \\\"%(group_name)s\\\" já existe"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Um repositório com o nome \"%(group_name)s\" já existe"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Caracteres inválidos (não-ascii) na senha"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Senhas não conferem"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 #, fuzzy
 msgid "Invalid username or password"
 msgstr "senha inválida"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "Descompasso de Token"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, fuzzy, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "O nome de repositório %(repo)s não é permitido"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Um repositório chamado %(repo)s já existe"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "Um repositório \"%(repo)s\" já existe no grupo \"%(group)s\""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Um Grupo de Repositórios chamado \"%(repo)s\" já existe"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "repositório privado"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "A bifurcação deve ser do mesmo tipo que o pai"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "Você não tem permissão para criar um repositório neste grupo"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "você não tem permissão para criar um repositório na raiz"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr "Você não tem permissão para criar um grupo neste local"
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr "Este nome de usuário ou de grupo de usuários não é válido"
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "Esse não é um caminho válido"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "Esse endereço de e-mail já está tomado"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "o e-mail \"%(email)s\" não existe."
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
@@ -2210,248 +1826,153 @@
 "O atributo de login LDAP do CN deve ser especificado - isto é o nome do "
 "atributo que é equivalente ao 'nome de usuário'"
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Por favor, forneça um endereço válido IPv4 ou IPv6"
 
-#: kallithea/model/validators.py:775
+#: kallithea/model/validators.py:732
 #, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr "O tamanho da rede (bits) deve estar no intervalo 0-32 (não %(bits)r)"
-
-#: kallithea/model/validators.py:808
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+"O tamanho da rede (bits) deve estar no intervalo 0-32 (não %(bits)r)"
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr "O nome da chave só pode conter letras, underscore, hífen ou dígitos"
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "O nome de arquivo não pode estar dentro de um diretório"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Painel de Controle"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "filtro rápido..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "repositórios"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr "Você tem direitos de administrador neste grupo e pode editá-lo"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Repositório"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Descrição"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Nome"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Última Alteração"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Ponta"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Dono"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "Clique para ordenar em ordem crescente"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "Clique para ordenar em ordem descrescente"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Nenhum repositório encontrado."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Erro de dados."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Carregando..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Entrar"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Log in em %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "Nome de usuário"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Senha"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Lembre-se de mim"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "Esqueceu sua senha ?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Não possui uma conta ?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Entrar"
 
@@ -2459,34 +1980,34 @@
 msgid "Password Reset"
 msgstr "Senha Trocada"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
-#, fuzzy, python-format
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 #, fuzzy
 msgid "Reset Your Password"
 msgstr "Esqueceu sua senha ?"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 #, fuzzy
 msgid "Send Password Reset Email"
 msgstr "Link para trocar senha"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
 msgid ""
 "A password reset link will be sent to the specified email address if it "
@@ -2495,119 +2016,102 @@
 "Link de reinicialização de senha será enviado ao endereço de e-mail "
 "correspondente"
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
 msgid "New Password"
 msgstr "Nova senha"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
 msgid "Confirm New Password"
 msgstr "Sua nova senha"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Inscrever-se"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "Repita a senha"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Primeiro Nome"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Último Nome"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Ainda não há ramos"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Ramos Fechados"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Ainda não há etiquetas"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Ainda não há marcadores"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 #, fuzzy
 msgid "Admin Journal"
 msgstr "Diário do administrador"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "filtro de diário..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 #, fuzzy
 msgid "Filter"
 msgstr "filtro"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, fuzzy, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2615,30 +2119,16 @@
 msgstr[1] "%s entradas"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Ação"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Repositório"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Data"
 
@@ -2646,7 +2136,7 @@
 msgid "From IP"
 msgstr "A partir do IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "Ainda não há ações"
 
@@ -2655,76 +2145,76 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Salvar"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "Padrões de repositórios"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Tipo"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Repositório privado"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
@@ -2732,34 +2222,34 @@
 "Repositórios privados são visíveis somente por pessoas explicitamente "
 "adicionadas como colaboradores."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Habilitar estatísticas"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Habilitar janela de estatísticas na página de sumário."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Habilitar downloads"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Habilitar menu de descarregar na página de sumário."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Habilitar travas"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "Habilitar trava-por-pulling no repositório."
 
@@ -2768,45 +2258,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Descrição do gist ..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr "Tempo de vida do Gist"
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Expira"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "nunca"
@@ -2816,7 +2306,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Cancelar"
 
@@ -2839,16 +2330,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "Criado"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Não há nenhum gist ainda"
 
@@ -2857,45 +2348,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "Limpar"
 
@@ -2905,113 +2396,111 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Excluir"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Editar"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "criado"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "Mostrar original"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Minha Conta"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 #, fuzzy
 msgid "Email Addresses"
 msgstr "Novo endereço de email"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "repositórios"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "Criar repositórios"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
@@ -3019,75 +2508,92 @@
 msgid "Show Permissions"
 msgstr "Copiar permissões"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, fuzzy, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "Confirme para excluir este IP: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 #, fuzzy
 msgid "Expired"
 msgstr "Expira"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "Confirme para excluir este IP: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 #, fuzzy
 msgid "Remove"
 msgstr "Removido"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 #, fuzzy
 msgid "New API key"
 msgstr "Próxima chave de campo"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Adicionar"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Confirme para excluir este email: %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "Novo endereço de email"
 
@@ -3096,109 +2602,71 @@
 msgid "Change Your Account Password"
 msgstr "Sua nova senha"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Nova senha"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
+#: kallithea/templates/admin/my_account/my_account_password.html:39
 #, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
 msgstr "Altere o seu avatar em"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "Usando"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 #, fuzzy
 msgid "Repositories You Own"
 msgstr "Nenhum repositório encontrado."
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Nenhum registro encontrado."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Nome"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 #, fuzzy
 msgid "Repositories You are Watching"
 msgstr "Localização dos repositórios"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Minhas Notificações"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Todos"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Comentários"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Pull Requests"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-#, fuzzy
-msgid "Mark All Read"
-msgstr "Marcar tudo como lido"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Ainda não há notificações aqui"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-#, fuzzy
-msgid "Show Notification"
-msgstr "Mostrar notificação"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Notificações"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 #, fuzzy
 msgid "Default Permissions"
 msgstr "Permissões padrão"
@@ -3213,45 +2681,51 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Acesso anônimo"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "Acesso anônimo"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 "Todas as permissões padrão em cada repositório serão modificadas para a "
-"permissão escolhida, note que todas as permissões padrão customizadas nos"
-" repositórios serão perdidas"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+"permissão escolhida, note que todas as permissões padrão customizadas nos "
+"repositórios serão perdidas"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Repositório existente?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "Grupo de repositórios"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "Grupo de repositórios"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
@@ -3261,165 +2735,166 @@
 "modificadas para a permissão escolhida, note que todas as permissões "
 "padrão customizadas em grupos de repositórios serão perdidas"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "Grupo de repositórios %s atualizado"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "Grupo de usuários"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 #, fuzzy
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
-"Todas as permissões padrão em cada repositório serão reinicializadas para"
-" as permissões escolhidas. Note que todas as permissões padrão "
+"Todas as permissões padrão em cada repositório serão reinicializadas para "
+"as permissões escolhidas. Note que todas as permissões padrão "
 "customizadas nos repositórios serão perdidas"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "Criação de repositório"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "Criação de grupo de usuários"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Bifurcação de repositório"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Registro"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "Ativação de autenticação de conta externa"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Confirme para excluir este IP: %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 #, fuzzy
 msgid "All IP addresses are allowed."
 msgstr "Todos os endereços IP são permitidos"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Novo endereço IP"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Nome do grupo"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Progenitor do grupo"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Configurações"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Permissões"
@@ -3444,12 +2919,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Criado em"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3460,8 +2935,30 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "Disabled"
+msgid "Visible"
+msgstr "Desabilitado"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "revisões"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#, fuzzy
+#| msgid "Add user group"
+msgid "Add/Edit groups"
+msgstr "Adicionar grupo de usuários"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 #, fuzzy
 msgid "User/User Group"
@@ -3469,8 +2966,8 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3479,112 +2976,106 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 #, fuzzy
 msgid "Revoke"
 msgstr "revogar"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "Adicionar novo"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 #, fuzzy
 msgid "Apply to children"
 msgstr "aplicar aos filhos"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-"Habilitar trava-por-pulling no grupo. Esta opção será aplicada a todos os"
-" outros grupos e repositórios dentro deles"
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+"Habilitar trava-por-pulling no grupo. Esta opção será aplicada a todos os "
+"outros grupos e repositórios dentro deles"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 #, fuzzy
 msgid "Remove this group"
 msgstr "Criar grupos de usuários"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 #, fuzzy
 msgid "Confirm to delete this group"
 msgstr "Confirme para excluir este grupo de usuário: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "Início"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "com"
+#, fuzzy, python-format
+#| msgid "Repository group"
+msgid "Repository group %s"
+msgstr "Grupo de repositórios"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 #, fuzzy
 msgid "Repository Groups Administration"
 msgstr "Administração de grupos de repositórios"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 #, fuzzy
 msgid "Number of Top-level Repositories"
 msgstr "Número de repositórios de nível superior"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "repositório [criado]"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
 msgstr ""
 "Seja sucinto e objetivo. Use um arquivo README para descrições mais "
 "longas."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "Opcionalmente selecione um grupo no qual colocar esse repositório."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Tipo de repositório a criar."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Revisão de pouso"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3613,27 +3104,27 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 #, fuzzy
 msgid "Extra Fields"
 msgstr "Campos extras"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Remoto"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Estatísticas"
 
@@ -3643,33 +3134,31 @@
 msgstr "Progenitor do grupo"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 #, fuzzy
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 "Marque manualmente este repositório como uma bifurcação de um outro da "
 "lista"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 #, fuzzy
 msgid "Public Journal Visibility"
 msgstr "diário público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "Remover do diário público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 #, fuzzy
 msgid "Add to Public Journal"
 msgstr "diário público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 #, fuzzy
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
@@ -3678,41 +3167,41 @@
 "Todas as ações feitas nesse repositório serão acessíveis a todos no "
 "diário público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 #, fuzzy
 msgid "Change Locking"
 msgstr "Habilitar travas"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 #, fuzzy
 msgid "Confirm to unlock repository."
 msgstr "Confirme para destravar repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 #, fuzzy
 msgid "Unlock Repository"
 msgstr "Repositório público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, fuzzy
 msgid "Confirm to lock repository."
 msgstr "Confirme para travar repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 #, fuzzy
 msgid "Lock Repository"
 msgstr "Repositório público"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Repositório não está travado"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3720,33 +3209,33 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Confirma excluir esse repositório: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 #, fuzzy
 msgid "Delete this Repository"
 msgstr "[excluir] repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, fuzzy, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] "este repositório tem %s bifurcação"
 msgstr[1] "este repositório tem %s bifurcações"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "Desassociar bifurcações"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "Excluir bifurcações"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3758,12 +3247,7 @@
 msgid "Invalidate Repository Cache"
 msgstr "Invalidar cache do repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-#, fuzzy
-msgid "Confirm to invalidate repository cache."
-msgstr "Confirma invalidar cache do repositório"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 #, fuzzy
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
@@ -3772,102 +3256,108 @@
 "Invalidar manualmente o cache deste repositório. No próximo acesso o "
 "repositório será cacheado novamente"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 #, fuzzy
 msgid "List of Cached Values"
 msgstr "Lista de valores cacheados"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "Prefixo"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Chave"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Ativo"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Confirme para excluir este campo: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "Próxima chave de campo"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "Próximo rótulo de campo"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "Entre com o rótulo curto"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "Nova descrição de campo"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "Entre com a descrição de um campo"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 #, fuzzy
 msgid "Extra fields are disabled."
 msgstr "Registro desabilitado"
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "repositório privado"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "repositório [bifurcado]"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Repositório %s criado"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "[pulled do remote] no repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "Confirma realizar pull de alterações a partir de lado remoto"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "repositório privado"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3877,35 +3367,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "repositório [criado]"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "Repositório"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr "Revisão padrão para página de arquivos, downloads, whoosh e readme"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Mudar o dono desse repositório."
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
 msgid "Processed commits"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:6
 msgid "Processed progress"
 msgstr ""
 
@@ -3924,7 +3419,7 @@
 msgid "Repositories Administration"
 msgstr "Administração de repositórios"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3946,7 +3441,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Ganchos"
 
@@ -3958,285 +3453,277 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Enviar"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "Marca do site"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "Realm de autenticação HTTP"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 #, fuzzy
 msgid "Save Settings"
 msgstr "Salvar configurações"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+#, fuzzy
+msgid "Custom Hooks"
+msgstr "Ganchos customizados"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-#, fuzzy
-msgid "Custom Hooks"
-msgstr "Ganchos customizados"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "Falha ao remover gancho"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Rescan option"
+msgid "Rescan options"
 msgstr "Opção de varredura"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 #, fuzzy
 msgid "Delete records of missing repositories"
 msgstr "Buscar nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "Invalidar o cache para todos os repositórios"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 #, fuzzy
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr "Invalidar o cache para todos os repositórios"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "Opção de construção do índice"
 
-#: kallithea/templates/admin/settings/settings_search.html:12
+#: kallithea/templates/admin/settings/settings_search.html:9
 msgid "Build from scratch"
 msgstr "Construir do zero"
 
-#: kallithea/templates/admin/settings/settings_search.html:15
+#: kallithea/templates/admin/settings/settings_search.html:12
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Reindexar"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
 #, fuzzy
 msgid "Git version"
 msgstr "Editar Permissão"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Web"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "Requer SSL para operações de VCS"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "Mostrar tamanho do repositório após o push"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "Armazenar registro de comandos de push dos usuários"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "Armazenar registro de comandos de pull dos usuários"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "Atualizar repositório após realizar push (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 #, fuzzy
 msgid "Mercurial extensions"
 msgstr "Extensões do Mercurial"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Habilitar extensão largefiles"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Habilitar extensão hgsubversion"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "Criar repositórios"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
 msgstr ""
 "Clique para destravar. Você deve reiniciar o Kallithea para que esta "
 "configuração tenha efeito."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Geral"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "Usar campos extras do repositório"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr "Permite armazenar campos customizados adicionais por repositório."
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Mostrar versão do Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4253,94 +3740,103 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+#, fuzzy
+msgid "Clone URL"
+msgstr "URL de clonagem"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "Itens do dashboard"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "Tamanho do Repositório"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Ícones"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "Mostrar ícone de repositório público nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "Mostrar ícone de repositório privado nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "Mostrar ícone de repositório público nos repositórios"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Meta-Tagging"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "Adicionar grupo de usuários"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4360,13 +3856,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Membros"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr "Confirme para excluir este grupo de usuário: %s"
@@ -4375,15 +3871,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Nenhum membro ainda"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Membros escolhidos do grupo"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Membros disponíveis"
 
@@ -4398,17 +3894,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "Usuários"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "Confirmação de senha"
 
@@ -4427,12 +3923,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4441,7 +3937,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "Confirma excluir este usuário: %s"
@@ -4450,24 +3946,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "Confirmação de nova senha"
 
@@ -4476,237 +3964,238 @@
 msgid "Users Administration"
 msgstr "Administração de usuários"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "Instância de servidor: %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Repositório Mercurial"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Repositório Git"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 #, fuzzy
 msgid "Create Fork"
 msgstr "Excluir bifurcações"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Sumário"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "Registro de alterações"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Arquivos"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "Trocar Para"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr "Mostrar Pull Requests para %s"
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Pull Requests"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "Opções"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 #, fuzzy
 msgid "Compare Fork"
 msgstr "Compare bifurcação"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "Compare"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "Pesquisar"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr "Destravar"
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr "Travar"
 
-#: kallithea/templates/base/base.html:173
+#: kallithea/templates/base/base.html:174
 msgid "Follow"
 msgstr "Seguir"
 
-#: kallithea/templates/base/base.html:174
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr "Parar de seguir"
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "Bifurcação"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr "Criar Pull Request"
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr "Mostrar Pull Requests para %s"
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "Trocar Para"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "Mostrar atividade recente"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "Diário público"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "Mostrar gists públicos"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gists"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 #, fuzzy
 msgid "All Public Gists"
 msgstr "Todos os gists públicos"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 #, fuzzy
 msgid "My Public Gists"
 msgstr "Meus gists públicos"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 #, fuzzy
 msgid "My Private Gists"
 msgstr "Meus gists privados"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "Buscar nos repositórios"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 #, fuzzy
 msgid "My Pull Requests"
 msgstr "Pull requests"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 #, fuzzy
 msgid "Not Logged In"
 msgstr "Não logado"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 #, fuzzy
 msgid "Login to Your Account"
 msgstr "Entrar com sua conta"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "Esqueceu a senha ?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "Não possui uma conta ?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "Sair"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Padrões de repositórios"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "Criar repositórios"
 
-#: kallithea/templates/base/default_perms_box.html:33
+#: kallithea/templates/base/default_perms_box.html:27
 msgid "Select this option to allow repository creation for this user"
 msgstr ""
 "Selecione esta opção para permitir a criação de repositórios para este "
 "usuário"
 
-#: kallithea/templates/base/default_perms_box.html:40
+#: kallithea/templates/base/default_perms_box.html:33
 msgid "Create user groups"
 msgstr "Criar grupos de usuários"
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 "Selecione esta opção para permitir a criação de grupos de usuários para "
 "este usuário"
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "Bufurcar repositórios"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
-"Selecione esta opção para permitir a bifurcação de repositórios para este"
-" usuário"
+"Selecione esta opção para permitir a bifurcação de repositórios para este "
+"usuário"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "Mostrar"
 
@@ -4715,239 +4204,185 @@
 msgstr "Nenhuma permissão definida ainda"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Permissão"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Editar Permissão"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+#, fuzzy
+msgid "Submitting ..."
+msgstr "Enviando..."
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "Habilitar downloads"
+
+#: kallithea/templates/base/root.html:31
 #, fuzzy
 msgid "Add Another Comment"
 msgstr "Adicionar outro comentário"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "Parar de seguir este repositório"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "Passar a seguir este repositório"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "Grupo"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "membros"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "Carregando..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "carregando ..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "Busca truncada"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "Nenhum arquivo encontrado"
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 #, fuzzy
 msgid "Open New Pull Request from {0}"
 msgstr "Comentar no pull request #%s"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr ""
 
-#: kallithea/templates/base/root.html:33
+#: kallithea/templates/base/root.html:41
 #, fuzzy
 msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr "Mostrar changesets selecionados __S -> __E"
 
-#: kallithea/templates/base/root.html:34
+#: kallithea/templates/base/root.html:42
 #, fuzzy
 msgid "Selection Link"
 msgstr "Link da seleção"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 #, fuzzy
 msgid "Collapse Diff"
 msgstr "Colapsar diff"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 #, fuzzy
 msgid "Expand Diff"
 msgstr "Expandir diff"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "revisões"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "Falhou ao revocar a permissão"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 #, fuzzy
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "confirme para revogar permissão para {0}: {1} ?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 #, fuzzy
 msgid "Specify changeset"
 msgstr "%s Changeset"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "%s Bookmarks"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Autor"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Revisão"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "%s Ramos"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "Clique para ordenar em ordem crescente"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "Clique para ordenar em ordem descrescente"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Nenhum registro encontrado."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Erro de dados."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Carregando..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "%s Changelog"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] "mostrando %d de %d revisão"
 msgstr[1] "mostrando %d de %d revisões"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "Deselecionar seleção"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "Confirme para travar repositório"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "Comparar bifurcação com %s"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, fuzzy, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "Comparar bifurcação com %s"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 #, fuzzy
 msgid "Branch filter:"
 msgstr "filtro"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, fuzzy, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-"Estado do changeset: %s\n"
-"Clique para abrir os pull request #%s associado"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "Estado do changeset: %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "O changeset tem comentários"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "Bookmark %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "Tag %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Ramo %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Ainda não há alteações"
 
@@ -4963,7 +4398,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "Adicionado"
 
@@ -4977,132 +4412,147 @@
 msgid "Affected %s files"
 msgstr "Afetados %s arquivos"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Idade"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Refs"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Adicionar ou enviar arquivos diretamente pelo Kallithea"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Fazer push de novo repositório"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "Repositório existente?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, fuzzy, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"Estado do changeset: %s\n"
+"Clique para abrir os pull request #%s associado"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Estado do changeset: %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d comentário"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "Bookmark %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "Tag %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Ramo %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "%s Changeset"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "Estado do changeset"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "Diff cru"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr "D"
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr "Baixar diff"
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "mesclar"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 #, fuzzy
 msgid "Grafted from:"
 msgstr "Criado em"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "criado"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "criado"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] "%s arquivo modificado"
 msgstr[1] "%s arquivos modificados"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "%s arquivo modificado com %s inserções e %s exclusões"
 msgstr[1] "%s arquivos modificados com %s inserções e %s exclusões"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 #, fuzzy
 msgid "Show full diff anyway"
 msgstr "Mostrar diff completo"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-#, fuzzy
-msgid "No revisions"
-msgstr "revisões"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "Comment"
+msgid "comment"
+msgstr "Comentário"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -5124,187 +4574,189 @@
 msgid "Delete comment?"
 msgstr "%d comentário"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Mudanças mais recentes"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "Comentando a linha {1}."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr "Comentários interpretados usando a sintaxe %s com suporte a %s."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-#, fuzzy
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
 msgstr ""
 "Use @nomedeusuário dentro desse texto para enviar notificação a este "
 "usuário do Kallithea"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Visualizar comentário"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-#, fuzzy
-msgid "Submitting ..."
-msgstr "Enviando..."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "Altere o estado do changeset"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr "Vote para estado do pull request"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "Sem modificações"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Comentar no pull request #%s"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(fechado)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Comentário"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Visualizar"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "Você precisa estar logado para comentar."
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "Entrar agora"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "Ocultar"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d comentário"
 msgstr[1] "%d comentários"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, fuzzy, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "(%d em linha)"
 msgstr[1] "(%d em linha)"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 msgstr[1] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-#, fuzzy
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-"Use @nomedeusuário dentro desse texto para enviar notificação a este "
-"usuário do Kallithea"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr "Vote para estado do pull request"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "Altere o estado do changeset"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "Sem modificações"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(fechado)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "%s Changesets"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "Estado do changeset: %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "Arquivos afetados"
 
-#: kallithea/templates/changeset/diff_block.html:21
-#: kallithea/templates/files/diff_2way.html:43
-msgid "Show full diff for this file"
-msgstr "Mostrar diff completo para este arquivo"
-
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
-msgid "Show full side-by-side diff for this file"
-msgstr "Mostrar diff completo lado-a-lado para este arquivo"
-
-#: kallithea/templates/changeset/diff_block.html:38
-msgid "Show inline comments"
-msgstr "Mostrar comentários inline"
-
-#: kallithea/templates/changeset/diff_block.html:86
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "arquivo removido"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Última alteração"
+
+#: kallithea/templates/changeset/diff_block.html:42
 #, fuzzy
 msgid "Deleted"
 msgstr "excluir"
 
-#: kallithea/templates/changeset/diff_block.html:89
+#: kallithea/templates/changeset/diff_block.html:44
 #, fuzzy
 msgid "Renamed"
 msgstr "renomear"
 
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Revisão desconhecida %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Nenhum nome de arquivo"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "arquivo adicionado"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr "Mostrar diff completo para este arquivo"
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr "Mostrar diff completo lado-a-lado para este arquivo"
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr "Mostrar comentários inline"
+
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "Nenhum changeset"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "Antecessor"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+#, fuzzy
+#| msgid "Common ancestor"
+msgid "Merge Ancestor"
+msgstr "Comentário no changeset"
+
+#: kallithea/templates/compare/compare_cs.html:40
 #, fuzzy
 msgid "Show merge diff"
 msgstr "Mostrar diff completo"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-#, fuzzy
-msgid "Common ancestor"
-msgstr "Comentário no changeset"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 #, fuzzy
 msgid "is"
 msgstr "Gist"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, fuzzy, python-format
 msgid "%s changesets"
 msgstr "%s Changesets"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 #, fuzzy
 msgid "behind"
 msgstr "Reindexar"
@@ -5316,131 +4768,186 @@
 msgstr "%s Comparar"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] "Mostrando %s commit"
 msgstr[1] "Mostrando %s commits"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "Mostrar diff completo"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Repositório público"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "Nenhum conjunto de alterações ainda."
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Assinar o feed rss de %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Assinar o feed atom de %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s comentou no changeset em %(when)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
 #, fuzzy, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "O estado do changeset foi alterado para"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Comment on Changeset \"%s\""
+msgstr "%(user)s comentou no changeset em %(when)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset flow"
+msgid "Changeset on"
+msgstr "Fluxo de changesets"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Ramo"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Mudanças mais recentes"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "Repositório não está travado"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Olá %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 #, fuzzy
 msgid "We have received a request to reset the password for your account."
 msgstr "Recebemos uma requisição para criar uma nova senha para sua conta."
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
+#: kallithea/templates/email_templates/pull_request.html:4
 #, fuzzy, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, fuzzy, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#| msgid "[commented] on pull request for"
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr "[comentado] no pull request para"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+msgid "Pull request"
+msgstr "Pull request #%s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, fuzzy, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-#, fuzzy
-msgid "The comment closed the pull request with status"
-msgstr "%s comentou no pull request \"%s\""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-#, fuzzy
-msgid "The comment was made with status"
-msgstr "O pull request foi fechado com o estado"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "Veja este usuário aqui"
+#| msgid "%(user)s commented on pull request %(age)s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%(user)s comentou no pull request em %(when)s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Pull requests de %s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "[comentado] no pull request para"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "Nome do grupo"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5458,7 +4965,7 @@
 msgstr "%s Diff de Arquivo"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "%s Arquivos"
@@ -5468,74 +4975,76 @@
 msgid "%s Files Add"
 msgstr "%s Adicionar Arquivos"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Local"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "ou"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr ""
+#, fuzzy
+msgid "New file type"
+msgstr "novo arquivo"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "Realizar commit das alterações"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "Revisão anterior"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "Próxima revisão"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "Seguir o ramo atual"
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "Carregando lista de arquivos..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Tamanho"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "Última revisão"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 #, fuzzy
 msgid "Last Modified"
 msgstr "Última alteração"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 #, fuzzy
 msgid "Last Committer"
 msgstr "Último commiter"
@@ -5546,7 +5055,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5559,24 +5068,20 @@
 msgid "Edit file"
 msgstr "Editar arquivo"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "Fonte"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "Editando arquivo"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5584,53 +5089,65 @@
 msgstr[0] "%s autor"
 msgstr[1] "%s autores"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:41
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "Excluído ramo: %s"
+
+#: kallithea/templates/files/files_source.html:37
 msgid "Editing binary files not allowed"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:40
 msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 "A edição de arquivos só é permitida quando se está na revisão mais "
 "recente do ramo"
 
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/files/files_source.html:41
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "Arquivo binário (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "Arquivo é grande demais para exibir"
+
+#: kallithea/templates/files/files_source.html:71
+#, fuzzy
+msgid "Show full annotation anyway."
+msgstr "Mostrar diff completo"
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Arquivo é grande demais para exibir"
+#, fuzzy
+msgid "Show as raw."
+msgstr "Mostrar original"
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5650,12 +5167,12 @@
 msgstr "%s Seguidores"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "Seguidores"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "Começou a seguir -"
 
@@ -5664,36 +5181,36 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "Nome da bifurcação"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 #, fuzzy
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr "Revisão padrão para página de arquivos, downloads, whoosh e readme"
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Privado"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Copiar permissões"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "Copiar permissões do repositório bifurcado"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "Atualizar após clonar"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "Checkout fontes depois de criar o clone"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5703,40 +5220,40 @@
 msgstr "%s Bifurcações"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "Bifurcações"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "Bifurcado"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "Ainda não há bifurcações"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "ATOM feed do diário"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "RSS feed do diário"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "Ainda não há entradas"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "ATOM feed do diário público"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "RSS feed do diário público"
 
@@ -5746,31 +5263,36 @@
 msgid "New Pull Request"
 msgstr "Novo pull request"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Título"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "Escreva uma breve descrição para este pull request"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "Fluxo de changesets"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "Repositório origem"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Revisão"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "Repositório de destino"
 
@@ -5784,6 +5306,10 @@
 msgid "Vote"
 msgstr "revogar"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Idade"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5807,7 +5333,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "Fechado"
 
@@ -5835,114 +5361,106 @@
 msgid "Pull request %s from %s#%s"
 msgstr "Pull requests de %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 #, fuzzy
 msgid "Summarize the changes"
 msgstr "Realizar commit das alterações"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr "Estado do pull request calculado a partir dos votos"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "Ainda não revisado por"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d revisor"
-msgstr[1] "%d revisores"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "O pull request foi revisado por todos os revisores"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "Ainda não há ramos"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 #, fuzzy
 msgid "on"
 msgstr "nenhum"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "Puxar mudanças"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-#, fuzzy
-msgid "Update"
-msgstr "usuário [atualizado]"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "Registro"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-#, fuzzy
-msgid "Pull Request Reviewers"
-msgstr "Revisores do pull request"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+#, fuzzy
+msgid "Cancel Changes"
+msgstr "Mudanças mais recentes"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "reviewer"
+msgid "Reviewers"
+msgstr "revisor"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 #, fuzzy
 msgid "Remove reviewer"
 msgstr "revisor"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 #, fuzzy
 msgid "Potential Reviewers"
 msgstr "Visualizar comentário"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-#, fuzzy
-msgid "Save as New Pull Request"
-msgstr "Crie novo pull request"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-#, fuzzy
-msgid "Cancel Changes"
-msgstr "Mudanças mais recentes"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 #, fuzzy
 msgid "Pull Request Content"
 msgstr "O pull request mudou de estado"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+#, fuzzy
+msgid "Common ancestor"
+msgstr "Comentário no changeset"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5950,7 +5468,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "Pull requests de %s"
 
@@ -5959,38 +5476,44 @@
 msgid "Pull Requests to '%s'"
 msgstr "Pull requests para %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 #, fuzzy
 msgid "Open New Pull Request"
 msgstr "Crie novo pull request"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, fuzzy, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Pull requests para %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, fuzzy, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Pull requests de %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 #, fuzzy
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr "Mostrar pull requests fechados também"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 #, fuzzy
 msgid "Pull Requests Created by Me"
 msgstr "Revisores do pull request"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "Revisores do pull request"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 #, fuzzy
 msgid "Pull Requests I Participate In"
 msgstr "Eu participo em"
@@ -6006,29 +5529,29 @@
 msgid "Search in All Repositories"
 msgstr "Buscar em todos os repositórios"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "Termo de pesquisa"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "Pesquisando em"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "Conteúdo dos arquivos"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "Mensagens de commit"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "Nomes dos arquivos"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "Permissão negada"
 
@@ -6038,80 +5561,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr "%s ATOM feed"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "%s RSS feed"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "Habilitar"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "Estatísticas coletadas:"
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "arquivos"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Mostrar mais"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "commits"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "arquivos adicionados"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "arquivos alterados"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "arquivos removidos"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "arquivo adicionado"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "arquivo alterado"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "arquivo removido"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "%s Sumário"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "Repositório travado por %s"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "Repositório destravado"
 
@@ -6119,93 +5642,276 @@
 msgid "Fork of"
 msgstr "Bifurcação de"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Clonar de"
 
-#: kallithea/templates/summary/summary.html:72
-#, fuzzy
-msgid "Clone URL"
-msgstr "URL de clonagem"
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr "Mostrar por ID"
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr "Mostrar por Nome"
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr "Mostrar por ID"
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "Tendências em arquivos"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "Download"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "Ainda não há downloads"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "Downloads estão desabilitados para este repositório"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "Download como zip"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "Marque isto para descarregar arquivo com subrepositórios"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 #, fuzzy
 msgid "With subrepos"
 msgstr "com subrepositórios"
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "Tamanho do Repositório"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "Feed"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "Mudanças mais recentes"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 #, fuzzy
 msgid "Quick Start"
 msgstr "Início rápido"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr "Adicionar ou enviar arquivos diretamente pelo Kallithea"
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "Fazer push de novo repositório"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "Repositório existente?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "Descarregar %s como %s"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s Tags"
-
-#: kallithea/templates/tags/tags.html:26
-#, fuzzy
-msgid "Compare Tags"
-msgstr "Comparar tags"
-
-#~ msgid "increase diff context to %(num)s lines"
+#~ msgid "There is no index to search in. Please run whoosh indexer"
 #~ msgstr ""
+#~ "Não há índice onde pesquisa. Por favor execute o indexador whoosh"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP %s não permitido"
+
+#, fuzzy
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s comentou no changeset em %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s enviou mensagem em %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s mencionou-o em %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s registrou-se no Kallithea em %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s abriu um novo pull request em %(when)s"
+
+#, fuzzy
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s comentou no pull request em %(when)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s comentou no changeset em %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s enviou mensagem em %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s mencionou-o em %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s registrou-se no Kallithea em %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s abriu um novo pull request em %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s comentou no pull request em %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "Grupo de repositórios"
+
+#~ msgid "My Notifications"
+#~ msgstr "Minhas Notificações"
+
+#~ msgid "All"
+#~ msgstr "Todos"
+
+#~ msgid "Comments"
+#~ msgstr "Comentários"
+
+#, fuzzy
+#~ msgid "Mark All Read"
+#~ msgstr "Marcar tudo como lido"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "Marcar tudo como lido"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Ainda não há notificações aqui"
+
+#, fuzzy
+#~ msgid "Show Notification"
+#~ msgstr "Mostrar notificação"
+
+#~ msgid "Notifications"
+#~ msgstr "Notificações"
+
+#~ msgid "Home"
+#~ msgstr "Início"
+
+#~ msgid "with"
+#~ msgstr "com"
+
+#~ msgid "members"
+#~ msgstr "membros"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "O changeset tem comentários"
+
+#~ msgid "Author"
+#~ msgstr "Autor"
+
+#~ msgid "Refs"
+#~ msgstr "Refs"
+
+#, fuzzy
+#~ msgid "Commenting on line."
+#~ msgstr "Comentando a linha {1}."
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "Comentar no pull request #%s"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "Data"
+
+#~ msgid "Previous revision"
+#~ msgstr "Revisão anterior"
+
+#~ msgid "Next revision"
+#~ msgstr "Próxima revisão"
+
+#~ msgid "Follow current branch"
+#~ msgstr "Seguir o ramo atual"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "Ainda não revisado por"
+
+#~ msgid "%d reviewer"
+#~ msgid_plural "%d reviewers"
+#~ msgstr[0] "%d revisor"
+#~ msgstr[1] "%d revisores"
+
+#~ msgid "Pull request was reviewed by all reviewers"
+#~ msgstr "O pull request foi revisado por todos os revisores"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "Ainda não há ramos"
+
+#, fuzzy
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Revisores do pull request"
+
+#~ msgid "Dashboard"
+#~ msgstr "Painel de Controle"
+
+#~ msgid "Remember me"
+#~ msgstr "Lembre-se de mim"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Altere o seu avatar em"
+
+#~ msgid "Using"
+#~ msgstr "Usando"
+
+#~ msgid "Rescan option"
+#~ msgstr "Opção de varredura"
+
+#~ msgid "Web"
+#~ msgstr "Web"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "Requer SSL para operações de VCS"
+
+#~ msgid "Dashboard items"
+#~ msgstr "Itens do dashboard"
+
+#~ msgid "quick filter..."
+#~ msgstr "filtro rápido..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "Esqueceu a senha ?"
+
+#~ msgid "Ancestor"
+#~ msgstr "Antecessor"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "O estado do changeset foi alterado para"
+
+#, fuzzy
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "%s comentou no pull request \"%s\""
+
+#, fuzzy
+#~ msgid "The comment was made with status"
+#~ msgstr "O pull request foi fechado com o estado"
+
+#~ msgid "View this user here"
+#~ msgstr "Veja este usuário aqui"
+
+#~ msgid "Repository Size"
+#~ msgstr "Tamanho do Repositório"
 
 #~ msgid "No comments."
 #~ msgstr "%d comentário"
@@ -6216,18 +5922,6 @@
 #~ msgid "journal"
 #~ msgstr "diário"
 
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
 #~ msgid "Unlocked"
 #~ msgstr "Destravado"
 
@@ -6243,9 +5937,6 @@
 #~ msgid "No Files"
 #~ msgstr "Nenhum Arquivo"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "O username \\\"%(username)s\\\" é proibido"
 
@@ -6258,66 +5949,29 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "URL de clonagem inválida"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr "URL inválida, por favor, forneça uma URL de clone http(s)/svn+http(s)"
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
+#~ msgid ""
+#~ "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
 #~ msgstr ""
+#~ "URL inválida, por favor, forneça uma URL de clone http(s)/svn+http(s)"
 
 #~ msgid "Defaults"
 #~ msgstr "Padrões"
 
-#~ msgid "My Emails"
-#~ msgstr ""
-
 #~ msgid "Watched"
 #~ msgstr "Seguindo"
 
 #~ msgid "My Permissions"
 #~ msgstr "Minhas permissões"
 
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
 #~ msgid "delete"
 #~ msgstr "excluir"
 
-#~ msgid "current IP"
-#~ msgstr ""
-
 #~ msgid "Permissions Administration"
 #~ msgstr "Administração de permissões"
 
-#~ msgid "Overview"
-#~ msgstr ""
-
 #~ msgid "Overwrite existing settings"
 #~ msgstr "Sobrescrever as configurações existentes"
 
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
 #~ msgid "Default User Permissions Overview"
 #~ msgstr "Permissões padrão"
 
@@ -6333,15 +5987,6 @@
 #~ msgid "admin"
 #~ msgstr "administrador"
 
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
 #~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr "URL opcional http[s] da qual o repositório deve ser clonado."
 
@@ -6351,81 +5996,18 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Realizar pull de alterações a partir de localização remota"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
 #~ msgid "Non-changeable id"
 #~ msgstr "ID inalterável"
 
 #~ msgid "edit"
 #~ msgstr "editar"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
 #~ msgid "Destroy old data"
 #~ msgstr "Destruir dados antigos"
 
-#~ msgid "check for updates"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "Permissões padrão"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "Alteração de estado no changeset"
 
@@ -6435,30 +6017,15 @@
 #~ msgid "Comment on changeset"
 #~ msgstr "Comentário no changeset"
 
-#~ msgid "revision"
-#~ msgstr ""
-
 #~ msgid "Mimetype"
 #~ msgstr "Mimetype"
 
 #~ msgid "My Repos"
 #~ msgstr "Meus repositórios"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "dono"
 
-#~ msgid "reviewer"
-#~ msgstr "revisor"
-
 #~ msgid "Your new Kallithea password:%s"
 #~ msgstr "Sua nova senha no Kallithea: %s"
 
@@ -6472,11 +6039,159 @@
 #~ msgstr "Você pode gerá-la clicando na seguinte URL"
 
 #~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr "Por favor, ignore este email se você não requisitou uma nova senha."
+#~ msgstr ""
+#~ "Por favor, ignore este email se você não requisitou uma nova senha."
 
 #~ msgid "Created by"
 #~ msgstr "criado"
 
-#~ msgid "You can only delete files with revision being a valid branch "
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "Confirma invalidar cache do repositório"
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr "Comentários interpretados usando a sintaxe %s com suporte a %s."
+
+#~ msgid "Comment preview"
+#~ msgstr "Visualizar comentário"
+
+#~ msgid "Preview"
+#~ msgstr "Visualizar"
+
+#~ msgid "Closing."
+#~ msgstr "carregando ..."
+
+#~ msgid "Repository no access"
+#~ msgstr "Nenhum acesso ao repositório"
+
+#~ msgid "Repository read access"
+#~ msgstr "Acesso de leitura ao repositório"
+
+#~ msgid "Repository write access"
+#~ msgstr "Acesso de escrita ao repositório"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Acesso administrativo ao repositório"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Nenhum acesso ao Grupo de Repositórios"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Acesso de leitura ao Grupo de Repositórios"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Acesso de escrita ao Grupo de Repositórios"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Acesso administrativo ao Grupo de Repositórios"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Criação de repositórios desabilitada"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Criação de repositórios habilitada"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Bifurcação de repositórios desabilitada"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Bifurcação de repositórios habilitada"
+
+#~ msgid "Register disabled"
+#~ msgstr "Registro desabilitado"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Registro de novo usuário no Kallithea com ativação manual"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr "Registro de novo usuário no Kallithea com auto-ativação"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "Não Revisado"
+
+#~ msgid "Rejected"
+#~ msgstr "Rejeitado"
+
+#~ msgid "Under Review"
+#~ msgstr "Sob Revisão"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Sem acesso ao grupo de repositórios"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Acesso de leitura ao grupo de repositórios"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Acesso de escrita ao grupo de repositórios"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Acesso administrativo ao grupo de repositórios"
+
+#~ msgid "User group no access"
+#~ msgstr "Sem acesso ao grupo de usuários"
+
+#~ msgid "User group read access"
+#~ msgstr "Acesso de leitura ao grupo de usuários"
+
+#~ msgid "User group write access"
+#~ msgstr "Acesso de escrita ao grupo de usuários"
+
+#~ msgid "User group admin access"
+#~ msgstr "Acesso administrativo ao grupo de usuários"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Criação de Grupo de Repositórios desatilibada"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Criação de Grupo de Repositórios habilitada"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Criação de Grupo de Usuários desabilitada"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Criação de Grupo de Usuários habilitada"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "Registro de Usuário com ativação manual de conta"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "Registro de Usuário com ativação automática de conta"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
 #~ msgstr ""
-
+#~ "%(user)s solicita sua revisão no pull request $%(pr_id)s: %(pr_title)s"
+
+#~ msgid "repositories"
+#~ msgstr "repositórios"
+
+#~ msgid "No repositories found."
+#~ msgstr "Nenhum repositório encontrado."
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Ainda não há ramos"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Ainda não há etiquetas"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Ainda não há marcadores"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "%s Bookmarks"
+
+#~ msgid "%s Branches"
+#~ msgstr "%s Ramos"
+
+#~ msgid "Editing file"
+#~ msgstr "Editando arquivo"
+
+#~ msgid "Update"
+#~ msgstr "usuário [atualizado]"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Crie novo pull request"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s Tags"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Comparar tags"
--- a/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/ru/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -12,471 +12,493 @@
 # SkryabinD <skryabind@gmail.com>, 2014
 # softforwinxp <softforwinxp@gmail.com>, 2013
 # zhmylove <zhmylove@narod.ru>, 2013
-# Andrew Shadura <andrew@shadura.me>, 2015
+# Andrej Shadura <andrew@shadura.me>, 2015, 2016.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2017-01-05 14:58+0000\n"
-"Last-Translator: Andrew Shadura <andrew@shadura.me>\n"
-"Language-Team: Russian "
-"<https://hosted.weblate.org/projects/kallithea/stable/ru/>\n"
+"Last-Translator: Andrej 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-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
-"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
 "X-Generator: Weblate 2.11-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Ещё не было изменений"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Ничего"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(закрыто)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Отображать пробелы"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Игнорировать пробелы"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "Увеличить контекст до %(num)s строк"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "No permissions defined yet"
+msgid "No permission to change status"
+msgstr "Привилегии еще не назначены"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Pull-запрос успешно удалён"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Нет такой ревизии в этом репозитории"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-"Нельзя редактировать статус изменений, связанных с закрытыми pull-"
-"request'ами"
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr "Выбрать набор изменений"
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Перейти на верхушку репозитория"
+
+#: kallithea/controllers/compare.py:72
+#, fuzzy
+#| msgid "Cannot compare repositories without using common ancestor"
+msgid "Cannot compare repositories of different types"
+msgstr "Невозможно сравнивать репозитории без общего предка"
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr "Невозможно сравнивать репозитории без общего предка"
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "Нет ответа"
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr "Нет ответа"
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr "Неизвестная ошибка"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr "Запрос не распознан сервером из-за неправильного синтаксиса."
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "Несанкционированный доступ к ресурсу"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "У вас нет прав для просмотра этой страницы"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "Ресурс не найден"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
-msgstr "Сервер не может выполнить запрос из-за неправильного условия в запросе."
-
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "Изменения в репозитории %s"
-
-#: kallithea/controllers/feed.py:56
+msgstr ""
+"Сервер не может выполнить запрос из-за неправильного условия в запросе."
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr "%s выполнил коммит в %s"
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "Изменения оказались слишком большими и были вырезаны..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "Лента новостей %s %s"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "Изменения оказались слишком большими и были вырезаны..."
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr "%s выполнил коммит в %s"
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Изменения в репозитории %s"
+
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Нажмите чтобы добавить новый файл"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Нет файлов. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr "%s (%s)"
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Репозиторий заблокировал %s в %s"
 
-#: kallithea/controllers/files.py:317
-#, fuzzy
-#| msgid "You can only delete files with revision being a valid branch "
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
-msgstr "Вы можете удалять файлы только в ревизии, связанной с существующей веткой "
-
-#: kallithea/controllers/files.py:328
+msgstr ""
+"Вы можете удалять файлы только в ревизии, связанной с существующей веткой "
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Файл %s удалён с помощью Kallithea"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Файл %s удалён"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Во время коммита произошла ошибка"
 
-#: kallithea/controllers/files.py:377
-#, fuzzy
-#| msgid "You can only edit files with revision being a valid branch "
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 "Вы можете редактировать файлы только в ревизии, связанной с существующей "
 "веткой "
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr "Файл %s отредактирован с помощью Kallithea"
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Без изменений"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Изменения применены в %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Файл добавлен с помощью Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Пусто"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "Безымянный"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
-"Расположение должно быть относительным путем, и не должно содержать "
-"\"..\" в пути"
-
-#: kallithea/controllers/files.py:526
+"Расположение должно быть относительным путем, и не должно содержать \".."
+"\" в пути"
+
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Возможность скачивать отключена"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Неизвестная ревизия %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Пустой репозиторий"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "Неизвестный тип архива"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Набор изменений"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Ветки"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Метки"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "Произошла ошибка во время создания форка репозитория %s"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Группы"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Репозитории"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Ветка"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "Закрытые ветки"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr "Тэги"
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Закладки"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "Публичный журнал"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Журнал"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr "Неверная капча"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr "Регистрация в Kallithea прошла успешно"
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "Регистрация в %s прошла успешно"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr "Код для сброса пароля отправлен"
-
-#: kallithea/controllers/login.py:246
+msgstr "Код для сброса пароля отправлена"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr "Неверный код сброса пароля"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Пароль обновлён"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (закрыта)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "Изменения"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr "Специальный"
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr "Ветки участника"
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Закладки"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr "Ошибка при создании pull-запроса: %s"
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "Нет описания"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr "Pull-запрос создан успешно"
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr "Произошла ошибка при создании pull-запроса"
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr "Отсутствующие ревизии относительно предыдущего pull-запроса:"
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr "Новые ревизии на %s %s относительно предыдущего pull-запроса:"
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr "Этот pull-запрос основан на другой ревизии %s, простой diff невозможен."
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr "Нет изменений на %s %s относительно предыдущей версии."
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr "Закрыт, замещён %s ."
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr "Pull-запрос создан успешно"
+
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
 msgstr "Обновление для pull-запроса создано"
 
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "Нет описания"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr "Pull-запрос обновлён"
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "Pull-запрос успешно удалён"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, fuzzy, python-format
+#| msgid "Changeset for %s %s not found in %s"
+msgid "Revision %s not found in %s"
+msgstr "Набор изменений не найден"
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "Нет изменений для обновления этого pull-запроса."
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr "Этот pull-запрос уже принят на ветку %s."
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr "Этот pull-запрос был закрыт и не может быть обновлён."
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr "Этот pull-запрос может быть обновлён из %s:"
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
 msgstr "Нет изменений для обновления этого pull-запроса."
 
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr "Внимание: Ветка %s имеет ещё одну верхушку: %s."
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
+#: kallithea/controllers/pullrequests.py:564
+#, fuzzy
+#| msgid "Git pull requests don't support updates yet."
+msgid "Git pull requests don't support iterating yet."
 msgstr "Обновление pull-запросы git не поддерживается."
 
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "Закрыт."
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "Нет изменений для обновления этого pull-запроса."
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "Недопустимый поисковый запрос. Попробуйте заключить его в кавычки."
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "Индексы отсутствуют. Пожалуйста, запустите индексатор Whoosh"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Произошла ошибка при выполнении этого поиска."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr "Нет данных"
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "Статистические данные отключены для этого репозитария"
 
@@ -488,152 +510,154 @@
 msgid "error occurred during update of auth settings"
 msgstr "произошла ошибка при обновлении настроек авторизации"
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "Стандартные настройки успешно обновлены"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr "Произошла ошибка при обновлении стандартных настроек"
 
-#: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
 #, fuzzy
 msgid "Forever"
 msgstr "навсегда"
 
-#: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
 msgid "5 minutes"
 msgstr "5 минут"
 
+#: kallithea/controllers/admin/gists.py:60
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 час"
+
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "1 час"
-
-#: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
 msgid "1 day"
 msgstr "1 день"
 
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/gists.py:62
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 месяц"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr "Срок"
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Произошла ошибка во время создания gist-записи"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr "Gist-запись %s удалена"
 
-#: kallithea/controllers/admin/gists.py:233
+#: kallithea/controllers/admin/gists.py:196
 msgid "Unmodified"
-msgstr "Без изменений"
-
-#: kallithea/controllers/admin/gists.py:262
+msgstr "Неизменный"
+
+#: kallithea/controllers/admin/gists.py:225
 msgid "Successfully updated gist content"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:267
+#: kallithea/controllers/admin/gists.py:230
 msgid "Successfully updated gist data"
 msgstr "Данные gist-записи обновлены"
 
-#: kallithea/controllers/admin/gists.py:270
+#: kallithea/controllers/admin/gists.py:233
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Произошла ошибка при обновлении gist-записи %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 "Вы не можете изменить данные этого пользователя, поскольку он важен для "
 "работы всего приложения"
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "Ваша учетная запись успешно обновлена"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr "Произошла ошибка при обновлении пользователя %s"
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Ошибка при обновлении пароля"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "Пользователю добавлен e-mail %s"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Произошла ошибка при сохранении e-mail"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "E-mail пользователя удалён"
 
-#: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
 msgid "API key successfully created"
 msgstr "API-ключ успешно создан"
 
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
 msgid "API key successfully reset"
 msgstr "API-ключ успешно сброшен"
 
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/my_account.py:259
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr "API-ключ успешно удалён"
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "Чтение"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "Запись"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -641,277 +665,269 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "Администратор"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "Отключено"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr "Разрешена, с ручной активацией учётной записи"
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr "Разрешена, с автоматической активацией учётной записи"
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr "Ручная активация внешней учетной записи"
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr "Автоматическая активация внешней учетной записи"
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "Отключено"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "Разрешена, с ручной активацией учётной записи"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "Разрешена, с автоматической активацией учётной записи"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "Ручная активация внешней учетной записи"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "Автоматическая активация внешней учетной записи"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "Включено"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr "Глобальные привилегии успешно обновлены"
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr "Произошла ошибка во время обновления привилегий"
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr "Произошла ошибка при создании группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr "Создана новая группа репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr "Группа репозиториев %s обновлена"
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr "Произошла ошибка при обновлении группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "Данная группа содержит %s репозитариев и не может быть удалена"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr "Группа содержит в себе %s подгрупп и не может быть удалён"
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr "Группа репозиториев %s удалена"
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr "Произошла ошибка при удалении группы репозиториев %s"
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr "Администратор не может отозвать свои привелегии"
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr "Привилегии группы репозиториев обновлены"
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr "Произошла ошибка при отзыве привелегии"
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr "Произошла ошибка при создании репозитория %s"
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr "Репозиторий %s создан из %s"
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr "Сделан форк(копия) репозитория %s на %s"
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr "Репозиторий %s создан"
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Репозитарий %s успешно обновлён"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr "Произошла ошибка во время обновления репозитория %s"
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr "Форки %s отсоединены"
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr "Удалены форки репозитория %s"
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr "Репозиторий %s удалён"
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "Невозможно удалить %s, у него всё ещё есть форки"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Произошла ошибка во время удаления %s"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr "Привилегии репозитория обновлены"
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during creation of field"
+msgid "An error occurred during creation of field: %r"
 msgstr "Произошла ошибка при создании поля"
 
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr "Произошла ошибка при удалении поля"
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr "-- Не форк --"
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "Видимость репозитория в публичном журнале обновлена"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr "Произошла ошибка при установке репозитария в общедоступный журнал"
 
-#: kallithea/controllers/admin/repos.py:512
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "Ничего"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "Репозиторий %s отмечен как форк %s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "Произошла ошибка при выполнении операции"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 msgid "Repository has been locked"
 msgstr "Репозиторий заблокирован"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "Репозиторий разблокирован"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "Произошла ошибка во время разблокирования"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr "Кэш сброшен"
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "Произошла ошибка при очистке кэша"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "Внесены изменения из удалённого репозитория"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "Произошла ошибка при внесении изменений из удалённого репозитория"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "Произошла ошибка при удалении статистики репозитория"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "Обновлены настройки VCS"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
@@ -919,351 +935,356 @@
 "Невозможно включить поддержку hgsubversion. Библиотека «hgsubversion» "
 "отсутствует"
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr "Произошла ошибка при обновлении настроек приложения"
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr "Репозитории успешно пересканированы, добавлено: %s, удалено: %s."
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
-#| msgid "Invalidate cache for all repositories"
 msgid "Invalidated %s repositories"
 msgstr "Сбросить кэш для всех репозиториев"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "Обновленные параметры настройки приложения"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "Настройки визуализации обновлены"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr "Произошла ошибка при обновлении настроек визуализации"
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr "Пожалуйста, введите адрес электронной почты"
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr "Задача отправки Email создана"
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "Нет данных"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "Добавлена новая ловушка"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "Обновлённые ловушки"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr "произошла ошибка при создании хука"
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Запланирована переиндексация базы Whoosh"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr "Создана группа пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr "Произошла ошибка при создании группы пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr "Группа пользователей %s обновлена"
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr "Произошла ошибка при обновлении группы пользователей %s"
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr "Группа пользователей успешно удалена"
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr "Произошла ошибка при удалении группы пользователей"
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr "Целевая группа не может быть такой же"
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr "Привилегии группы пользователей обновлены"
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr "Обновлены привилегии"
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "Произошла ошибка при сохранении привилегий"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr "Пользователь %s создан"
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr "Произошла ошибка при создании пользователя %s"
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "Пользователь успешно обновлён"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr "Пользователь успешно удалён"
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "Произошла ошибка при удалении пользователя"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr "Добавлен IP %s в белый список пользователя"
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr "Произошла ошибка при сохранении IP"
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr "Удален IP %s из белого списка пользователя"
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr "IP %s заблокирован"
-
-#: kallithea/lib/auth.py:757
-msgid "Invalid API key"
-msgstr ""
-
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
+#: kallithea/lib/auth.py:824
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 "Вы должны быть зарегистрированным пользователем, чтобы выполнить это "
 "действие"
 
-#: kallithea/lib/auth.py:864
+#: kallithea/lib/auth.py:852
 msgid "You need to be signed in to view this page"
 msgstr "Страница доступна только авторизованным пользователям"
 
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr "Репозиторий не найден на файловой системе"
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
-#| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "Набор изменений не найден"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr "Двоичный файл"
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
 msgstr ""
 "Набор изменения оказался слишком большими и был урезан, используйте меню "
 "сравнения для показа результата сравнения"
 
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "Изменений не обнаружено"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "Удалена ветка: %s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "Создан тег: %s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
-#| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "Набор изменений не найден"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Показать отличия вместе %s->%s"
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 #, fuzzy
 msgid "Compare view"
 msgstr "сравнение"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "и"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "на %s больше"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "версии"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "имя форка %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr "Pull-запрос %s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[удален] репозиторий"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[создан] репозиторий"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[создан] репозиторий как форк"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[форкнут] репозиторий"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[обновлён] репозиторий"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr "[загружен] архив из репозитория"
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[удален] репозиторий"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "[создан] пользователь"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "[обновлён] пользователь"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr "[создана] группа пользователей"
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr "[обновлена] группа пользователей"
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[комментарий] к ревизии в репозитории"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "[прокомментировано] в запросе на внесение изменений для"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "[закрыт] Pull-запрос для"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[отправлено] в"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[внесены изменения с помощью Kallithea] в репозитории"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[внесены изменения из удалённого репозитория] в репозиторий"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[внесены изменения] из"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[добавлен в наблюдения] репозиторий"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[удалён из наблюдения] репозиторий"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " и на %s больше"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "Нет файлов"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr "новый файл"
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr "изменён"
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr "удалён"
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr "переименован"
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr "chmod"
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1274,7 +1295,7 @@
 "переименован из файловой системы. Пожалуйста, перезапустите приложение "
 "для сканирования репозиториев"
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1282,7 +1303,7 @@
 msgstr[1] "%d лет"
 msgstr[2] "%d года"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1290,7 +1311,7 @@
 msgstr[1] "%d месяца"
 msgstr[2] "%d месяцев"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1298,7 +1319,7 @@
 msgstr[1] "%d дня"
 msgstr[2] "%d дней"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1306,7 +1327,7 @@
 msgstr[1] "%d часов"
 msgstr[2] "%d часа"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1314,7 +1335,7 @@
 msgstr[1] "%d минут"
 msgstr[2] "%d минуты"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1322,722 +1343,330 @@
 msgstr[1] "%d секунды"
 msgstr[2] "%d секунды"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "в %s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s назад"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "в %s и %s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s и %s назад"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "прямо сейчас"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "Репозитарий - нет доступа"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "Репозитарий - доступ на чтение"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "Репозитарий - доступ на запись"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "Репозитарий - администрирование"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "Группа Репозиториев - нет доступа"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "Группа Репозиториев - доступ на чтение"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "Группа Репозиториев - доступ на запись"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "Группа Репозиториев - администрирование"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Администратор Kallithea"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "Создание репозиториев отключено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "Создание репозиториев включено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "Возможность создавать форк репозитория отключена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "Возможность создавать форк репозитория включена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "Регистрация отключена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "Регистрация нового пользователя в Kallithea с ручной активацией"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "Регистрация нового пользователя в Kallithea с автоматической активацией"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "Не просмотрено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "Одобрено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "Отклонено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "На рассмотрении"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr "верхний уровень"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr "Группа Репозиториев - нет доступа"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr "Группа репозиториев - доступ на чтение"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr "Группа репозиториев - доступ на запись"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr "Группа репозиториев - администрирование"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr "Группа пользователей - нет доступа"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr "Группа пользователей - доступ на чтение"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr "Группа пользователей - доступ на запись"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr "Группа пользователей - администрирование"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr "Создание групп репозиториев отключено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr "Создание групп репозиториев включено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr "Создание групп пользователей отключено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr "Создание групп пользователей включено"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr "Регистрация отключена"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr "Регистрация пользователя с ручной активацией учётной записи"
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr "Регистрация пользователя с автоматической активацией"
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "на строке %s"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[Упоминание]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr "верхний уровень"
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Администратор Kallithea"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr "Неавторизованные пользователи имеют право чтения новых репозиториев"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
-msgstr "Неавторизованные пользователи имеют право записи в новые репозитории"
-
-#: kallithea/model/db.py:1671
+msgstr ""
+"Неавторизованные пользователи имеют право записи в новые репозитории"
+
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr "Только администраторы могут создавать группы"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "Создана новая группа репозиториев %s"
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 #, fuzzy
 msgid "Only admins can create user groups"
 msgstr "Создавать группы пользователей"
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 #, fuzzy
 msgid "Non-admins can create user groups"
 msgstr "Создавать группы пользователей"
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Местонахождение репозиториев"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 #, fuzzy
-#| msgid "Non-admins can can fork repositories"
 msgid "Non-admins can fork repositories"
 msgstr "Сбросить кэш для всех репозиториев"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr "Регистрация отключена"
+
+#: kallithea/model/db.py:1736
 #, fuzzy
 msgid "User registration with manual account activation"
 msgstr "Регистрация пользователя с ручной активацией учётной записи"
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 #, fuzzy
 msgid "User registration with automatic account activation"
 msgstr "Регистрация пользователя с автоматической активацией"
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 #, fuzzy
 msgid "Not reviewed"
 msgstr "Не просмотрено"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 #, fuzzy
 msgid "Under review"
 msgstr "На рассмотрении"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "Одобрено"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "Одобрено"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "Пожалуйста, введите логин"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Введите значение длиной не менее %(min)i символов"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "Пожалуйста, введите пароль"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Введите не менее %(min)i символов"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr "%(user)s оставил комментарий к набору изменений %(age)s"
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr "%(user)s отправил сообщение %(age)s"
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr "%(user)s упомянул вас %(age)s"
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr "%(user)s зарегистрировался в Kallithea %(age)s"
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "%(user)s открыл новый pull-запрос %(age)s"
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "%(user)s оставил комментарий к pull-запросу %(age)s"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr "%(user)s оставил комментарий к набору изменений %(when)s"
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr "%(user)s отправил сообщение %(when)s"
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr "%(user)s упомянул вас %(when)s"
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr "%(user)s зарегистрировался в Kallithea %(when)s"
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr "%(user)s открыл новый pull-запрос %(when)s"
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr "%(user)s оставил комментарий к pull-запросу %(when)s"
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, fuzzy, python-format
+#| msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "[прокомментировано] в запросе на внесение изменений для"
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr "Новый пользователь \"%(new_username)s\" зарегистрирован"
 
-#: kallithea/model/notification.py:308
-#, fuzzy, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "[прокомментировано] в запросе на внесение изменений для"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "Закрыт"
 
-#: kallithea/model/pull_request.py:137
+#: kallithea/model/pull_request.py:76
 #, fuzzy, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr "%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s"
-
-#: kallithea/model/scm.py:812
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+"%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s"
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "Ошибка при создании pull-запроса: %s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "Подтвердите удаление этого pull-request'а"
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "Отсутствующие ревизии относительно предыдущего pull-запроса:"
+
+#: kallithea/model/pull_request.py:348
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "Новые ревизии на %s %s относительно предыдущего pull-запроса:"
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, fuzzy, python-format
+#| msgid ""
+#| "This pull request is based on another %s revision and there is no "
+#| "simple diff."
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+"Этот pull-запрос основан на другой ревизии %s, простой diff невозможен."
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "No changes found on %s %s since previous version."
+msgid "No changes found on %s %s since previous iteration."
+msgstr "Нет изменений на %s %s относительно предыдущей версии."
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "последняя версия"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr "Регистрация нового пользователя"
 
-#: kallithea/model/user.py:256
+#: kallithea/model/user.py:253
 #, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr ""
 "Вы не можете удалить пользователя, поскольку это критично для работы "
 "всего приложения"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
-"Пользователь \"%s\" всё ещё является владельцем %s репозиториев и поэтому"
-" не может быть удалён. Смените владельца или удалите эти репозитории: %s"
-
-#: kallithea/model/user.py:266
+"Пользователь \"%s\" всё ещё является владельцем %s репозиториев и поэтому "
+"не может быть удалён. Смените владельца или удалите эти репозитории: %s"
+
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
 msgstr ""
 "Пользователь \"%s\" всё ещё является владельцем %s групп репозиториев и "
 "поэтому не может быть удалён. Смените владельца или удалите данные "
 "группы: %s"
 
-#: kallithea/model/user.py:273
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
@@ -2047,65 +1676,65 @@
 "поэтому не может быть удалён. Смените владельца или удалите данные "
 "группы: %s"
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr "Ссылка сброса пароля"
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 #, fuzzy
 msgid "Password reset notification"
 msgstr "Ссылка сброса пароля"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "Значение не может быть пустым списком"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "Пользователь с именем \"%(username)s\" уже существует"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, fuzzy, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "Имя \"%(username)s\" недопустимо"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 #, fuzzy
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
 msgstr ""
 "Имя пользователя может содержать только буквы, цифры, символы "
 "подчеркивания, точки и тире; а так же должно начинаться с буквы, цифры "
 "либо с символа подчеркивания"
 
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "Имя \"%(username)s\" недопустимо"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr "Неверное имя группы пользователей"
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr "Группа пользователей \"%(usergroup)s\" уже существует"
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
@@ -2113,107 +1742,107 @@
 "имя группы пользователей может содержать только буквы, цифры, символы "
 "подчеркивания, точки и тире; а так же должно начинаться с буквы или цифры"
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "Невозможно использовать эту группу как родителя"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "Группа \"%(group_name)s\" уже существует"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "Репозитарий с  именем \"%(group_name)s\" уже существует"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "Недопустимые символы (не ascii) в пароле"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr "Неверно задан старый пароль"
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "Пароли не совпадают"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 #, fuzzy
 msgid "Invalid username or password"
 msgstr "неверный пароль"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "Несовпадение токенов"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, fuzzy, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "Имя репозитория %(repo)s запрещено"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "Репозитарий %(repo)s уже существует"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "Репозитарий \"%(repo)s\" уже существует в группе \"%(group)s\""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr "Группа репозиториев \"%(repo)s\" уже существует"
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "приватный репозиторий"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "Тип форка будет совпадать с родительским"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "У вас недостаточно прав для создания репозиториев в этой группе"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr "недостаточно прав для создания репозитория в корневом каталоге"
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr "У Вас недостаточно привилегий для создания группы в этом месте"
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr "Данное имя пользователя или группы пользователей недопустимо"
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "Этот путь ошибочен"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "Этот E-mail уже занят"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "\"%(email)s\" не существует."
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
@@ -2221,252 +1850,157 @@
 "Для входа по LDAP должно быть указано значение аттрибута CN - это "
 "эквивалент имени пользователя"
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr "Пожалуйста, введите существующий IPv4 или IPv6 адре"
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
 msgstr ""
 "Значение маски подсети должно быть в пределах от 0 до 32 (%(bits)r - "
 "неверно)"
 
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 "Ключевое имя может только состоять из букв, символа подчеркивания, тире "
 "или чисел"
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr "Файла нет в каталоге"
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr "О программе"
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "Панель управления"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "фильтр..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "репозитории"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr "Добавить репозиторий"
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
+#: kallithea/templates/index_base.html:32
 msgid "Add Repository Group"
 msgstr "Добавить группу репозиториев"
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
-msgstr "Вы имеете администраторские права на эту группу и можете редактировать её"
-
-#: kallithea/templates/index_base.html:32
+msgstr ""
+"Вы имеете администраторские права на эту группу и можете редактировать её"
+
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr "Изменить группу репозиториев"
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr "Имя группы"
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "Репозиторий"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "Описание"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "Имя"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "Последнее изменение"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Состояние"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "Владелец"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "По возрастанию"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "По убыванию"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr "Репозитарии не найдены."
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "Ошибка данных."
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "Загрузка..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "Войти"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr "Войти в %s"
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "Имя пользователя"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "Пароль"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "Запомнить"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "Забыли пароль?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "Нет аккаунта?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "Войти"
 
@@ -2474,149 +2008,133 @@
 msgid "Password Reset"
 msgstr "Сброс пароля"
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr "Сброс пароля для %s"
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr "Сброс пароля"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr "Почтовый адрес"
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr "Капча"
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 msgid "Send Password Reset Email"
 msgstr "Послать ссылку сброса пароля"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr "Ссылка для сброса пароля была отправлена на соответствующий e-mail."
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
 msgid "New Password"
 msgstr "Новый пароль"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
 msgid "Confirm New Password"
 msgstr "Подтвердите новый пароль"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "Регистрация"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr "Регистра на %s"
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "Повторите пароль"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "Имя"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "Фамилия"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "E-mail"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
-msgstr "Пожалуйста, подождите, пока администратор подтвердит Вашу регистрацию."
-
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "Ветки ещё не созданы"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr "Закрытые ветки"
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "Метки отсутсвуют"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "Закладок ещё нет"
+msgstr ""
+"Пожалуйста, подождите, пока администратор подтвердит Вашу регистрацию."
 
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr "Журнал администратора"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "Фильтр журнала..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr "Отфильтровать"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2625,30 +2143,16 @@
 msgstr[2] "%s записи"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "Действие"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "Репозиторий"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "Дата"
 
@@ -2656,7 +2160,7 @@
 msgid "From IP"
 msgstr "С IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "Действия ещё не производились"
 
@@ -2665,19 +2169,19 @@
 msgstr "Настройки аутентификации"
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr "Аутентификация"
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr "Плагины аутентификации"
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr "Включенные плагины"
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 #, fuzzy
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
@@ -2686,90 +2190,90 @@
 "Список плагинов, разделенных запятой. Kallithea будет пробовать "
 "аутентифицировать пользователя в порядке указания плагинов"
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr "Доступные встроенные плагины"
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr "Плагин"
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "Сохранить"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr "Значения по умолчанию"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "Тип"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "Приватный репозиторий"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
 msgstr "Приватные репозитории видны только их участникам."
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "Включить статистику"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr "Включить окно статистики на странице «Общие сведения»."
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "Включить скачивание"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr "Включить меню скачивания на странице «Общие сведения»."
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "Включить блокирование"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr "Включить автоблокировку для репозитория."
 
@@ -2778,45 +2282,45 @@
 msgid "Edit Gist"
 msgstr "Правка gist-записи"
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr "Описание..."
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr "Истекает"
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "никогда"
@@ -2826,7 +2330,8 @@
 msgstr "Обновить"
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr "Отмена"
 
@@ -2849,16 +2354,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr "Создать новую gist-запись"
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr "Создано"
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr "Gist-записи отсутствуют"
 
@@ -2867,45 +2372,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "Сброс"
 
@@ -2915,113 +2420,111 @@
 msgstr "Gist"
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr "URL"
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "Удалить"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr "Подтвердите удаление этой gist-записи"
 
-#: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
 #: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
 #: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
 #: kallithea/templates/pullrequests/pullrequest_show.html:41
 msgid "Edit"
 msgstr "Редактировать"
 
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/admin/gists/show.html:63
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr "Показать только текст"
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr "создана"
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr "Показать только текст"
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "Мой Аккаунт"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr "Профиль"
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 #, fuzzy
 msgid "Email Addresses"
 msgstr "Новый E-mail"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr "API-ключи"
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "репозитории"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "Создать репозитории"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
@@ -3029,76 +2532,93 @@
 msgid "Show Permissions"
 msgstr "Скопировать привилегии"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, fuzzy, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "Подтвердите сброс этого API-ключа: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 #, fuzzy
 msgid "Expired"
 msgstr "Истекает"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "Подтвердите удаление этого API-ключа: %s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 #, fuzzy
 msgid "Remove"
 msgstr "Удалено"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 #, fuzzy
 msgid "No additional API keys specified"
 msgstr "Дополнительные адреса e-mail не указаны"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 #, fuzzy
 msgid "New API key"
 msgstr "Ключ"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "Добавить"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr "Основной"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "Подтвердите удаление E-mail: %s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr "Нет дополнительных адресов e-mail."
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "Новый E-mail"
 
@@ -3106,106 +2626,70 @@
 msgid "Change Your Account Password"
 msgstr "Смена пароля"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr "Текущий пароль"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "Новый пароль"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr "Подтвердите новый пароль"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+#, fuzzy
+msgid "Current IP"
+msgstr "текущий IP-адрес"
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
 msgstr "Измените аватар через сайт"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "Используется"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr "Аватары отключены"
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr "Не указан email. Пожалуйста, обновите ваш email."
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-#, fuzzy
-msgid "Current IP"
-msgstr "текущий IP-адрес"
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "Репозитории, где Вы — владелец"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "Записи не найдены."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "Имя"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr "Репозитории, за которыми Вы наблюдаете"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "Мои уведомления"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "Всё"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "Комментарии"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr "Pull-запросы"
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr "Отметить все как прочтённые"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "Уведомлений нет"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr "Показать уведомление"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "Уведомления"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr "Стандартные привилегии"
 
@@ -3219,18 +2703,24 @@
 msgid "IP Whitelist"
 msgstr "Белый список IP"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "Анонимный доступ"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "Анонимный доступ"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
@@ -3240,48 +2730,48 @@
 "репозитория. Учтите, что ранее установленные привилегии по умолчанию "
 "будут сброшены"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Существующий репозиторий?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "Группа репозиториев"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "Группа репозиториев"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 "Выбранные привилегии будут установлены по умолчанию для каждой группы "
-"репозиториев. Учтите, что ранее установленные привилегии по умолчанию для"
-" групп репозиториев будут сброшены"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+"репозиториев. Учтите, что ранее установленные привилегии по умолчанию для "
+"групп репозиториев будут сброшены"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "Удалить эту группу репозиториев"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr "Группа пользователей"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 #, fuzzy
 msgid ""
 "All default permissions on each user group will be reset to chosen "
@@ -3292,107 +2782,109 @@
 "пользователей. Учтите, что ранее установленные привилегии по умолчанию "
 "для групп пользователей будут сброшены"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "Создание репозитория"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr "Создание групп пользователей"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "Создание форка репозитория"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "Регистрация"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr "Активация сторонней учетной записи"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "Подтвердите удаление IP %s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr "Все IP-адреса разрешены."
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr "Новый IP-адрес"
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr "Группы репозиториев"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "Имя группы"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "Родительская группа"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3401,30 +2893,29 @@
 msgid "%s Repository Group Settings"
 msgstr "Настройки группы репозиториев %s"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr "Добавить подгруппу"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "Настройки"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr "Дополнительно"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "Привилегии"
@@ -3449,12 +2940,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "Создано"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3466,8 +2957,30 @@
 msgid "Delete this repository group"
 msgstr "Удалить эту группу репозиториев"
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "disabled"
+msgid "Visible"
+msgstr "отключено"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "Add Repository"
+msgid "Add repos"
+msgstr "Добавить репозиторий"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+#, fuzzy
+#| msgid "Add user group"
+msgid "Add/Edit groups"
+msgstr "Добавить группу пользователей"
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 #, fuzzy
 msgid "User/User Group"
@@ -3475,8 +2988,8 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3485,106 +2998,101 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 #, fuzzy
 msgid "Revoke"
 msgstr "отозвать"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr "Добавить новый"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 #, fuzzy
 msgid "Apply to children"
 msgstr "применить к дочерним"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
 msgstr ""
 "Включить автоблокировку для группы. Эта опция будет применена ко всем "
 "дочерним группам и репозиториям"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr "Удалить эту группу"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr "Подтвердите удаление этой группы пользователей"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "Домой"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "с"
+#, fuzzy, python-format
+#| msgid "Repository Group: %s"
+msgid "Repository group %s"
+msgstr "Группа репозиториев: %s"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr "Администрирование групп репозиториев"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr "Число репозиториев верхнего уровня"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "[создан] репозиторий"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr "Короткое и осмысленное. Для развернутого описания используйте файл README."
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+"Короткое и осмысленное. Для развернутого описания используйте файл README."
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr "Опционально выбрать группу, в которую поместить данный репозиторий."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr "Тип создаваемого репозитория."
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "Ревизия для выгрузки"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3617,22 +3125,22 @@
 msgid "%s Repository Settings"
 msgstr "Настройки репозитория %s"
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr "Дополнительные поля"
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "Удалённый"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "Статистика"
 
@@ -3641,28 +3149,26 @@
 msgstr "Родительская группа"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr "Набор"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr "Вручную сделать этот репозиторий форком выбранного из списка."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr "Доступ к публичному журналу"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "Удалить из общедоступного журнала"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr "Добавить в публичный журнал"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
@@ -3670,36 +3176,36 @@
 "Все производимые с этим репозиторием действия будут отображаться в "
 "публичном журнале."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr "Включить блокирование"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 msgid "Confirm to unlock repository."
 msgstr "Подтвердите снятие блокировки с репозитория."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 msgid "Unlock Repository"
 msgstr "Разблокировать репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 msgid "Confirm to lock repository."
 msgstr "Подтвердите блокировку репозитория."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "Заблокировать репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "Репозиторий не заблокирован"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3707,17 +3213,17 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "Подтвердите удаление этого репозитория: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Удалить этот репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
@@ -3725,15 +3231,15 @@
 msgstr[1] "Данный репозиторий имеет %s копии"
 msgstr[2] "Данный репозиторий имеет %s копий"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr "Отсоединить fork'и"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr "Удалить fork'и"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3744,110 +3250,113 @@
 msgid "Invalidate Repository Cache"
 msgstr "Сбросить кэш репозитория"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr "Подтвердите сброс кэша."
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
-msgstr "Ручной сброс кэша репозитория. При первом доступе кэш восстановится."
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+msgstr ""
+"Ручной сброс кэша репозитория. При первом доступе кэш восстановится."
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 msgid "List of Cached Values"
 msgstr "Список кешированных значений"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "Префикс"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "Ключ"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "Активный"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr "Подтвердите удаление этого поля: %s"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr "Ключ"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr "Имя поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr "Введите краткое имя поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr "Описание поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr "Введите описание поля"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr "Дополнительные поля отключены."
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "приватный репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "[форкнут] репозиторий"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Репозиторий %s создан"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "[внесены изменения из удалённого репозитория] в репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "Подтвердите скачивание изменений."
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "приватный репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3857,37 +3366,42 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "[создан] репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "Репозиторий"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 "Ревизия по умолчанию, из которой будет производиться выгрузка файлов при "
 "скачивании"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr "Изменить владельца репозитория."
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3903,7 +3417,7 @@
 msgid "Repositories Administration"
 msgstr "Администрирование репозиториев"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3924,7 +3438,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "Хуки"
 
@@ -3936,279 +3450,271 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "Отправить"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr "Брендинг сайта"
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr "Приветствие для HTTP-аутентификации"
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr "Сохранить настройки"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr "Пользовательские хуки"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr "Пользовательские хуки"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "Не удалось удалить хук"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Rescan option"
+msgid "Rescan options"
 msgstr "Опции пересканирования"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 #, fuzzy
 msgid "Delete records of missing repositories"
 msgstr "Поиск по репозиториям"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr "Сбросить кэш для всех репозиториев"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr "Сбросить кэш для всех репозиториев."
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
+#: kallithea/templates/admin/settings/settings_mapping.html:25
 msgid "Install Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr "Опции создания индекса"
 
-#: kallithea/templates/admin/settings/settings_search.html:12
+#: kallithea/templates/admin/settings/settings_search.html:9
 msgid "Build from scratch"
 msgstr "Сборка с нуля"
 
-#: kallithea/templates/admin/settings/settings_search.html:15
+#: kallithea/templates/admin/settings/settings_search.html:12
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "Перестроить индекс"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
-msgstr "Версия Git"
+msgid "Kallithea configuration file"
+msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr "Версия Git"
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "Веб"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr "Запрашивать SSL для операций с VCS"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "Показывать размер репозитория после отправки"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "Логировать пользовательские команды отправки"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "Логировать пользовательские команды получения"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "Обновлять репозиторий после отправки (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr "Расширения Mercurial"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr "Включить поддержку больших файлов"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr "Включить поддержку hgsubversion"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 msgid "Location of repositories"
 msgstr "Местонахождение репозиториев"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
 msgstr ""
 "Нажмите для разблокирования. Изменения вступят в силу после перезагрузки "
 "Kallithea."
 
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "Главное"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr "Использовать дополнительные поля в репозиториях"
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr "Позволяет хранить дополнительные поля в репозиториях."
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr "Отображать версию Kallithea"
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4225,93 +3731,101 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr "Ссылка для клонирования"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr "Элементы панели"
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repository Size"
+msgid "Repository page size"
+msgstr "Размер репозитория"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "Иконки"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "Показывать иконки публичных репозиториев"
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr "Показывать иконки приватных репозиториев"
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr "Показывать иконки приватных репозиториев"
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr "Показывать иконки публичных репозиториев."
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "Метатегирование"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr "Добавить группу пользователей"
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4331,13 +3845,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "Участники"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr "Подтвердите удаление следующей группы пользователей: %s"
@@ -4346,15 +3860,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "Нет участников"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr "Выбранные участники группы"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "Доступные участники"
 
@@ -4368,17 +3882,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "Пользователи"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "Подтверждение пароля"
 
@@ -4397,12 +3911,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4411,7 +3925,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "Подтвердите удаление пользователя %s"
@@ -4420,24 +3934,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "Подтвердите новый пароль"
 
@@ -4445,225 +3951,226 @@
 msgid "Users Administration"
 msgstr "Администрирование пользователей"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr "Экземпляр сервера: %s"
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Репозиторий Mercurial"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Git репозиторий"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr "Создать форк"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "Общие сведения"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "История изменений"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "Файлы"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr "Переключиться на"
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr "Показать pull-запросы для %s"
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr "Pull-запросы"
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "Опции"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr "Сравнить форк"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "Сравнить"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "Поиск"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr "Разблокировать"
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr "Заблокировать"
 
-#: kallithea/templates/base/base.html:173
+#: kallithea/templates/base/base.html:174
 msgid "Follow"
 msgstr "Наблюдать"
 
-#: kallithea/templates/base/base.html:174
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr "Не наблюдать"
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "Форк"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr "Создать Pull запрос"
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr "Показать pull-запросы для %s"
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr "Переключиться на"
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr "Показать последнюю активность"
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "Общедоступный журнал"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr "Показать публичные записи"
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr "Gist"
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr "Все публичные Gist-записи"
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr "Мои публичные Gist-записи"
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr "Мои приватные Gist-записи"
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr "Поиск по репозиториям"
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr "Мои Pull-запросы"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr "Не авторизован"
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr "Авторизоваться"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "Забыли пароль?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "Нет аккаунта?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "Выход"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Значения по умолчанию"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "Создать репозитории"
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr "Опция разрешает пользователю создавать репозитарии"
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr "Опция разрешает пользователю создавать репозитарии"
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr "Создавать группы пользователей"
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr "Опция разрешает пользователю создавать группы пользователей"
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "Создавать fork от репозиториев"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 "Выберите эту опцию чтобы разрешить данному пользователю создавать fork'и "
 "репозиториев"
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "Показать"
 
@@ -4672,150 +4179,145 @@
 msgstr "Привилегии еще не назначены"
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "Привилегия"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "Изменить привилегии"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
+msgstr "Применение..."
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "Включить скачивание"
+
+#: kallithea/templates/base/root.html:31
 msgid "Add Another Comment"
 msgstr "Добавить ещё один комментарий"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "Отменить наблюдение за репозиторием"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "Наблюдать за репозиторием"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "Группа"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "участники"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr "Загрузка..."
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr "загрузка..."
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr "Поиск усечен"
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr "Нет совпадений"
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 #, fuzzy
 msgid "Open New Pull Request from {0}"
 msgstr "Комментарий в pull-запросе"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr ""
 
-#: kallithea/templates/base/root.html:33
+#: kallithea/templates/base/root.html:41
 #, fuzzy
 msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr "Показать выбранные наборы изменений: __S &rarr; __E"
 
-#: kallithea/templates/base/root.html:34
+#: kallithea/templates/base/root.html:42
 msgid "Selection Link"
 msgstr "Ссылка выбора"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 msgid "Collapse Diff"
 msgstr "Свернуть сравнение"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 msgid "Expand Diff"
 msgstr "Раскрыть сравнение"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "версии"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr "Не удалось отозвать привилегии"
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr "Подтвердите удаление привилегии для {0}: {1} ?"
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr "включено"
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr "отключено"
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "Выбрать набор изменений"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr "Выбрать набор изменений"
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "Закладки %s"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "Автор"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "Ревизия"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "Ветки %s"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "По возрастанию"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "По убыванию"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "Записи не найдены."
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "Ошибка данных."
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "Загрузка..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "Логи изменений %s"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4823,81 +4325,31 @@
 msgstr[1] "Показаны %d из %d ревизий"
 msgstr[2] "Показаны %d из %d ревизий"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "Очистить выбор"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr "Перейти на верхушку репозитория"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr "Сравнить fork с %s"
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr "Сравнить форк с родительским репозиторием (%s)"
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr "Отфильтровать ветку:"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, fuzzy, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-"Статус набора изенений: %s⏎\n"
-"Кликрните, чтобы перейти к соответствующему pull-request'у #%s"
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr "Статус набора изменений: %s"
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr "Комментарии отсутствуют"
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr "Закладка %s"
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr "Метка %s"
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr "Ветка %s"
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "Изменений ещё нет"
 
@@ -4913,7 +4365,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr "Добавлено"
 
@@ -4927,103 +4379,117 @@
 msgid "Affected %s files"
 msgstr "Затрагивает %s файлов"
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr "Возраст"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr "Ссылки"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "Добавить или загрузить файлы через Kallithea"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Отправить новый репозиторий"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "Существующий репозиторий?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, fuzzy, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"Статус набора изенений: %s⏎\n"
+"Кликрните, чтобы перейти к соответствующему pull-request'у #%s"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Статус набора изменений: %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d комментарий"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr "Закладка %s"
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr "Метка %s"
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr "Ветка %s"
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
 msgstr "%s Изменения"
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "Статут изменений"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr "Отобразить в формате diff"
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr "Применить разностное исправление (Patch diff)"
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr "Скачать diff"
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "свести"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr "Перенесено из:"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "Создано"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "Создано"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -5031,9 +4497,9 @@
 msgstr[1] "%s файлов изменено"
 msgstr[2] "%s файла изменено"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -5041,18 +4507,19 @@
 msgstr[1] "%s файла изменёно: %s добавления, %s удаления"
 msgstr[2] "%s файлов изменёно: %s добавлений, %s удалений"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr "Показать полный diff"
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
+#: kallithea/templates/changeset/changeset_file_comment.html:20
 #, fuzzy
-msgid "No revisions"
-msgstr "версии"
+#| msgid "Comment"
+msgid "comment"
+msgstr "Комментировать"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -5072,61 +4539,58 @@
 msgid "Delete comment?"
 msgstr "Удалить комментарий?"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Последние изменения"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "Комментарий к строке {1}."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-"Парсинг комментариев выполнен с использованием синтаксиса %s с поддержкой"
-" %s."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
 msgstr ""
 "Используйте @имя_пользователя в тексте, чтобы отправить оповещение "
-"указанному пользователю"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr "Предварительный просмотр комментария"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr "Применение..."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+"указанному пользователю."
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "Изменить статус ревизии"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr "Без изменений"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Комментарий в pull-запросе"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr "Закрыть"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "Комментировать"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr "Предпросмотр"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr "Вам необходимо авторизоваться, чтобы оставлять комментарии."
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "Авторизоваться сейчас"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "Скрыть"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
@@ -5134,7 +4598,7 @@
 msgstr[1] "%d комментария"
 msgstr[2] "%d комментариев"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
@@ -5142,7 +4606,7 @@
 msgstr[1] "%d к строкам"
 msgstr[2] "%d к строкам"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
@@ -5150,104 +4614,110 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-#, fuzzy
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-"Используйте @имя_пользователя в тексте, чтобы отправить оповещение "
-"указанному пользователю."
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr "Изменить статус ревизии"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr "Без изменений"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr "Закрыть"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr "%s Изменения"
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "Статус набора изменений: %s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "Затронутые файлы"
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "файл удалён"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "Неизменный"
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr "Удалён"
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr "Переименован"
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Неизвестная ревизия %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "Безымянный"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "файл удалён"
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr "Показать полный diff для этого файла"
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr "Показать полный diff для этого файла"
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr "Показать комментарии в строках"
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr "Удалён"
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr "Переименован"
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr "Нет изменений"
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr "Предок"
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+#, fuzzy
+#| msgid "Common ancestor"
+msgid "Merge Ancestor"
+msgstr "Общий предок"
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr "Показать merge diff"
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr "Общий предок"
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr "отстаёт на"
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr "%s изменений"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr "от"
 
@@ -5258,20 +4728,20 @@
 msgstr "%s Сравнить"
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -5279,109 +4749,168 @@
 msgstr[1] "Показать %s commit'а"
 msgstr[2] "Показать %s commit'ов"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr "Показать полный diff"
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "Публичный репозиторий"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "Изменений ещё не было"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Подписаться на ленту RSS %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Подписаться на ленту Atom %s"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s оставил комментарий к набору изменений %(age)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Comment from %s on %s changeset %s"
+msgid "Comment on Changeset \"%s\""
 msgstr "Комментарий от %s к набору изменений %s %s"
 
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr "Состояние набора изменений изменено на"
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset flow"
+msgid "Changeset on"
+msgstr "Поток изменений"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Ветка"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Последние изменения"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "This pull request has been closed and can not be updated."
+msgid "The pull request has been closed."
+msgstr "Этот pull-запрос был закрыт и не может быть обновлён."
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr "Здравствуйте, %s"
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 #, fuzzy
 msgid "We have received a request to reset the password for your account."
 msgstr "Мы отправили запрос на создание нового пароля для вашего аккаунта."
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention on Pull Request %s \"%s\" by %s"
 msgstr "%s упомянул Вас в комментарии к pull-запросу %s \"%s\""
 
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, fuzzy, python-format
+#| msgid "%s requested your review of %s pull request \"%s\""
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
 msgstr "%s запросил рецензирование pull-запроса %s \"%s\""
 
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "Pull-запрос %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
+msgstr ""
+
 #: kallithea/templates/email_templates/pull_request_comment.html:4
-#, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr "Комментарий закрыл pull-запрос со статусом"
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr "Комментарий оставлен со статусом"
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "Подробнее о пользователе"
+#, fuzzy, python-format
+#| msgid "%s mentioned you on %s pull request \"%s\""
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr "%s упомянул Вас в комментарии к pull-запросу %s \"%s\""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "Pull-запросы %s от %s#%s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "[прокомментировано] в запросе на внесение изменений для"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "Имя группы"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5399,7 +4928,7 @@
 msgstr "Сравнение файла %s"
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr "%s Файлы"
@@ -5409,72 +4938,74 @@
 msgid "%s Files Add"
 msgstr "%s Файлов добавлено"
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Расположение"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "или"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr "Режим нового файла"
+#, fuzzy
+msgid "New file type"
+msgstr "новый файл"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr "Применить изменения"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr "Предыдущая ревизия"
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr "Следующая ревизия"
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr "Отслеживать данную ветку"
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "Загружается список файлов..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "Размер"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "Последняя версия"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr "Последнее изменение"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr "Автор последней ревизии"
 
@@ -5484,7 +5015,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5497,24 +5028,20 @@
 msgid "Edit file"
 msgstr "Редактировать файл"
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr "Исходный код"
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "Редактирование файла"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5523,51 +5050,60 @@
 msgstr[1] "%s авторов"
 msgstr[2] "%s автора"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
-msgstr ""
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "Удалена ветка: %s"
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
+msgstr "Редактирование файлов разрешено только в HEAD-ревизии данной ветки"
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr "Редактирование файлов разрешено только в HEAD-ревизии данной ветки"
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "Бинарный файл (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr "Файл слишком большой для отображения."
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr "Показать полный diff."
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "Файл слишком большой для отображения"
+msgid "Show as raw."
+msgstr "Показать только текст."
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5587,12 +5123,12 @@
 msgstr "%s Наблюдатели"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "Наблюдатели"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr "Наблюдать за репозиторием"
 
@@ -5601,37 +5137,37 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "Имя форка"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 "Ревизия по умолчанию, из которой будет производиться выгрузка файлов при "
 "скачивании."
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "Приватный"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "Скопировать привилегии"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "Скопировать привилегии с форкнутого репозитория"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "Обновлять после клонирования"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "Скачивать исходники после создания клона"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5641,40 +5177,40 @@
 msgstr "Форки %s"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "Ответвления"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr "Форкнуто"
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "Форки ещё не созданы"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "Лента журнала ATOM"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "Лента журнала RSS"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr "Мои репозитории"
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "Записи отсуствуют"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "Общая лента журнала ATOM"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "Общая лента журнала RSS"
 
@@ -5683,31 +5219,36 @@
 msgid "New Pull Request"
 msgstr "Новый pull-запрос"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "Заголовок"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr "Написать короткое писание по этому запросу"
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr "Поток изменений"
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr "Первоначальный репозиторий"
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "Ревизия"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr "Репозиторий назначения"
 
@@ -5720,6 +5261,10 @@
 msgid "Vote"
 msgstr "отозвать"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr "Возраст"
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5743,7 +5288,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "Закрыто"
 
@@ -5766,114 +5311,106 @@
 msgstr "%s Pull-запрос #%s"
 
 #: kallithea/templates/pullrequests/pullrequest_show.html:10
-#, fuzzy, python-format
+#, python-format
 msgid "Pull request %s from %s#%s"
-msgstr "Pull-запросы №%s от %s#%s"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+msgstr "Pull-запросы %s от %s#%s"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 #, fuzzy
 msgid "Summarize the changes"
 msgstr "Применить изменения"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "Еще не рассмотренный"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d рецензент"
-msgstr[1] "%d рецензента"
-msgstr[2] "%d рецензентов"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr "Запрос на внесение изменений был рассмотрен всеми рецензентами"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr "Нет рецензентов"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 #, fuzzy
 msgid "on"
 msgstr "ничего"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr "Принять изменения"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
 #, fuzzy
-msgid "Update"
-msgstr "[обновлён] пользователь"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "Регистрация"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr "Рецензенты pull-запросов"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr "Сохранить изменения"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr "Отменить изменения"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "reviewer"
+msgid "Reviewers"
+msgstr "рецензент"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr "Удалить рецензента"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr "Потенциальные рецензенты"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr "Сохранить изменения"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-#, fuzzy
-msgid "Save as New Pull Request"
-msgstr "Создать новый pull запрос"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr "Отменить изменения"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 #, fuzzy
 msgid "Pull Request Content"
 msgstr "Статус pull-request'а был изменен"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr "Общий предок"
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5881,7 +5418,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "Pull-запросы от %s"
 
@@ -5890,37 +5426,43 @@
 msgid "Pull Requests to '%s'"
 msgstr "Pull-запросы для %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr "Создать новый pull-запрос"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, fuzzy, python-format
 msgid "Show Pull Requests to %s"
 msgstr "Pull-запросы для %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, fuzzy, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr "Pull запросы от %s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 #, fuzzy
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr "Показать закрытые pull-запросы"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 #, fuzzy
 msgid "Pull Requests Created by Me"
 msgstr "Рецензенты запросов на внесение изменений Pull request"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "Рецензенты pull-запросов"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 #, fuzzy
 msgid "Pull Requests I Participate In"
 msgstr "Моё участие"
@@ -5935,29 +5477,29 @@
 msgid "Search in All Repositories"
 msgstr "Поиск по всем репозиториям"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "Фраза для поиска"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "Критерий поиска"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "Содержимое файлов"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "Сообщения коммитов"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "Имя файла"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "Недостаточно прав"
 
@@ -5967,80 +5509,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr "ATOM лента репозитория %s"
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr "RSS лента репозитория %s"
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr "Включено"
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr "Полученная статистика: "
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "файлы"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Показать еще"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "commit'ы"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "файлы добавлены"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "файлы изменены"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "файлы удалены"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "commit"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "файл удалён"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "файл изменён"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "файл удалён"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "%s общие сведения"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "Репозитарий заблокирован %s"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "Репозиторий разблокирован"
 
@@ -6048,86 +5590,276 @@
 msgid "Fork of"
 msgstr "Форк от"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Клонировать из"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr "Ссылка для клонирования"
-
-#: kallithea/templates/summary/summary.html:78
-msgid "Show by Name"
-msgstr "Показать по имени"
-
-#: kallithea/templates/summary/summary.html:79
+#: kallithea/templates/summary/summary.html:68
 msgid "Show by ID"
 msgstr "Показать по ID"
 
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr "Показать по имени"
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "Популярные файлы"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "Скачать"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "Скачиваний ещё нет"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "Скачивание отключено в этом репозитории"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "Скачать в zip"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "Отметьте для скачивания архива с дочерними репозиториями"
 
-#: kallithea/templates/summary/summary.html:125
-#, fuzzy
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
-msgstr "с дочерними репозиториями"
-
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr "Размер репозитория"
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+msgstr "С дочерними репозиториями"
+
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr "Лента новостей"
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Последние изменения"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr "Быстрый старт"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr "Добавить или загрузить файлы через Kallithea"
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "Отправить новый репозиторий"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "Существующий репозиторий?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "Скачать %s как %s"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s Меток"
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr "Сравнить теги"
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "Индексы отсутствуют. Пожалуйста, запустите индексатор Whoosh"
+
+#~ msgid "IP %s not allowed"
+#~ msgstr "IP %s заблокирован"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s оставил комментарий к набору изменений %(age)s"
+
+#~ msgid "%(user)s sent message %(age)s"
+#~ msgstr "%(user)s отправил сообщение %(age)s"
+
+#~ msgid "%(user)s mentioned you %(age)s"
+#~ msgstr "%(user)s упомянул вас %(age)s"
+
+#~ msgid "%(user)s registered in Kallithea %(age)s"
+#~ msgstr "%(user)s зарегистрировался в Kallithea %(age)s"
+
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "%(user)s открыл новый pull-запрос %(age)s"
+
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "%(user)s оставил комментарий к pull-запросу %(age)s"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s оставил комментарий к набору изменений %(when)s"
+
+#~ msgid "%(user)s sent message at %(when)s"
+#~ msgstr "%(user)s отправил сообщение %(when)s"
+
+#~ msgid "%(user)s mentioned you at %(when)s"
+#~ msgstr "%(user)s упомянул вас %(when)s"
+
+#~ msgid "%(user)s registered in Kallithea at %(when)s"
+#~ msgstr "%(user)s зарегистрировался в Kallithea %(when)s"
+
+#~ msgid "%(user)s opened new pull request at %(when)s"
+#~ msgstr "%(user)s открыл новый pull-запрос %(when)s"
+
+#~ msgid "%(user)s commented on pull request at %(when)s"
+#~ msgstr "%(user)s оставил комментарий к pull-запросу %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "Группа репозиториев"
+
+#~ msgid "My Notifications"
+#~ msgstr "Мои уведомления"
+
+#~ msgid "All"
+#~ msgstr "Всё"
+
+#~ msgid "Comments"
+#~ msgstr "Комментарии"
+
+#~ msgid "Mark All Read"
+#~ msgstr "Отметить все как прочтённые"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "Отметить все как прочтённые"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "Уведомлений нет"
+
+#~ msgid "Show Notification"
+#~ msgstr "Показать уведомление"
+
+#~ msgid "Notifications"
+#~ msgstr "Уведомления"
+
+#~ msgid "Home"
+#~ msgstr "Домой"
+
+#~ msgid "with"
+#~ msgstr "с"
+
+#~ msgid "members"
+#~ msgstr "участники"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "Комментарии отсутствуют"
+
+#~ msgid "Author"
+#~ msgstr "Автор"
+
+#~ msgid "Refs"
+#~ msgstr "Ссылки"
+
+#, fuzzy
+#~ msgid "Commenting on line."
+#~ msgstr "Комментарий к строке {1}."
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "Комментарий в pull-запросе"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "Дата"
+
+#~ msgid "Previous revision"
+#~ msgstr "Предыдущая ревизия"
+
+#~ msgid "Next revision"
+#~ msgstr "Следующая ревизия"
+
+#~ msgid "Follow current branch"
+#~ msgstr "Отслеживать данную ветку"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "Еще не рассмотренный"
+
+#~ msgid "%d reviewer"
+#~ msgid_plural "%d reviewers"
+#~ msgstr[0] "%d рецензент"
+#~ msgstr[1] "%d рецензента"
+#~ msgstr[2] "%d рецензентов"
+
+#~ msgid "Pull request was reviewed by all reviewers"
+#~ msgstr "Запрос на внесение изменений был рассмотрен всеми рецензентами"
+
+#~ msgid "There are no reviewers"
+#~ msgstr "Нет рецензентов"
+
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "Рецензенты pull-запросов"
+
+#~ msgid "Dashboard"
+#~ msgstr "Панель управления"
+
+#~ msgid "Group Name"
+#~ msgstr "Имя группы"
+
+#~ msgid "Remember me"
+#~ msgstr "Запомнить"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "Измените аватар через сайт"
+
+#~ msgid "Using"
+#~ msgstr "Используется"
+
+#~ msgid "Missing email, please update your user email address."
+#~ msgstr "Не указан email. Пожалуйста, обновите ваш email."
+
+#~ msgid "Rescan option"
+#~ msgstr "Опции пересканирования"
+
+#~ msgid "Web"
+#~ msgstr "Веб"
+
+#~ msgid "Require SSL for vcs operations"
+#~ msgstr "Запрашивать SSL для операций с VCS"
+
+#~ msgid "Dashboard items"
+#~ msgstr "Элементы панели"
+
+#~ msgid "quick filter..."
+#~ msgstr "фильтр..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "Забыли пароль?"
+
+#~ msgid "Ancestor"
+#~ msgstr "Предок"
+
+#~ msgid "Comment from %s on %s changeset %s"
+#~ msgstr "Комментарий от %s к набору изменений %s %s"
+
+#~ msgid "The changeset status was changed to"
+#~ msgstr "Состояние набора изменений изменено на"
+
+#~ msgid "%s mentioned you on %s pull request \"%s\""
+#~ msgstr "%s упомянул Вас в комментарии к pull-запросу %s \"%s\""
+
+#~ msgid "%s requested your review of %s pull request \"%s\""
+#~ msgstr "%s запросил рецензирование pull-запроса %s \"%s\""
+
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "Комментарий закрыл pull-запрос со статусом"
+
+#~ msgid "The comment was made with status"
+#~ msgstr "Комментарий оставлен со статусом"
+
+#~ msgid "View this user here"
+#~ msgstr "Подробнее о пользователе"
+
+#~ msgid "Repository Size"
+#~ msgstr "Размер репозитория"
 
 #~ msgid "No comments."
 #~ msgstr "Нет комментариев."
@@ -6138,9 +5870,6 @@
 #~ msgid "journal"
 #~ msgstr "журнал"
 
-#~ msgid "unmodified"
-#~ msgstr ""
-
 #~ msgid "Locked repository"
 #~ msgstr "Закрытый репозиторий"
 
@@ -6162,9 +5891,6 @@
 #~ msgid "No Files"
 #~ msgstr "Файлов нет"
 
-#~ msgid ""
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "Имя \"%(username)s\" отклонено"
 
@@ -6177,12 +5903,6 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "неверный URL для клонирования"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
 #~ msgid "Defaults"
 #~ msgstr "Значения по умолчанию"
 
@@ -6195,24 +5915,6 @@
 #~ msgid "My Permissions"
 #~ msgstr "Мои привилегии"
 
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
 #~ msgid "delete"
 #~ msgstr "удалить"
 
@@ -6243,15 +5945,6 @@
 #~ msgid "admin"
 #~ msgstr "администратор"
 
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
 #~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr "Опциональный URL, с которого требуется склонировать репозиторий."
 
@@ -6261,93 +5954,21 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "Получить изменения с удалённой стороны"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
 #~ msgid "Non-changeable id"
 #~ msgstr "Неизменяемый id"
 
 #~ msgid "edit"
 #~ msgstr "редактировать"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
 #~ msgid "Destroy old data"
 #~ msgstr "Уничтожить все данные"
 
-#~ msgid "check for updates"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "Стандартные привилегии"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "Изменение статуса"
 
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
 #~ msgid "Mimetype"
 #~ msgstr "Тип файла"
 
@@ -6357,15 +5978,9 @@
 #~ msgid "Latest vote: %s"
 #~ msgstr "Последняя оценка: %s"
 
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "владелец"
 
-#~ msgid "reviewer"
-#~ msgstr "рецензент"
-
 #~ msgid "Your new password"
 #~ msgstr "Ваш новый пароль"
 
@@ -6381,9 +5996,210 @@
 #~ msgid "You can generate it by clicking following URL"
 #~ msgstr "Вы можете заново сгенерировать его, перейдя по следующей ссылке"
 
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
 #~ msgid "Created by"
 #~ msgstr "Создано"
 
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "Закрыт, замещён %s ."
+
+#~ msgid "Closing."
+#~ msgstr "Закрыт."
+
+#~ msgid "Changeset not found"
+#~ msgstr "Набор изменений не найден"
+
+#~ msgid "Repository no access"
+#~ msgstr "Репозитарий - нет доступа"
+
+#~ msgid "Repository read access"
+#~ msgstr "Репозитарий - доступ на чтение"
+
+#~ msgid "Repository write access"
+#~ msgstr "Репозитарий - доступ на запись"
+
+#~ msgid "Repository admin access"
+#~ msgstr "Репозитарий - администрирование"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "Группа Репозиториев - нет доступа"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "Группа Репозиториев - доступ на чтение"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "Группа Репозиториев - доступ на запись"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "Группа Репозиториев - администрирование"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "Создание репозиториев отключено"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "Создание репозиториев включено"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "Возможность создавать форк репозитория отключена"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "Возможность создавать форк репозитория включена"
+
+#~ msgid "Register disabled"
+#~ msgstr "Регистрация отключена"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "Регистрация нового пользователя в Kallithea с ручной активацией"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr ""
+#~ "Регистрация нового пользователя в Kallithea с автоматической активацией"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "Не просмотрено"
+
+#~ msgid "Rejected"
+#~ msgstr "Отклонено"
+
+#~ msgid "Under Review"
+#~ msgstr "На рассмотрении"
+
+#~ msgid "Repository group no access"
+#~ msgstr "Группа Репозиториев - нет доступа"
+
+#~ msgid "Repository group read access"
+#~ msgstr "Группа репозиториев - доступ на чтение"
+
+#~ msgid "Repository group write access"
+#~ msgstr "Группа репозиториев - доступ на запись"
+
+#~ msgid "Repository group admin access"
+#~ msgstr "Группа репозиториев - администрирование"
+
+#~ msgid "User group no access"
+#~ msgstr "Группа пользователей - нет доступа"
+
+#~ msgid "User group read access"
+#~ msgstr "Группа пользователей - доступ на чтение"
+
+#~ msgid "User group write access"
+#~ msgstr "Группа пользователей - доступ на запись"
+
+#~ msgid "User group admin access"
+#~ msgstr "Группа пользователей - администрирование"
+
+#~ msgid "Repository Group creation disabled"
+#~ msgstr "Создание групп репозиториев отключено"
+
+#~ msgid "Repository Group creation enabled"
+#~ msgstr "Создание групп репозиториев включено"
+
+#~ msgid "User Group creation disabled"
+#~ msgstr "Создание групп пользователей отключено"
+
+#~ msgid "User Group creation enabled"
+#~ msgstr "Создание групп пользователей включено"
+
+#~ msgid "User Registration with manual account activation"
+#~ msgstr "Регистрация пользователя с ручной активацией учётной записи"
+
+#~ msgid "User Registration with automatic account activation"
+#~ msgstr "Регистрация пользователя с автоматической активацией"
+
+#~ msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr ""
+#~ "%(user)s просит вас рассмотреть pull request #%(pr_id)s: %(pr_title)s"
+
+#~ msgid "repositories"
+#~ msgstr "репозитории"
+
+#~ msgid "No repositories found."
+#~ msgstr "Репозитарии не найдены."
+
+#~ msgid "There are no branches yet"
+#~ msgstr "Ветки ещё не созданы"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "Метки отсутсвуют"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "Закладок ещё нет"
+
+#~ msgid "enabled"
+#~ msgstr "включено"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "Закладки %s"
+
+#~ msgid "%s Branches"
+#~ msgstr "Ветки %s"
+
+#~ msgid "Editing file"
+#~ msgstr "Редактирование файла"
+
+#~ msgid "Update"
+#~ msgstr "[обновлён] пользователь"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "Создать новый pull запрос"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s Меток"
+
+#~ msgid "Compare Tags"
+#~ msgstr "Сравнить теги"
+
+#~ msgid ""
+#~ "Changing status on a changeset associated with a closed pull request "
+#~ "is not allowed"
+#~ msgstr ""
+#~ "Нельзя редактировать статус изменений, связанных с закрытыми pull-"
+#~ "request'ами"
+
+#~ msgid "You have successfully registered into Kallithea"
+#~ msgstr "Регистрация в Kallithea прошла успешно"
+
+#~ msgid "This pull request can be updated with changes on %s:"
+#~ msgstr "Этот pull-запрос может быть обновлён из %s:"
+
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "Подтвердите сброс кэша."
+
+#~ msgid ""
+#~ "Changeset status: %s\n"
+#~ "Click to open associated pull request %s"
+#~ msgstr ""
+#~ "Статус набора изенений: %s⏎\n"
+#~ "Кликрните, чтобы перейти к соответствующему pull-request'у #%s"
+
+#~ msgid "Commenting on line {1}."
+#~ msgstr "Комментарий к строке {1}."
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr ""
+#~ "Парсинг комментариев выполнен с использованием синтаксиса %s с "
+#~ "поддержкой %s."
+
+#~ msgid "Use @username inside this text to notify another user"
+#~ msgstr ""
+#~ "Используйте @имя_пользователя в тексте, чтобы отправить оповещение "
+#~ "указанному пользователю"
+
+#~ msgid "Comment preview"
+#~ msgstr "Предварительный просмотр комментария"
+
+#~ msgid "Preview"
+#~ msgstr "Предпросмотр"
+
+#~ msgid "Use @username inside this text to notify another user."
+#~ msgstr ""
+#~ "Используйте @имя_пользователя в тексте, чтобы отправить оповещение "
+#~ "указанному пользователю."
+
+#~ msgid "New file mode"
+#~ msgstr "Режим нового файла"
+
+#~ msgid "File is too big to display"
+#~ msgstr "Файл слишком большой для отображения"
+
+#~ msgid "Save as New Pull Request"
+#~ msgstr "Создать новый pull запрос"
--- a/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/sk/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,456 +7,470 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2015-04-01 12:59+0200\n"
-"Last-Translator: Andrew Shadura <andrew@shadura.me>\n"
-"Language-Team: Slovak "
-"<https://hosted.weblate.org/projects/kallithea/kallithea/sk/>\n"
+"Last-Translator: Andrej Shadura <andrew@shadura.me>\n"
+"Language-Team: Slovak <https://hosted.weblate.org/projects/kallithea/"
+"kallithea/sk/>\n"
 "Language: sk\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
-"X-Generator: Weblate 2.3-dev\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n"
+"Generated-By: Babel 1.3\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "Zatiaľ nie sú žiadne zmeny"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(zatvorené)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Ukázať medzery"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ignorovať medzery"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "Zmeny"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "Úspešne zmazaný súbor %s"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr "Taká revízia neexistuje"
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Prázdny repozitár"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 #, fuzzy
 msgid "No response"
 msgstr "Neznáma revízia %s"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "Nemáte oprávnenie na zobrazenie tejto stránky"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:55
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Zmeny na repozitáre %s"
 
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
+#: kallithea/controllers/files.py:92
 msgid "Click here to add new file"
 msgstr "Kliknite pre pridanie nového súboru"
 
-#: kallithea/controllers/files.py:92
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr "Zatiaľ nie sú žiadne súbory. %s"
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr "Zmazaný súbor %s cez Kallithea"
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr "Úspešne zmazaný súbor %s"
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "Došlo k chybe pri ukladaní"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "Žiadne zmeny"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr "Pridaný súbor cez Kallithea"
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "Žiadny obsah"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr "Sťahovanie vypnuté"
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Neznáma revízia %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "Zmeny"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "Vetvy"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "Tagy"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr "Skupiny"
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "Repozitáre"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr "Vetva"
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr "Záložka"
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr ""
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 #, fuzzy
 msgid "Bad captcha"
 msgstr "zlá captcha"
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr ""
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr "Úspešne aktualizované heslo"
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr "%s (zatvorené)"
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "Záložky"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:363
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:352
 msgid "Successfully opened new pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-msgid "Error occurred while creating pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
 #, python-format
 msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
-msgid "Pull request updated"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:529
-msgid "Successfully deleted pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:595
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:597
-msgid "This pull request has been closed and can not be updated."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr "Došlo k chybe počas vyhľadávania."
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -468,149 +482,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 minút"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 minút"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 hodina"
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr "1 hodina"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 deň"
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr "1 deň"
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 mesiac"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr "Došlo k chybe pri vytváraní gist"
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr "Došlo k chybe pri aktualizácii gist %s"
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr "Došlo k chybe pri aktualizácii hesla užívateľa"
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "Došlo k chybe pri ukladaní e-mailovej adresy"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -618,623 +634,621 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "Error occurred during gist creation"
+msgid "An error occurred during creation of field: %r"
+msgstr "Došlo k chybe pri vytváraní gist"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr "Nič"
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr "Nič"
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 #, fuzzy
 msgid "Repository has been locked"
 msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 #, fuzzy
 msgid "Repository has been unlocked"
 msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid ""
-"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
-"missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Watched Repositories"
 msgid "Invalidated %s repositories"
 msgstr "Repozitáre"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "Zmeny"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1242,7 +1256,7 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1250,7 +1264,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1258,7 +1272,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1266,7 +1280,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1274,7 +1288,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1282,7 +1296,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1290,1120 +1304,613 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "Repozitáre"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 #, fuzzy
-#| msgid "Non-admins can can fork repositories"
 msgid "Non-admins can fork repositories"
 msgstr "Repozitáre"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
-#, fuzzy
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
-#, fuzzy
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "Odblokovať repozitár"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2411,146 +1918,129 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2559,30 +2049,16 @@
 msgstr[2] "%s záznamov"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2590,7 +2066,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2599,109 +2075,109 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid ""
-"Private repositories are only visible to people explicitly added as "
-"collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2710,45 +2186,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2757,7 +2233,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2780,16 +2257,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2798,45 +2275,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2846,184 +2323,199 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -3031,105 +2523,68 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr "Vaše repozitáre"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr ""
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3143,168 +2598,174 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "Repozitáre"
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid ""
-"Note: This will also give all users API access to create repositories "
-"everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
-#, fuzzy, python-format
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3313,30 +2774,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3361,12 +2821,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3378,16 +2838,34 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "Neznáma revízia %s"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3395,103 +2873,96 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
-#, fuzzy
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr ""
+#, fuzzy, python-format
+#| msgid "Repository Defaults"
+msgid "Repository group %s"
+msgstr "Repozitáre"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
@@ -3520,26 +2991,26 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3548,63 +3019,61 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr "Odblokovať repozitár"
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
-msgstr "Odblokovať repozitár"
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 msgid "Confirm to lock repository."
 msgstr "Potvrdenie blokovania repozitára."
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 msgid "Lock Repository"
 msgstr "Zablokovať repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3612,33 +3081,33 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr "Vymazať tento repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
-#, fuzzy, python-format
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3649,110 +3118,112 @@
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Go to tip of repository"
+msgid "Fork of repository"
+msgstr "Prázdny repozitár"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "Zmeny na repozitáre %s"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "Potvrdenie blokovania repozitára."
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3762,35 +3233,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3806,7 +3282,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3827,7 +3303,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3839,277 +3315,267 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+msgid "Custom Hooks"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-msgid "Custom Hooks"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "Repozitáre"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4126,92 +3592,100 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "Repositories"
+msgid "Repository page size"
+msgstr "Repozitáre"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4230,13 +3704,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4245,15 +3719,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4267,17 +3741,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4296,12 +3770,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4310,7 +3784,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4319,24 +3793,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4344,223 +3810,220 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr ""
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "Repozitáre"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4569,150 +4032,142 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-#, fuzzy
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-#, fuzzy
-msgid "Open New Pull Request from {0}"
-msgstr "Zmena stavu"
+msgid "Add Another Comment"
+msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
+#, fuzzy
+msgid "Open New Pull Request from {0}"
+msgstr "Zmena stavu"
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "Neznáma revízia %s"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4720,80 +4175,32 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "Prázdny repozitár"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4809,7 +4216,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4823,39 +4230,60 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "Prázdny repozitár"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "Zmeny"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "No comments."
+msgid "%s comments"
+msgstr "Nie sú žiadne komentáre."
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4863,60 +4291,50 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -4924,9 +4342,9 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -4934,18 +4352,19 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
+#: kallithea/templates/changeset/changeset_file_comment.html:20
 #, fuzzy
-msgid "No revisions"
-msgstr "Neznáma revízia %s"
+#| msgid "No comments."
+msgid "comment"
+msgstr "Nie sú žiadne komentáre."
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -4962,61 +4381,61 @@
 msgstr "%s zmien"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#, fuzzy
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "Posledné zmeny"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "Zmeny"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "Žiadne zmeny"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "Zmena stavu"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "(zatvorené)"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
@@ -5024,15 +4443,15 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
@@ -5040,104 +4459,100 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "Zmeny"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "Žiadne zmeny"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "(zatvorené)"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "Neznáma revízia %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, fuzzy, python-format
 msgid "%s changesets"
 msgstr "%s zmien"
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -5148,20 +4563,20 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -5169,107 +4584,159 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Set changeset status"
+msgid "Comment on Changeset \"%s\""
+msgstr "Zmeny"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changesets"
+msgid "Changeset on"
+msgstr "Zmeny"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "Vetva"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "Posledné zmeny"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "Tento repozitár bol uzamknutý používateľom %s dňa %s"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "on pull request"
+msgid "Pull request"
+msgstr "Zmena stavu"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "on pull request"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "Zmena stavu"
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5288,7 +4755,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5298,73 +4765,74 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "Žiadne zmeny"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5374,7 +4842,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5387,24 +4855,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5413,50 +4877,58 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5477,12 +4949,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5491,35 +4963,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5529,40 +5001,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5571,31 +5043,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5607,6 +5084,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5630,7 +5111,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5643,7 +5124,7 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
 msgstr ""
 
@@ -5657,108 +5138,95 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "Zatiaľ nie sú reviewers"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
-#, fuzzy
-msgid "Remove reviewer"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
-msgid "Type name of reviewer to add"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
-#, fuzzy
-msgid "Potential Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
-msgid "Click to add the repository owner as reviewer:"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
 msgid "Save Changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
 msgid "Cancel Changes"
 msgstr "Zrušiť zmeny"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5766,7 +5234,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Open New Pull Request from {0}"
 msgid "Pull Requests from '%s'"
 msgstr "Zmena stavu"
 
@@ -5775,35 +5242,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5817,29 +5288,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5849,80 +5320,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5930,449 +5401,90 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Posledné zmeny"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "Prázdny repozitár"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
-#~ msgid "increase diff context to %(num)s lines"
-#~ msgstr ""
-
-#~ msgid "No comments."
-#~ msgstr "Nie sú žiadne komentáre."
-
-#~ msgid "public journal"
-#~ msgstr ""
-
-#~ msgid "journal"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Cannot delete %s it still contains attached forks"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked"
-#~ msgstr ""
-
-#~ msgid "Locked"
-#~ msgstr ""
-
-#~ msgid "Repository has been %s"
-#~ msgstr ""
-
-#~ msgid "You can't edit this user"
-#~ msgstr ""
-
-#~ msgid "compare view"
-#~ msgstr ""
-
-#~ msgid "fork name %s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s"
-#~ msgstr ""
-
-#~ msgid "No Files"
-#~ msgstr ""
-
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
-#~ msgid "You can't remove this user since it's crucial for entire application"
-#~ msgstr ""
-
-#~ msgid "Username \"%(username)s\" is forbidden"
-#~ msgstr ""
-
-#~ msgid "invalid password"
-#~ msgstr ""
-
-#~ msgid "invalid user name"
-#~ msgstr ""
-
-#~ msgid "Your account is disabled"
-#~ msgstr ""
-
-#~ msgid "Repository name %(repo)s is disallowed"
-#~ msgstr ""
-
-#~ msgid "invalid clone URL"
-#~ msgstr ""
-
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "This email address is already taken"
-#~ msgstr ""
-
-#~ msgid "email \"%(email)s\" does not exist."
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
-#~ msgid "Defaults"
-#~ msgstr ""
-
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
-#~ msgid "Watched"
-#~ msgstr ""
-
-#~ msgid "My Permissions"
-#~ msgstr ""
-
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
-#~ msgid "delete"
-#~ msgstr ""
-
-#~ msgid "current IP"
-#~ msgstr ""
-
-#~ msgid "Permissions Administration"
-#~ msgstr ""
-
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Repository creation"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
-#~ msgid "Default User Permissions Overview"
-#~ msgstr ""
-
-#~ msgid "none"
-#~ msgstr ""
-
-#~ msgid "read"
-#~ msgstr ""
-
-#~ msgid "write"
-#~ msgstr ""
-
-#~ msgid "admin"
-#~ msgstr ""
-
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "default"
-#~ msgstr ""
-
-#~ msgid "revoke"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr ""
-
-#~ msgid "private repository"
-#~ msgstr ""
-
-#~ msgid "Remote URL"
-#~ msgstr ""
-
-#~ msgid "Pull Changes from Remote Location"
-#~ msgstr ""
-
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
-#~ msgid "edit"
-#~ msgstr ""
-
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
-#~ msgid "Default permissions"
-#~ msgstr ""
-
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "merge"
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Comment from pull request"
-#~ msgstr ""
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
-#~ msgid "Mimetype"
-#~ msgstr ""
+#, fuzzy
+#~| msgid "Repository URL"
+#~ msgid "Repository Group"
+#~ msgstr "Repozitáre"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "Zatiaľ nie sú reviewers"
 
 #~ msgid "My Repos"
 #~ msgstr "Moje repozitáre"
-
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
-#~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid "Your password reset link was sent"
-#~ msgstr ""
-
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changeset __S"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
-#~ msgid "Created by"
-#~ msgstr ""
-
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "Changeset not found"
-#~ msgstr ""
-
-#~ msgid "Pull Requests from %s'"
-#~ msgstr ""
-
--- a/kallithea/i18n/uk/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/uk/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3.2\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2018-03-31 21:42+0000\n"
 "Last-Translator: Максим Якимчук <xpinovo@gmail.com>\n"
 "Language-Team: Ukrainian <https://hosted.weblate.org/projects/kallithea/"
@@ -16,440 +16,458 @@
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
-"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
 "X-Generator: Weblate 2.20-dev\n"
 
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "Нічого"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "Відображати пробіли"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "Ігнорувати пробіли"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+msgid "No permission to change status"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:214
+#, python-format
+msgid "Successfully deleted pull request %s"
+msgstr ""
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid "Changing status on a changeset associated with a closed pull request is not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr ""
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr ""
+
 #: kallithea/controllers/error.py:71
-msgid "No response"
-msgstr ""
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
 msgstr "Невідома помилка"
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr ""
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr ""
 
-#: kallithea/controllers/error.py:109
-msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
-msgstr ""
-
-#: kallithea/controllers/feed.py:55
+#: kallithea/controllers/error.py:93
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
 
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
-msgstr ""
-
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr ""
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr ""
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr ""
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr ""
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr ""
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr ""
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr ""
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr ""
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr ""
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr ""
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "Журнал"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr ""
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr ""
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:152
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:174
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
 msgid "No description"
 msgstr "Без опису"
 
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-msgid "Error occurred while creating pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid "This pull request is based on another %s revision and there is no simple diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:529
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr ""
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, python-format
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr ""
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
@@ -461,149 +479,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 хвилин"
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr "5 хвилин"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 день"
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr "1 день"
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr "1 місяць"
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
-#: kallithea/templates/base/perms_summary.html:15
-msgid "Read"
-msgstr ""
-
 #: kallithea/controllers/admin/permissions.py:63
 #: kallithea/controllers/admin/permissions.py:67
 #: kallithea/controllers/admin/permissions.py:71
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
-#: kallithea/templates/base/perms_summary.html:16
-msgid "Write"
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
+#: kallithea/templates/base/perms_summary.html:15
+msgid "Read"
 msgstr ""
 
 #: kallithea/controllers/admin/permissions.py:64
 #: kallithea/controllers/admin/permissions.py:68
 #: kallithea/controllers/admin/permissions.py:72
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
+#: kallithea/templates/base/perms_summary.html:16
+msgid "Write"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -611,622 +631,624 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 msgid "Repository has been locked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid "Unable to activate hgsubversion support. The \"hgsubversion\" library is missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, python-format
 msgid "Invalidated %s repositories"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
+#: kallithea/lib/auth.py:824
+msgid "You need to be a registered user to perform this action"
+msgstr ""
+
+#: kallithea/lib/auth.py:852
+msgid "You need to be signed in to view this page"
+msgstr ""
+
+#: kallithea/lib/base.py:444
 msgid "Invalid API key"
 msgstr ""
 
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
-msgid "You need to be a registered user to perform this action"
-msgstr ""
-
-#: kallithea/lib/auth.py:864
-msgid "You need to be signed in to view this page"
-msgstr ""
-
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr ""
 
-#: kallithea/lib/helpers.py:610
-#, python-format
-msgid "Deleted branch: %s"
-msgstr ""
-
 #: kallithea/lib/helpers.py:612
 #, python-format
+msgid "Deleted branch: %s"
+msgstr ""
+
+#: kallithea/lib/helpers.py:614
+#, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, python-format
 msgid "Changeset %s not found"
 msgstr ""
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr ""
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr ""
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
-#, python-format
-msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
-msgstr ""
-
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/helpers.py:1264
+#, python-format
+msgid ""
+"%s repository is not mapped to db perhaps it was created or renamed from "
+"the filesystem please run the application again in order to rescan "
+"repositories"
+msgstr ""
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
@@ -1234,7 +1256,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
@@ -1242,7 +1264,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
@@ -1250,7 +1272,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
@@ -1258,7 +1280,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
@@ -1266,7 +1288,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
@@ -1274,1098 +1296,610 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr ""
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 msgid "Non-admins can fork repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr ""
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr ""
 
-#: kallithea/model/forms.py:57
-msgid "Please enter a login"
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
 msgstr ""
 
 #: kallithea/model/forms.py:58
+msgid "Please enter a login"
+msgstr ""
+
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
-msgid "Please enter a password"
-msgstr ""
-
 #: kallithea/model/forms.py:67
+msgid "Please enter a password"
+msgstr ""
+
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr ""
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
-msgstr ""
-
-#: kallithea/model/user.py:261
-#, python-format
-msgid "User \"%s\" still owns %s repositories and cannot be removed. Switch owners or remove those repositories: %s"
-msgstr ""
-
-#: kallithea/model/user.py:266
-#, python-format
-msgid "User \"%s\" still owns %s repository groups and cannot be removed. Switch owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
-#, python-format
-msgid "User \"%s\" still owns %s user groups and cannot be removed. Switch owners or remove those user groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
+msgstr ""
+
+#: kallithea/model/user.py:258
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories: %s"
+msgstr ""
+
+#: kallithea/model/user.py:263
+#, python-format
+msgid ""
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
+#, python-format
+msgid ""
+"User \"%s\" still owns %s user groups and cannot be removed. Switch "
+"owners or remove those user groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
-#, python-format
-msgid "The password to your account %s has been changed using password reset form."
-msgstr ""
-
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/user.py:414
+#, python-format
+msgid ""
+"The password to your account %s has been changed using password reset "
+"form."
+msgstr ""
+
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
-msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+#: kallithea/model/validators.py:78
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
-msgid "user group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
-msgstr ""
-
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:136
+msgid ""
+"user group name may only contain alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr ""
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr ""
 
+#: kallithea/model/validators.py:312
+msgid "Token mismatch"
+msgstr ""
+
+#: kallithea/model/validators.py:328
+#, python-format
+msgid "Repository name %(repo)s is not allowed"
+msgstr ""
+
+#: kallithea/model/validators.py:330
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
 #: kallithea/model/validators.py:331
-msgid "Token mismatch"
-msgstr ""
-
-#: kallithea/model/validators.py:345
-#, python-format
-msgid "Repository name %(repo)s is not allowed"
-msgstr ""
-
-#: kallithea/model/validators.py:347
-#, python-format
-msgid "Repository named %(repo)s already exists"
-msgstr ""
-
-#: kallithea/model/validators.py:348
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr ""
 
-#: kallithea/model/validators.py:466
-msgid "Invalid repository URL. It must be a valid http, https, ssh, svn+http or svn+https URL"
-msgstr ""
-
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:420
+msgid ""
+"Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
+"svn+https URL"
+msgstr ""
+
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr ""
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 msgid "This email address is already in use"
 msgstr ""
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
-msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to \"username\""
-msgstr ""
-
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:719
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr ""
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr ""
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr ""
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr ""
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr ""
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr ""
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr ""
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr ""
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr ""
 
@@ -2373,142 +1907,129 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
 #, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 msgid "Reset Your Password"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
-msgid "A password reset link will be sent to the specified email address if it is registered in the system."
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:19
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
+msgid ""
+"A password reset link will be sent to the specified email address if it "
+"is registered in the system."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
-msgid "Note that you must use the same browser session for this as the one used to request the password reset."
-msgstr ""
-
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:24
+msgid ""
+"Note that you must use the same browser session for this as the one used "
+"to request the password reset."
+msgstr ""
+
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 msgid "New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 msgid "Confirm New Password"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr ""
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr ""
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr ""
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr ""
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr ""
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 msgid "Admin Journal"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 msgid "Filter"
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
@@ -2517,30 +2038,16 @@
 msgstr[2] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr ""
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr ""
 
@@ -2548,7 +2055,7 @@
 msgid "From IP"
 msgstr ""
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2557,104 +2064,108 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
-msgid "Comma-separated list of plugins; Kallithea will try user authentication in plugin order"
-msgstr ""
-
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:32
+msgid ""
+"Comma-separated list of plugins; Kallithea will try user authentication "
+"in plugin order"
+msgstr ""
+
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 msgid "Repository Defaults"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr ""
 
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
+msgid "Private repository"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
+msgid ""
+"Private repositories are only visible to people explicitly added as "
+"collaborators."
+msgstr ""
+
 #: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
-msgid "Private repository"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
-msgid "Private repositories are only visible to people explicitly added as collaborators."
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
+msgid "Enable statistics"
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
+msgid "Enable downloads"
 msgstr ""
 
 #: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
-msgid "Enable statistics"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
-msgid "Enable statistics window on summary page."
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
-msgid "Enable downloads"
-msgstr ""
-
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2663,43 +2174,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
-#, python-format
-msgid "Gist was update since you started editing. Copy your changes and click %(here)s to reload new version."
-msgstr ""
-
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:35
+#, python-format
+msgid ""
+"Gist was update since you started editing. Copy your changes and click "
+"%(here)s to reload new version."
+msgstr ""
+
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 msgid "Never"
 msgstr ""
 
@@ -2708,7 +2221,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2731,16 +2245,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2749,45 +2263,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr ""
 
@@ -2797,182 +2311,197 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 msgid "Owned Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 msgid "Watched Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
 msgid "Show Permissions"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 msgid "Remove"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -2980,105 +2509,68 @@
 msgid "Change Your Account Password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, python-format
+msgid "Change %s avatar at"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 msgid "Repositories You Own"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 msgid "Repositories You are Watching"
 msgstr ""
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 msgid "Default Permissions"
 msgstr ""
 
@@ -3092,151 +2584,173 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
-#, python-format
-msgid "Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
-msgid "All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+msgid "Allow anonymous access"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
+#, python-format
+msgid ""
+"Allow access to Kallithea without needing to log in. Anonymous users use "
+"%s user permissions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
+msgid ""
+"All default permissions on each repository will be reset to chosen "
+"permission, note that all custom default permission on repositories will "
+"be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 msgid "Apply to all existing repositories"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
+msgid "Permissions for the Default user on new repositories."
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:27
-msgid "Permissions for the Default user on new repositories."
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
-msgid "All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost"
+msgid ""
+"All default permissions on each repository group will be reset to chosen "
+"permission, note that all custom default permission on repository groups "
+"will be lost"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
+msgid "Apply to all existing repository groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
+msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:40
-msgid "Apply to all existing repository groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
-msgid "Permissions for the Default user on new repository groups."
+#: kallithea/templates/data_table/_dt_elements.html:141
+msgid "User group"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
+msgid ""
+"All default permissions on each user group will be reset to chosen "
+"permission, note that all custom default permission on user groups will "
+"be lost"
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
-msgid "User group"
+msgid "Apply to all existing user groups"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
+msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
 #: kallithea/templates/admin/permissions/permissions_globals.html:53
-msgid "All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
-msgid "Apply to all existing user groups"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
-msgid "Permissions for the Default user on new user groups."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
 msgid "Top level repository creation"
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
+msgid ""
+"Note: This will also give all users API access to create repositories "
+"everywhere. That might change in future versions."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
+msgid "Repository creation with group write access"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
-msgid "Note: This will also give all users API access to create repositories everywhere. That might change in future versions."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
-msgid "Repository creation with group write access"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
-msgid "With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+msgid ""
+"With this, write permission to a repository group allows creating "
+"repositories inside that group. Without this, group write permissions "
+"mean nothing."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
+msgid "Repository forking"
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
+msgid "Enable this to allow non-admins to fork repositories."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
+msgid "Registration"
+msgstr ""
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:88
-msgid "Repository forking"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
-msgid "Enable this to allow non-admins to fork repositories."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
-msgid "Registration"
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
@@ -3245,30 +2759,29 @@
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr ""
@@ -3293,12 +2806,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3310,16 +2823,32 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+msgid "Visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+msgid "Add repos"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 msgid "Default"
@@ -3327,96 +2856,97 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
-msgid "Set or revoke permission to all children of that group, including non-private repositories and other groups if selected."
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
-msgid "Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
+msgid ""
+"Set or revoke permission to all children of that group, including non-"
+"private repositories and other groups if selected."
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
+msgid ""
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
 #, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
+msgid "Repository group %s"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 msgid "Repository Groups Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 msgid "Number of Top-level Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 msgid "Clone remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
-msgid "Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:16
+msgid ""
+"Optional: URL of a remote repository. If set, the repository will be "
+"created as a clone from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
-msgid "Default revision for files page, downloads, full text search index and readme generation"
+#: kallithea/templates/admin/repos/repo_add_base.html:52
+msgid ""
+"Default revision for files page, downloads, full text search index and "
+"readme generation"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
@@ -3430,11 +2960,15 @@
 
 #: kallithea/templates/admin/repos/repo_creating.html:27
 #, python-format
-msgid "Repository \"%(repo_name)s\" is being created, you will be redirected when this process is finished.repo_name"
+msgid ""
+"Repository \"%(repo_name)s\" is being created, you will be redirected "
+"when this process is finished.repo_name"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:39
-msgid "We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator."
+msgid ""
+"We're sorry but error occurred during this operation. Please check your "
+"Kallithea server logs, or contact administrator."
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
@@ -3442,22 +2976,22 @@
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr ""
 
@@ -3466,75 +3000,79 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 msgid "Public Journal Visibility"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 msgid "Add to Public Journal"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
-msgid "All actions done in this repository will be visible to everyone in the public journal."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
+msgid ""
+"All actions done in this repository will be visible to everyone in the "
+"public journal."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 msgid "Change Locking"
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
+msgid "Confirm to unlock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
+msgid "Unlock Repository"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:52
-msgid "Confirm to unlock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
-msgid "Unlock Repository"
+#, python-format
+msgid "Locked by %s on %s"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:56
-#, python-format
-msgid "Locked by %s on %s"
+msgid "Confirm to lock repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
+msgid "Lock Repository"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:60
-msgid "Confirm to lock repository."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
-msgid "Lock Repository"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
-msgid "Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
+msgid ""
+"Force locking on the repository. Works only when anonymous access is "
+"disabled. Triggering a pull locks the repository.  The user who is "
+"pulling locks the repository; only the user who pulled and locked it can "
+"unlock it by doing a push."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 msgid "Delete this Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
 #, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
@@ -3542,150 +3080,165 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
-msgid "The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it."
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
+msgid ""
+"The deleted repository will be moved away and hidden until the "
+"administrator expires it. The administrator can both permanently delete "
+"it or restore it."
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:4
 msgid "Invalidate Repository Cache"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-msgid "Confirm to invalidate repository cache."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
-msgid "Manually invalidate cache for this repository. On first access, the repository will be cached again."
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
+msgid ""
+"Manually invalidate cache for this repository. On first access, the "
+"repository will be cached again."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 msgid "Private Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+msgid "Fork of repository"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 msgid "Remote repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 msgid "Pull Changes from Remote Repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
-msgid "Confirm to pull changes from remote repository."
-msgstr ""
-
 #: kallithea/templates/admin/repos/repo_edit_remote.html:17
+msgid "Confirm to pull changes from remote repository."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "Permanent Repository ID"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
-"In case this repository is renamed or moved into another group the repository URL changes.\n"
-"                               Using the above permanent URL guarantees that this repository always will be accessible on that URL.\n"
-"                               This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+"In case this repository is renamed or moved into another group the "
+"repository URL changes.\n"
+"                               Using the above permanent URL guarantees "
+"that this repository always will be accessible on that URL.\n"
+"                               This is useful for CI systems, or any "
+"other cases that you need to hardcode the URL into a 3rd party service."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 msgid "Remote repository"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 msgid "Repository URL"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
-msgid "Optional: URL of a remote repository. If set, the repository can be pulled from this URL."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
+msgid ""
+"Optional: URL of a remote repository. If set, the repository can be "
+"pulled from this URL."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3701,7 +3254,7 @@
 msgid "Repositories Administration"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3722,7 +3275,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3734,336 +3287,374 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
-msgid "HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
+msgid ""
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
-msgid "Private key for reCaptcha system. Setting this value will enable captcha on registration."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:43
+msgid ""
+"Private key for reCaptcha system. Setting this value will enable captcha "
+"on registration."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 msgid "Save Settings"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
-msgid "Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_hooks.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:17
 msgid "Custom Hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:18
+msgid ""
+"Hooks can be used to trigger actions on certain events such as push / "
+"pull. They can trigger Python functions or external applications."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+msgid "Rescan options"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
-msgid "Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem."
+#: kallithea/templates/admin/settings/settings_mapping.html:12
+msgid ""
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid "Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid "If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
-msgid "This option completely reindexeses all of the repositories for proper fulltext search capabilities."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:21
+msgid ""
+"This option completely reindexeses all of the repositories for proper "
+"fulltext search capabilities."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid "Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 msgid "Mercurial extensions"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_vcs.html:38
+msgid "Enable largefiles extension"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:44
+msgid "Enable hgsubversion extension"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_vcs.html:47
-msgid "Enable largefiles extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:51
-msgid "Enable hgsubversion extension"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:53
-msgid "Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial."
+msgid ""
+"Requires hgsubversion library to be installed. Enables cloning of remote "
+"Subversion repositories while converting them to Mercurial."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:59
+msgid "Location of repositories"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_vcs.html:64
-msgid "Location of repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:69
-msgid "Click to unlock. You must restart Kallithea in order to make this setting take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
-msgid "Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:8
+msgid ""
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
+msgid ""
+"Filesystem location where repositories are stored. After changing this "
+"value, a restart and rescan of the repository folder are both required."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
-"                                                        The following variables of the URL will be replaced accordingly.\n"
-"                                                        {scheme}    'http' or 'https' sent from running Kallithea server,\n"
-"                                                        {email}     user email,\n"
-"                                                        {md5email}  md5 hash of the user email (like at gravatar.com),\n"
-"                                                        {size}      size of the image that is expected from the server application,\n"
-"                                                        {netloc}    network location/server host of running Kallithea server"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:42
+"                                                        The following "
+"variables of the URL will be replaced accordingly.\n"
+"                                                        {scheme}    "
+"'http' or 'https' sent from running Kallithea server,\n"
+"                                                        {email}     user "
+"email,\n"
+"                                                        {md5email}  md5 "
+"hash of the user email (like at gravatar.com),\n"
+"                                                        {size}      size "
+"of the image that is expected from the server application,\n"
+"                                                        {netloc}    "
+"network location/server host of running Kallithea server"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+msgid "Clone URL"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.\n"
-"                                                        The following variables are available:\n"
-"                                                        {scheme} 'http' or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current user username,\n"
-"                                                        {netloc} network location/server host of running Kallithea server,\n"
-"                                                        {repo}   full repository name,\n"
-"                                                        {repoid} ID of repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
-msgid "Number of items displayed in the main page dashboard before pagination is shown."
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
+"variables are available:\n"
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
+"location/server host of running Kallithea server,\n"
+"                                                    {repo}   full "
+"repository name,\n"
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+msgid "Repository page size"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
+msgid ""
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
-msgid "Number of items displayed in the admin pages grids before pagination is shown."
+msgid ""
+"Number of items displayed in the admin pages grids before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:70
+msgid "Icons"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:75
-msgid "Icons"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:80
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 msgid "Meta Tagging"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
+#: kallithea/templates/admin/settings/settings_visual.html:94
+msgid ""
+"Parses meta tags from the repository description field and turns them "
+"into colored tags."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:98
 msgid "Stylify recognised meta tags:"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:111
-msgid "Parses meta tags from the repository description field and turns them into colored tags."
-msgstr ""
-
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4082,13 +3673,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4097,15 +3688,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr ""
 
@@ -4119,17 +3710,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4148,12 +3739,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4162,7 +3753,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4171,24 +3762,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4196,220 +3779,219 @@
 msgid "Users Administration"
 msgstr ""
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr ""
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr ""
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr ""
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr ""
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr ""
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr ""
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
-#: kallithea/templates/forks/fork.html:9
-msgid "Fork"
-msgstr ""
-
 #: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/forks/fork.html:9
+msgid "Fork"
+msgstr ""
+
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
-msgid "Show recent activity"
-msgstr ""
-
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
-msgid "Public journal"
-msgstr ""
-
-#: kallithea/templates/base/base.html:233
-msgid "Show public gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:234
-msgid "Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:238
-msgid "All Public Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:240
-msgid "My Public Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:241
-msgid "My Private Gists"
-msgstr ""
-
-#: kallithea/templates/base/base.html:246
-msgid "Search in repositories"
-msgstr ""
-
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
-msgid "My Pull Requests"
-msgstr ""
-
-#: kallithea/templates/base/base.html:289
-msgid "Not Logged In"
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
 msgstr ""
 
 #: kallithea/templates/base/base.html:296
+msgid "Show recent activity"
+msgstr ""
+
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
+msgid "Public journal"
+msgstr ""
+
+#: kallithea/templates/base/base.html:308
+msgid "Show public gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:309
+msgid "Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:313
+msgid "All Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:315
+msgid "My Public Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:316
+msgid "My Private Gists"
+msgstr ""
+
+#: kallithea/templates/base/base.html:321
+msgid "Search in repositories"
+msgstr ""
+
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:6
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:10
+msgid "My Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:360
+msgid "Not Logged In"
+msgstr ""
+
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
-msgstr ""
-
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:379
+msgid "Forgot password?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:383
+msgid "Don't have an account?"
+msgstr ""
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr ""
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 msgid "Inherit defaults"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:19
-#, python-format
-msgid "Select to inherit global settings, IP whitelist and permissions from the %s."
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:15
+#, python-format
+msgid ""
+"Select to inherit global settings, IP whitelist and permissions from the "
+"%s."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr ""
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr ""
 
@@ -4418,148 +4000,140 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr ""
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr ""
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr ""
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
+msgid "Unable to post"
 msgstr ""
 
 #: kallithea/templates/base/root.html:31
-msgid "Open New Pull Request from {0}"
+msgid "Add Another Comment"
 msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
+msgid "Stop following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
+msgid "Start following this repository"
 msgstr ""
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
+msgid "Group"
 msgstr ""
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
-msgid "Collapse Diff"
+msgid "Loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:36
-msgid "Expand Diff"
+msgid "loading ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:37
-msgid "Failed to revoke permission"
+msgid "Search truncated"
 msgstr ""
 
 #: kallithea/templates/base/root.html:38
-msgid "Confirm to revoke permission for {0}: {1} ?"
+msgid "No matching files"
 msgstr ""
 
 #: kallithea/templates/base/root.html:39
-msgid "enabled"
+msgid "Open New Pull Request from {0}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:40
-msgid "disabled"
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
 msgstr ""
 
 #: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
+msgid "Collapse Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:44
+msgid "Expand Diff"
+msgstr ""
+
+#: kallithea/templates/base/root.html:45
+msgid "No revisions"
+msgstr ""
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
+msgid "Failed to revoke permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:48
+msgid "Confirm to revoke permission for {0}: {1} ?"
+msgstr ""
+
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
@@ -4567,79 +4141,31 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 msgid "Go to tip of repository"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr ""
 
@@ -4655,7 +4181,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4669,38 +4195,59 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-msgid "Push new repository"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, python-format
+msgid "%s comments"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4708,60 +4255,50 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 msgid "Merge"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
@@ -4769,9 +4306,9 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
@@ -4779,16 +4316,16 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-msgid "No revisions"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+msgid "comment"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
@@ -4807,56 +4344,53 @@
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 msgid "Status change"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+msgid "No change"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+msgid "Finish pull request"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
@@ -4864,7 +4398,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
@@ -4872,7 +4406,7 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
@@ -4880,101 +4414,100 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-msgid "Set changeset status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-msgid "No change"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr ""
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+msgid "File before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:40
+msgid "Modified"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:42
+msgid "Deleted"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:44
+msgid "Renamed"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown error"
+msgid "Unknown operation: %r"
+msgstr "Невідома помилка"
+
+#: kallithea/templates/changeset/diff_block.html:52
+msgid "No file after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:55
+msgid "File after"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:60
 #: kallithea/templates/files/diff_2way.html:43
 msgid "Show full diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
 msgid "Show full side-by-side diff for this file"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:38
+#: kallithea/templates/changeset/diff_block.html:72
 msgid "Show inline comments"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:86
-msgid "Deleted"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:89
-msgid "Renamed"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
+#: kallithea/templates/compare/compare_cs.html:55
 #, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 msgid "behind"
 msgstr ""
 
@@ -4985,20 +4518,20 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
@@ -5006,103 +4539,149 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+msgid "Changeset on"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+msgid "branch"
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Latest Changes"
+msgid "Status change:"
+msgstr "Останні зміни"
+
+#: kallithea/templates/email_templates/comment.html:33
+msgid "The pull request has been closed."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
-msgid "Should you not be able to use the link above, please type the following code into the password reset form"
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:12
-msgid "If it weren't you who requested the password reset, just disregard this message."
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/password_reset.html:33
+msgid ""
+"Should you not be able to use the link above, please type the following "
+"code into the password reset form"
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:44
+msgid ""
+"If it weren't you who requested the password reset, just disregard this "
+"message."
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+msgid "Pull request"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Pull Request %s \"%s\" Closed"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, python-format
+msgid "Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/registration.html:22
+msgid "Full Name"
 msgstr ""
 
 #: kallithea/templates/files/diff_2way.html:15
@@ -5121,7 +4700,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5131,72 +4710,73 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
+msgid "New file type"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 msgid "Commit Changes"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 msgid "Last Modified"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 msgid "Last Committer"
 msgstr ""
 
@@ -5206,7 +4786,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5219,24 +4799,20 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
@@ -5245,50 +4821,58 @@
 msgstr[1] ""
 msgstr[2] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:69
+msgid "File is too big to display."
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
+msgid "Show as raw."
 msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
@@ -5309,12 +4893,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr ""
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5323,35 +4907,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5361,40 +4945,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:21
-msgid "ATOM journal feed"
-msgstr ""
-
 #: kallithea/templates/journal/journal.html:22
+msgid "ATOM journal feed"
+msgstr ""
+
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5403,31 +4987,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5439,6 +5028,10 @@
 msgid "Vote"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5462,7 +5055,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5489,103 +5082,95 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 msgid "Summarize the changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-msgid "There are no reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 msgid "on"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
-msgid "This is just a range of changesets and doesn't have a target or a real merge ancestor."
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
+msgid ""
+"This is just a range of changesets and doesn't have a target or a real "
+"merge ancestor."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-msgid "Update"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+msgid "Next iteration"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+msgid "Cancel Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 msgid "Remove reviewer"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 msgid "Potential Reviewers"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-msgid "Cancel Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 msgid "Pull Request Content"
 msgstr ""
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5601,35 +5186,39 @@
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5643,29 +5232,29 @@
 msgid "Search in All Repositories"
 msgstr ""
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr ""
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr ""
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr ""
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr ""
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr ""
 
@@ -5675,80 +5264,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "файли"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr "Показати більше"
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5756,82 +5345,77 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "Клонувати з"
 
-#: kallithea/templates/summary/summary.html:72
-msgid "Clone URL"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:78
-msgid "Show by Name"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:79
+#: kallithea/templates/summary/summary.html:68
 msgid "Show by ID"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:73
+msgid "Show by Name"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 msgid "Latest Changes"
 msgstr "Останні зміни"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 msgid "Quick Start"
 msgstr "Швидкий старт"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+msgid "Push new repository"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
-
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
--- a/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -9,619 +9,633 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
-"PO-Revision-Date: 2014-12-12 14:19+0200\n"
-"Last-Translator: Michal Čihař <michal@cihar.com>\n"
-"Language-Team: Simplified Chinese "
-"<https://hosted.weblate.org/projects/kallithea/kallithea/zh_CN/>\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
+"PO-Revision-Date: 2018-04-12 03:35+0000\n"
+"Last-Translator: Pheng Heong Tan <phtan90@gmail.com>\n"
+"Language-Team: Chinese (Simplified) <https://hosted.weblate.org/projects/"
+"kallithea/kallithea/zh_Hans/>\n"
 "Language: zh_CN\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=1; plural=0;\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"X-Generator: Weblate 2.20\n"
+"Generated-By: Babel 1.3\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr "还没有修订集"
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "无"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
-msgstr ""
-
-#: kallithea/controllers/changeset.py:89
+msgstr "(已关闭)"
+
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
-msgstr ""
-
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+msgstr "显示空白"
+
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
-msgstr ""
-
-#: kallithea/controllers/changeset.py:169
+msgstr "忽略空白"
+
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
-msgstr ""
-
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+msgstr "增加差异上下文到 %(num)s 行"
+
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+#| msgid "Set changeset status"
+msgid "No permission to change status"
+msgstr "设置修订集状态"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "成功删除拉取请求"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
-msgstr ""
-
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+msgstr "在此代码库内,此修改并不存在"
+
+#: kallithea/controllers/compare.py:66
+#, python-format
+msgid "Could not find other repository %s"
+msgstr "找不到那个版本库 %s"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
+#: kallithea/controllers/error.py:70
+msgid "No response"
+msgstr "无响应"
+
 #: kallithea/controllers/error.py:71
-#, fuzzy
-msgid "No response"
-msgstr "修订"
-
-#: kallithea/controllers/error.py:72
 msgid "Unknown error"
-msgstr ""
-
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
+msgstr "未知错误"
+
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
 msgstr "由于错误的语法,服务器无法对请求进行响应。"
 
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr "未授权的资源访问"
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "无权访问该页面"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "资源未找到"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr "服务进入非预期的混乱状态,这会阻止它对请求进行响应。"
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "%s库的修改"
-
-#: kallithea/controllers/feed.py:56
+#: kallithea/controllers/feed.py:63
+#, python-format
+msgid "%s committed on %s"
+msgstr ""
+
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr "修订集太大并已被截断..."
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
 #, python-format
 msgid "%s %s feed"
 msgstr "%s %s订阅"
 
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr "修订集太大已被截断......"
-
-#: kallithea/controllers/feed.py:91
-#, python-format
-msgid "%s committed on %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
-msgstr ""
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "%s库的修改"
 
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr "点击这里添加新文件"
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:193
-#, fuzzy, python-format
+msgstr "这里还没有文件。%s"
+
+#: kallithea/controllers/files.py:194
+#, python-format
 msgid "%s at %s"
-msgstr "%s零%s"
-
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+msgstr "%s 在 %s"
+
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr "版本库由%s于%s锁定"
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
-msgstr ""
-
-#: kallithea/controllers/files.py:328
+msgstr "您只能删除有效分支的修订中的文件"
+
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:350
+msgstr "删除文件 %s 通过 Kallithea"
+
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
-msgstr ""
-
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+msgstr "成功删除文件 %s"
+
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr "提交时发生错误"
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
-msgstr ""
-
-#: kallithea/controllers/files.py:391
+msgstr "您只能编辑有效分支的修订中的文件"
+
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:407
+msgstr "已编辑文件 %s 通过 Kallithea"
+
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "无变更"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "成功提交到%s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
-msgstr ""
-
-#: kallithea/controllers/files.py:464
+msgstr "已添加文件通过 Kallithea"
+
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr "无内容"
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr "无文件名"
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
-msgstr ""
-
-#: kallithea/controllers/files.py:537
+msgstr "下载已禁用"
+
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "未知版本%s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "空版本库"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "未知包类型"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "修订集"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "分支"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "标签"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr "在复刻版本库%s的时候发生错误"
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
-msgstr ""
-
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+msgstr "组"
+
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "版本库"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
-msgstr ""
-
-#: kallithea/controllers/home.py:136
+msgstr "分支"
+
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr "已关闭分支"
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
-msgstr ""
-
-#: kallithea/controllers/home.py:142
+msgstr "标签"
+
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
-msgstr ""
-
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+msgstr "书签"
+
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "公共日志"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "日志"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
-msgstr ""
-
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
-#, fuzzy
+msgstr "验证码错误"
+
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr "您已成功注册 %s"
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
-msgstr "密码重置链接已经发送"
-
-#: kallithea/controllers/login.py:246
-#, fuzzy
+msgstr "密码重置确认码已经发送"
+
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
-msgstr "密码重置链接已经发送"
-
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+msgstr "无效的密码重置令牌"
+
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:124
+msgstr "成功更新密码"
+
+#: kallithea/controllers/pullrequests.py:71
+#, python-format
+msgid "Invalid reviewer \"%s\" specified"
+msgstr "指定的审核者 \"%s\" 无效"
+
+#: kallithea/controllers/pullrequests.py:133
 #, python-format
 msgid "%s (closed)"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:152
+msgstr "%s (已关闭)"
+
+#: kallithea/controllers/pullrequests.py:160
 #: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
 msgid "Changeset"
 msgstr "修订集"
 
-#: kallithea/controllers/pullrequests.py:173
+#: kallithea/controllers/pullrequests.py:181
 msgid "Special"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:174
+msgstr "特殊"
+
+#: kallithea/controllers/pullrequests.py:182
 msgid "Peer branches"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
+msgstr "同等分支"
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
 msgid "Bookmarks"
 msgstr "书签"
 
-#: kallithea/controllers/pullrequests.py:310
+#: kallithea/controllers/pullrequests.py:320
 #, python-format
 msgid "Error creating pull request: %s"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-#, fuzzy
-msgid "No description"
-msgstr "描述"
-
-#: kallithea/controllers/pullrequests.py:363
+msgstr "创建拉取请求出错:%s"
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
+msgid "Error occurred while creating pull request"
+msgstr "创建拉取请求时发生错误"
+
+#: kallithea/controllers/pullrequests.py:352
 msgid "Successfully opened new pull request"
 msgstr "成功提交拉取请求"
 
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
-#, python-format
-msgid "Invalid reviewer \"%s\" specified"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
-#, fuzzy
-msgid "Error occurred while creating pull request"
-msgstr "提交拉取请求时发生错误"
-
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
-#, python-format
-msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-#, fuzzy
-msgid "Pull request update created"
-msgstr "拉取请求检视人员"
-
-#: kallithea/controllers/pullrequests.py:514
-#, fuzzy
+#: kallithea/controllers/pullrequests.py:375
+#, fuzzy
+#| msgid "Pull request update created"
+msgid "New pull request iteration created"
+msgstr "拉取请求更新已创建"
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "无描述"
+
+#: kallithea/controllers/pullrequests.py:432
 msgid "Pull request updated"
-msgstr "拉取请求"
-
-#: kallithea/controllers/pullrequests.py:529
+msgstr "拉取请求已更新"
+
+#: kallithea/controllers/pullrequests.py:445
 msgid "Successfully deleted pull request"
 msgstr "成功删除拉取请求"
 
-#: kallithea/controllers/pullrequests.py:595
+#: kallithea/controllers/pullrequests.py:481
+#, fuzzy, python-format
+#| msgid "Changeset for %s %s not found in %s"
+msgid "Revision %s not found in %s"
+msgstr "未找到修订集"
+
+#: kallithea/controllers/pullrequests.py:508
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr "没有找到更新此拉取请求的修订集。"
+
+#: kallithea/controllers/pullrequests.py:522
 #, python-format
 msgid "This pull request has already been merged to %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:597
+#: kallithea/controllers/pullrequests.py:524
 msgid "This pull request has been closed and can not be updated."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+#, fuzzy
+#| msgid "No changesets found for updating this pull request."
+msgid "No additional changesets found for iterating on this pull request."
+msgstr "没有找到更新此拉取请求的修订集。"
+
+#: kallithea/controllers/pullrequests.py:557
 #, python-format
 msgid "Note: Branch %s has another head: %s."
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-#, fuzzy
-msgid "Closing."
-msgstr "使用中"
-
-#: kallithea/controllers/search.py:135
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
+#, fuzzy, python-format
+#| msgid "No changesets found for updating this pull request."
+msgid ""
+"Error: some changesets not found when displaying pull request from %s."
+msgstr "没有找到更新此拉取请求的修订集。"
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "错误的搜索。请尝试用引号包含它。"
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "没有索引用于搜索。请运行whoosh索引器"
-
-#: kallithea/controllers/search.py:144
-#, fuzzy
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
-msgstr "在搜索操作中发生异常"
-
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
-#, fuzzy
+msgstr "搜索操作期间发生错误。"
+
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
-msgstr "数据未加载"
-
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+msgstr "数据尚未就绪"
+
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "该版本库统计功能已经禁用"
 
 #: kallithea/controllers/admin/auth_settings.py:135
 msgid "Auth settings updated successfully"
-msgstr ""
+msgstr "验证设置更新成功"
 
 #: kallithea/controllers/admin/auth_settings.py:146
 msgid "error occurred during update of auth settings"
-msgstr ""
-
-#: kallithea/controllers/admin/defaults.py:97
+msgstr "验证设置更新时发生错误"
+
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr "默认设置已经成功更新"
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
+msgstr "默认值更新时发生错误"
+
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
 #, fuzzy
 msgid "Forever"
 msgstr "检视者"
 
+#: kallithea/controllers/admin/gists.py:59
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
+msgstr "5 分钟"
+
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
+msgstr "1 小时"
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
-msgstr ""
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
+msgstr "1 天"
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+msgstr "1 个月"
+
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:146
+msgstr "终身"
+
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:184
+msgstr "gist 创建时发生错误"
+
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
-msgstr ""
+msgstr "已删除 gist %s"
+
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr "未修改"
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr "成功更新 gist 内容"
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr "成功更新 gist 数据"
 
 #: kallithea/controllers/admin/gists.py:233
-#, fuzzy
-msgid "Unmodified"
-msgstr "最后修改于"
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+msgstr "gist %s 更新时发生错误"
+
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr "由于是系统帐号,无法编辑该用户"
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "你的帐号已经更新完成"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:178
+msgstr "用户 %s 更新时发生错误"
+
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+msgstr "用户密码更新时发生错误"
+
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr "已为用户添加电子邮件 %s"
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr "保存电子邮件时发生错误"
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr "成功删除用户电子邮件"
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr "API 密钥创建成功"
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr "API 密钥重置成功"
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+msgstr "API 密钥删除成功"
+
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "读"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "写"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -629,1791 +643,1302 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "管理"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "停用"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "停用"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr "已允许手动激活账号"
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr "已允许自动激活账号"
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr "外部账号手动激活"
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr "外部账号自动激活"
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "启用"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:139
+msgstr "全局权限更新成功"
+
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
-msgstr ""
-
-#: kallithea/controllers/admin/repo_groups.py:188
+msgstr "权限更新时发生错误"
+
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr "这个组内有%s个版本库因而无法删除"
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "版本库%s成功更新"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, fuzzy, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr "无法删除%s因为它还有其他分复刻本库"
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "在删除%s的时候发生错误"
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, fuzzy, python-format
+#| msgid "An error occurred during deletion of user"
+msgid "An error occurred during creation of field: %r"
+msgstr "删除用户时发生错误"
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:446
 msgid "Updated repository visibility in public journal"
 msgstr "成功更新在公共日志中的可见性"
 
-#: kallithea/controllers/admin/repos.py:495
+#: kallithea/controllers/admin/repos.py:450
 msgid "An error occurred during setting this repository in public journal"
 msgstr "设置版本库到公共日志时发生错误"
 
-#: kallithea/controllers/admin/repos.py:512
+#: kallithea/controllers/admin/repos.py:466
 msgid "Nothing"
 msgstr "无"
 
-#: kallithea/controllers/admin/repos.py:514
+#: kallithea/controllers/admin/repos.py:468
 #, python-format
 msgid "Marked repository %s as fork of %s"
 msgstr "成功将版本库%s标记为复刻自%s"
 
-#: kallithea/controllers/admin/repos.py:521
+#: kallithea/controllers/admin/repos.py:475
 msgid "An error occurred during this operation"
 msgstr "在搜索操作中发生错误"
 
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
+#: kallithea/controllers/admin/repos.py:491
+#: kallithea/controllers/admin/repos.py:512
 #, fuzzy
 msgid "Repository has been locked"
 msgstr "版本库未锁定"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 #, fuzzy
 msgid "Repository has been unlocked"
 msgstr "版本库未锁定"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr "解锁时发生错误"
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr "清除缓存时发生错误"
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr "成功拉取自远程路径"
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr "从远程路径拉取时发生错误"
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr "删除版本库统计时发生错误"
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr "成功更新版本控制系统设置"
 
-#: kallithea/controllers/admin/settings.py:174
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
 msgid ""
 "Unable to activate hgsubversion support. The \"hgsubversion\" library is "
 "missing"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
 msgid "Error occurred while updating application settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:211
+#: kallithea/controllers/admin/settings.py:174
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
-#| msgid "Invalidate Repository Cache"
 msgid "Invalidated %s repositories"
 msgstr "清除版本库缓存"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "更新应用设置"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr "成功更新可视化设置"
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+#, fuzzy
+#| msgid "No data ready yet"
+msgid "Hook already exists"
+msgstr "数据尚未就绪"
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "新建钩子"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "更新钩子"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh重新索引任务调度"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr "保存权限时发生错误"
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "用户更新成功"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr "删除用户时发生错误"
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
-msgid "Invalid API key"
-msgstr ""
-
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
+#: kallithea/lib/auth.py:824
 msgid "You need to be a registered user to perform this action"
 msgstr "必须是注册用户才能进行此操作"
 
-#: kallithea/lib/auth.py:864
+#: kallithea/lib/auth.py:852
 msgid "You need to be signed in to view this page"
 msgstr "必须登录才能访问该页面"
 
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, fuzzy, python-format
-#| msgid "Changeset not found"
 msgid "Changeset for %s %s not found in %s"
 msgstr "未找到修订集"
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr "修订集因过大而被截断,可查看原始修订集作为替代"
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr "修订集过大并已被截断,使用差异菜单查看此差异"
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "未发现差异"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr "已经删除分支%s"
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr "创建标签%s"
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
-#| msgid "Changeset not found"
 msgid "Changeset %s not found"
 msgstr "未找到修订集"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
-msgstr "显示合并的修订集%s->%s"
-
-#: kallithea/lib/helpers.py:678
+msgstr "显示所有合并的修订集 %s->%s"
+
+#: kallithea/lib/helpers.py:680
 #, fuzzy
 msgid "Compare view"
 msgstr "比较显示"
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "还有"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr "%s个"
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "修订"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, fuzzy, python-format
 msgid "Fork name %s"
 msgstr "复刻名称%s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, fuzzy, python-format
 msgid "Pull request %s"
 msgstr "拉取请求#%s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr "[删除]版本库"
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr "[创建]版本库"
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr "[创建]复刻版本库"
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr "[复刻]版本库"
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr "[更新]版本库"
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr "[删除]版本库"
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr "[创建]用户"
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr "[更新]用户"
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr "[评论]了版本库中的修订"
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr "[评论]拉取请求"
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr "[关闭] 拉取请求"
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr "[推送]到"
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr "[通过Kallithea提交]到版本库"
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr "[远程拉取]到版本库"
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr "[拉取]自"
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr "[开始关注]版本库"
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr "[停止关注]版本库"
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr " 还有%s个"
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr "无文件"
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
 "the filesystem please run the application again in order to rescan "
 "repositories"
-msgstr "版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启Kallithea以重新扫描版本库"
-
-#: kallithea/lib/utils2.py:415
+msgstr ""
+"版本库%s没有映射到数据库,可能是从文件系统创建或者重命名,请重启Kallithea"
+"以重新扫描版本库"
+
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] "%d年"
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] "%d月"
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] "%d天"
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] "%d时"
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] "%d分"
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] "%d秒"
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr "%s"
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr "%s前"
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr "%s零%s"
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr "%s零%s前"
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "刚才"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr "无版本库访问权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr "版本库读取权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr "版本库写入权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr "版本库管理权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr "无版本库组访问权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr "版本库组读取权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr "版本库组写入"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr "版本库组管理权限"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr "Kallithea 管理员"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr "禁用创建版本库"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr "允许创建版本库"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr "禁用复刻版本库"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr "允许复刻版本库"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr "禁用注册"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr "用手动激活注册新用户"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr "用自动激活注册新用户"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr "未检视"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr "已批准"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr "驳回"
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr "检视中"
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr "在%s行"
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr "[提及]"
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr "Kallithea 管理员"
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 #, fuzzy
 msgid "Default user has read access to new repositories"
 msgstr "未授权的资源访问"
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 #, fuzzy
 msgid "Default user has write access to new repositories"
 msgstr "未授权的资源访问"
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 #, fuzzy
 msgid "Only admins can create repository groups"
 msgstr "没有在该版本库组中创建版本库的权限"
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 #, fuzzy
 msgid "Non-admins can create repository groups"
 msgstr "没有在该版本库组中创建版本库的权限"
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 #, fuzzy
 msgid "Only admins can fork repositories"
 msgstr "创建版本库"
 
-#: kallithea/model/db.py:1696
-#, fuzzy
-#| msgid "Non-admins can can fork repositories"
+#: kallithea/model/db.py:1733
+#, fuzzy
 msgid "Non-admins can fork repositories"
 msgstr "创建版本库"
 
-#: kallithea/model/db.py:1699
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 #, fuzzy
 msgid "Not reviewed"
 msgstr "未检视"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 #, fuzzy
 msgid "Under review"
 msgstr "检视中"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+#, fuzzy
+#| msgid "Approved"
+msgid "Not approved"
+msgstr "已批准"
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr "已批准"
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "请登录"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "输入一个不少于%(min)i个字符的值"
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "请输入密码"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "输入少于%(min)i个字符"
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, fuzzy, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr "成功提交拉取请求"
-
-#: kallithea/model/notification.py:260
+#: kallithea/model/notification.py:165
 #, fuzzy, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr "[评论]拉取请求"
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#| msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr "[评论] %(repo_name)s 修订集 %(short_id)s 在 %(branch)s"
+
+#: kallithea/model/notification.py:168
 #, fuzzy, python-format
 msgid "New user %(new_username)s registered"
 msgstr "用户名称 %(new_username)s 无效"
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, fuzzy, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr "[评论]拉取请求"
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 #, fuzzy
 msgid "Closing"
 msgstr "使用中"
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+#, fuzzy
+#| msgid "Error creating pull request: %s"
+msgid "Cannot create empty pull request"
+msgstr "创建拉取请求出错:%s"
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+#, fuzzy
+#| msgid "Confirm to delete this pull request"
+msgid "You are not authorized to create the pull request"
+msgstr "确认删除拉取请求"
+
+#: kallithea/model/pull_request.py:341
+#, fuzzy
+#| msgid "Missing changesets since the previous pull request:"
+msgid "Missing changesets since the previous iteration:"
+msgstr "缺少上次拉取请求之后的修订集:"
+
+#: kallithea/model/pull_request.py:348
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr "在上次拉取请求之后,在 %s %s 上的新修订集:"
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, fuzzy, python-format
+#| msgid "New changesets on %s %s since the previous pull request:"
+msgid "No changes found on %s %s since previous iteration."
+msgstr "在上次拉取请求之后,在 %s %s 上的新修订集:"
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr "最新tip版本"
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-#, fuzzy
-msgid "You can't remove this user since it is crucial for the entire application"
+#: kallithea/model/user.py:253
+#, fuzzy
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr "由于是系统帐号,无法删除该用户"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
-msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s"
-
-#: kallithea/model/user.py:266
+msgstr ""
+"由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
+"库。%s"
+
+#: kallithea/model/user.py:263
 #, fuzzy, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s"
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+"由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
+"库。%s"
+
+#: kallithea/model/user.py:270
 #, fuzzy, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
-msgstr "由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本库。%s"
-
-#: kallithea/model/user.py:360
+msgstr ""
+"由于用户 \"%s\" 拥有版本库%s因而无法删除,请修改版本库所有者或删除版本"
+"库。%s"
+
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 #, fuzzy
 msgid "Password reset notification"
 msgstr "确认密码"
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr "值不能为空"
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr "用户名称%(username)s已经存在"
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, fuzzy, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr "用户名称 %(username)s 无效"
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:133
+#: kallithea/model/validators.py:112
 #, python-format
 msgid "Username %(username)s is not valid"
 msgstr "用户名称 %(username)s 无效"
 
-#: kallithea/model/validators.py:152
+#: kallithea/model/validators.py:133
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: kallithea/model/validators.py:193
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr "不能将这个组作为parent"
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr "组 \"%(group_name)s\" 已经存在"
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr "已经存在名为 \"%(group_name)s\" 的版本库"
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr "密码含有无效(非ASCII)字符"
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "密码不符"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 #, fuzzy
 msgid "Invalid username or password"
 msgstr "无效密码"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr "令牌不匹配"
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, fuzzy, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr "版本库名称不能为%(repo)s"
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr "已经存在版本库%(repo)s"
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "版本库组 \"%(group)s\" 中已经存在版本库 \"%(repo)s\""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 #, fuzzy
 msgid "Invalid repository URL"
 msgstr "私有版本库"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr "复刻版本库必须和父版本库类型相同"
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr "没有在该版本库组中创建版本库的权限"
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "不是一个合法的路径"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "该邮件地址已被使用"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, fuzzy, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr "邮件地址\"%(email)s\"不存在"
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr "LDAP 登陆属性的 CN 必须指定 - 这个名字作为用户名"
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "控制面板"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "快速过滤..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "版本库"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "版本库"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "描述"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "名称"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr "最后修改"
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr "Tip"
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "所有者"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr "点击以升序排列"
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr "点击以降序排列"
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr "数据错误"
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr "载入中..."
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr "登录"
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "帐号"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "密码"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr "记住密码"
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "忘记了密码?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "还没有帐号?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "登录"
 
@@ -2421,183 +1946,152 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
-#, fuzzy, python-format
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 #, fuzzy
 msgid "Reset Your Password"
 msgstr "忘记了密码?"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
+#: kallithea/templates/password_reset.html:47
 #, fuzzy
 msgid "Send Password Reset Email"
 msgstr "确认新密码"
 
-#: kallithea/templates/password_reset.html:47
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr "密码重置地址已经发送到邮件"
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
 msgid "New Password"
 msgstr "新密码"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
 msgid "Confirm New Password"
 msgstr "新密码"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "注册"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "确认密码"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "名"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "姓"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "电子邮件"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "没有任何分支"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "没有任何标签"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr "无书签"
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 #, fuzzy
 msgid "Admin Journal"
 msgstr "系统日志"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr "日志过滤..."
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 #, fuzzy
 msgid "Filter"
 msgstr "过滤"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/templates/journal/journal.html:13
 #, fuzzy, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
 msgstr[0] "%s条"
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "操作"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "版本库"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "日期"
 
@@ -2605,7 +2099,7 @@
 msgid "From IP"
 msgstr "来源IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr "无操作"
 
@@ -2614,156 +2108,156 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "保存"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "版本库默认设置"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "类型"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "私有版本库"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
 msgstr "私有版本库只对成员可见。"
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "启用统计"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
-msgstr "启用概况页的统计窗口"
-
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+msgstr "启用概况页面上的统计窗口。"
+
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "启用下载"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
-msgstr "启用概况页的下载菜单"
-
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+msgstr "启用概况页面上的下载菜单。"
+
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr "启用锁定"
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
-msgstr "启用版本库的拉取锁定"
+msgstr "启用版本库的拉取锁定。"
 
 #: kallithea/templates/admin/gists/edit.html:5
 #: kallithea/templates/admin/gists/edit.html:18
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "检视者"
@@ -2773,7 +2267,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2796,16 +2291,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2814,45 +2309,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "重置"
 
@@ -2862,113 +2357,111 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "删除"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "我的账户"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 #, fuzzy
 msgid "Email Addresses"
 msgstr "增加邮箱"
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "版本库"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "创建版本库"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
@@ -2976,73 +2469,90 @@
 msgid "Show Permissions"
 msgstr "拷贝权限"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, fuzzy, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr "确认删除用户:%s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "确认删除用户:%s"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 #, fuzzy
 msgid "Remove"
 msgstr "移除"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr "增加"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr "确认删除邮箱:%s"
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr "增加邮箱"
 
@@ -3051,109 +2561,71 @@
 msgid "Change Your Account Password"
 msgstr "忘记了密码?"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "新密码"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
 msgstr "修改你的头像"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "使用中"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 #, fuzzy
 msgid "Repositories You Own"
 msgstr "版本库"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr "没有找到记录"
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "名称"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 #, fuzzy
 msgid "Repositories You are Watching"
 msgstr "版本库路径"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr "我的通知"
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr "全部"
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr "评论"
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-#, fuzzy
-msgid "Mark All Read"
-msgstr "全部标记为已读"
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr "无通知"
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-#, fuzzy
-msgid "Show Notification"
-msgstr "显示通知"
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr "通知"
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 #, fuzzy
 msgid "Default Permissions"
 msgstr "默认权限"
@@ -3168,202 +2640,209 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "匿名访问"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "匿名访问"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "已有版本库?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "版本库组"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "版本库组"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 #, fuzzy
 msgid "Apply to all existing repository groups"
 msgstr "已有版本库?"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "建立版本库"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr "版本库复刻"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "注册"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
 #, fuzzy, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr "确认删除用户:%s"
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "组名"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "上级组"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "设置"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "权限"
@@ -3388,12 +2867,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr "创建于"
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3403,16 +2882,36 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "Disabled"
+msgid "Visible"
+msgstr "停用"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "无响应"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3421,114 +2920,108 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 #, fuzzy
 msgid "Revoke"
 msgstr "移除"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 #, fuzzy
 msgid "Apply to children"
 msgstr "应用到成员"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
 msgstr "启用组的拉取锁定。这个选项将应用到组内的其他组和版本库"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 #, fuzzy
 msgid "Confirm to delete this group"
 msgstr "确认删除用户:%s"
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "首页"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr "有"
+#, fuzzy, python-format
+#| msgid "Repository group"
+msgid "Repository group %s"
+msgstr "版本库组"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 #, fuzzy
 msgid "Repository Groups Administration"
 msgstr "版本库管理员"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 #, fuzzy
 msgid "Number of Top-level Repositories"
 msgstr "顶层版本库数量"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "[创建]版本库"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
 msgstr "保持简短。用README文件来写更长的描述。"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
-msgstr "可选的,选择一个组将版本库放到其中"
-
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+msgstr "可选的选择一个组将版本库放到其中。"
+
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
-msgstr "要创建的版本库类型"
-
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+msgstr "要创建的版本库类型。"
+
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr "默认修订"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
-#, fuzzy, python-format
+#, python-format
 msgid "%s Creating Repository"
 msgstr ""
 
@@ -3550,26 +3043,26 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "远程"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "统计"
 
@@ -3579,72 +3072,70 @@
 msgstr "上级组"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 #, fuzzy
 msgid "Manually set this repository as a fork of another from the list."
 msgstr "从列表中手动设置这个版本库复刻自另一版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 #, fuzzy
 msgid "Public Journal Visibility"
 msgstr "公共日志"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "从公共日志删除"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 #, fuzzy
 msgid "Add to Public Journal"
 msgstr "公共日志"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 #, fuzzy
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr "任何人都可以在公共日志上看到这个版本库上的所有动作"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 #, fuzzy
 msgid "Change Locking"
 msgstr "启用锁定"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 #, fuzzy
 msgid "Confirm to unlock repository."
 msgstr "确认解锁版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 #, fuzzy
 msgid "Unlock Repository"
 msgstr "公共版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, fuzzy
 msgid "Confirm to lock repository."
 msgstr "确认锁定版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 #, fuzzy
 msgid "Lock Repository"
 msgstr "公共版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr "版本库未锁定"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3652,32 +3143,32 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr "确认删除版本库:%s"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 #, fuzzy
 msgid "Delete this Repository"
 msgstr "[删除]版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
-#, fuzzy, python-format
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3689,114 +3180,115 @@
 msgid "Invalidate Repository Cache"
 msgstr "清除版本库缓存"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-#, fuzzy
-msgid "Confirm to invalidate repository cache."
-msgstr "确认清除版本库缓存"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 #, fuzzy
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr "手动清除版本库缓存。之后第一次访问的时候将重建缓存"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:12
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
 #, fuzzy
 msgid "List of Cached Values"
 msgstr "缓存值列表"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
+#: kallithea/templates/admin/repos/repo_edit_caches.html:12
 msgid "Prefix"
 msgstr "前缀"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr "键"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "启用"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 #, fuzzy
 msgid "Extra fields are disabled."
 msgstr "禁用注册"
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "私有版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "[forked] repository"
+msgid "Fork of repository"
+msgstr "[复刻]版本库"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "[创建]版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "[远程拉取]到版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "确认从远程拉取修订集"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "私有版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3806,35 +3298,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "[创建]版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "版本库"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr "文件浏览、下载、whoosh和README的默认修订版本"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
-msgstr "修改这个版本库的所有者"
+msgstr "修改这个版本库的所有者。"
+
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3853,7 +3350,7 @@
 msgid "Repositories Administration"
 msgstr "版本库管理员"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3875,7 +3372,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr "钩子"
 
@@ -3887,281 +3384,273 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr "发送"
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 #, fuzzy
 msgid "Save Settings"
 msgstr "保存设置"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+#, fuzzy
+msgid "Custom Hooks"
+msgstr "自定义钩子"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-#, fuzzy
-msgid "Custom Hooks"
-msgstr "自定义钩子"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "移除钩子失败"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Description"
+msgid "Rescan options"
+msgstr "描述"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "重新索引"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
+msgid "Kallithea configuration file"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:9
+msgid "Python version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
 #, fuzzy
 msgid "Git version"
 msgstr "编辑权限"
 
-#: kallithea/templates/admin/settings/settings_system.html:9
+#: kallithea/templates/admin/settings/settings_system.html:12
 msgid "Git path"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr "网络"
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "推送后显示版本库大小"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "记录用户推送命令"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "记录用户拉取命令"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "推送后更新版本库(hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 #, fuzzy
 msgid "Mercurial extensions"
 msgstr "Mercurial扩展"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "创建版本库"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr "通用"
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4178,94 +3667,103 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+#, fuzzy
+msgid "Clone URL"
+msgstr "克隆地址"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "repositories"
+msgid "Repository page size"
+msgstr "版本库"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr "图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr "显示公共版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:84
+#: kallithea/templates/admin/settings/settings_visual.html:81
 msgid "Show private repository icon on repositories"
 msgstr "显示私有版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:86
+#: kallithea/templates/admin/settings/settings_visual.html:84
 #, fuzzy
 msgid "Show public/private icons next to repository names."
 msgstr "显示公共版本库图标"
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "元标记"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4285,13 +3783,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "成员"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4300,15 +3798,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr "还没有成员"
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "启用成员"
 
@@ -4323,17 +3821,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "用户"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr "确认密码"
 
@@ -4352,12 +3850,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4366,7 +3864,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr "确认删除用户:%s"
@@ -4375,24 +3873,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr "确认新密码"
 
@@ -4401,227 +3891,228 @@
 msgid "Users Administration"
 msgstr "用户管理员"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Mercurial版本库"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Git版本库"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 #, fuzzy
 msgid "Create Fork"
 msgstr "创建于"
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "概况"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "修订记录"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "浏览"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "选项"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 #, fuzzy
 msgid "Compare Fork"
 msgstr "比较复刻"
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr "比较显示"
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "搜索"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "复刻"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "公共日志"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 #, fuzzy
 msgid "My Pull Requests"
 msgstr "拉取请求"
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 #, fuzzy
 msgid "Login to Your Account"
 msgstr "登录"
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "忘记密码?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "还没有帐号?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "退出"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "版本库默认设置"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "创建版本库"
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr "复刻版本库"
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "显示"
 
@@ -4630,235 +4121,181 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr "权限"
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr "编辑权限"
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
+#: kallithea/templates/base/root.html:28
+msgid "Retry"
+msgstr ""
+
+#: kallithea/templates/base/root.html:29
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+#, fuzzy
+msgid "Submitting ..."
+msgstr "提交中……"
+
+#: kallithea/templates/base/root.html:30
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "启用下载"
+
+#: kallithea/templates/base/root.html:31
 #, fuzzy
 msgid "Add Another Comment"
 msgstr "%d条评论"
 
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
+#: kallithea/templates/base/root.html:32
 msgid "Stop following this repository"
 msgstr "停止关注该版本库"
 
-#: kallithea/templates/base/root.html:24
+#: kallithea/templates/base/root.html:33
 msgid "Start following this repository"
 msgstr "开始关注该版本库"
 
-#: kallithea/templates/base/root.html:25
+#: kallithea/templates/base/root.html:34
 msgid "Group"
 msgstr "组"
 
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "成员"
-
-#: kallithea/templates/base/root.html:27
+#: kallithea/templates/base/root.html:35
 msgid "Loading ..."
 msgstr ""
 
-#: kallithea/templates/base/root.html:28
+#: kallithea/templates/base/root.html:36
 msgid "loading ..."
 msgstr ""
 
-#: kallithea/templates/base/root.html:29
+#: kallithea/templates/base/root.html:37
 msgid "Search truncated"
 msgstr ""
 
-#: kallithea/templates/base/root.html:30
+#: kallithea/templates/base/root.html:38
 msgid "No matching files"
 msgstr ""
 
-#: kallithea/templates/base/root.html:31
+#: kallithea/templates/base/root.html:39
 #, fuzzy
 msgid "Open New Pull Request from {0}"
 msgstr "[评论]拉取请求"
 
-#: kallithea/templates/base/root.html:32
+#: kallithea/templates/base/root.html:40
 msgid "Open New Pull Request for {0} &rarr; {1}"
 msgstr ""
 
-#: kallithea/templates/base/root.html:33
-#, fuzzy
+#: kallithea/templates/base/root.html:41
 msgid "Show Selected Changesets {0} &rarr; {1}"
-msgstr "显示合并的修订集%s->%s"
-
-#: kallithea/templates/base/root.html:34
+msgstr "显示选中的修订集 {0} &rarr; {1}"
+
+#: kallithea/templates/base/root.html:42
 #, fuzzy
 msgid "Selection Link"
 msgstr "选择链接"
 
-#: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 #, fuzzy
 msgid "Collapse Diff"
 msgstr "文件差异"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 #, fuzzy
 msgid "Expand Diff"
 msgstr "文件差异"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "修订"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr ""
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr ""
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:42
-#, fuzzy
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr "选择修订集"
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
-msgstr "%s修订集"
-
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr "%s书签"
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr "作者"
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "修订"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr "%s分支"
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+msgstr "指定修订集"
+
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr "点击以升序排列"
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr "点击以降序排列"
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr "没有找到记录。"
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr "数据错误。"
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr "载入中..."
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr "%s修订记录"
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] "显示%d/%d个版本"
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr "清除选择"
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "确认锁定版本库"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 #, fuzzy
 msgid "Branch filter:"
 msgstr "过滤"
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "没有任何变更"
 
@@ -4874,7 +4311,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4888,129 +4325,144 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr "通过Kallithea直接添加或者上传文件"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "推送新版本库"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
-msgstr "已有版本库?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr "此列表中首个(最旧)修订集"
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr "此列表中末个(最近)修订集"
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr "修订集在此列表中的位置"
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+"修订集状态:%s 由 %s\n"
+"点击打开相关联的拉取请求 %s"
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, python-format
+msgid "Changeset status: %s by %s"
+msgstr "修订集状态:%s 由 %s"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%d comment"
+#| msgid_plural "%d comments"
+msgid "%s comments"
+msgstr "%d条评论"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
+msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
 #, python-format
 msgid "%s Changeset"
-msgstr "%s修订集"
-
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+msgstr "%s 修订集"
+
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr "修订集状态"
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "合并"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 #, fuzzy
 msgid "Grafted from:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 #, fuzzy
 msgid "Replaced by:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 #, fuzzy
 msgid "Preceded by:"
 msgstr "创建于"
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] "修改%s个文件"
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] "修改%s个文件包括%s行插入和%s行删除"
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
-#, fuzzy
-msgid "No revisions"
-msgstr "修订"
+#: kallithea/templates/changeset/changeset_file_comment.html:20
+#, fuzzy
+#| msgid "Comment"
+msgid "comment"
+msgstr "评论"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 #, fuzzy
@@ -5023,186 +4475,187 @@
 msgstr "无文件"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:24
-#, fuzzy
 msgid "on this changeset"
-msgstr "无修订"
+msgstr "在此修订集"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
 #, fuzzy
 msgid "Delete comment?"
 msgstr "%d条评论"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "文件已更改"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr "在{1}行上评论"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr "评论使用%s语法并支持%s"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-#, fuzzy
-msgid "Use @username inside this text to notify another user"
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+#, fuzzy
+msgid "Comments are in plain text. Use @username to notify another user."
 msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-#, fuzzy
-msgid "Submitting ..."
-msgstr "提交中……"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+msgid "Set changeset status"
+msgstr "设置修订集状态"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "无变更"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "[评论]拉取请求"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+#, fuzzy
+msgid "Close"
+msgstr "已关闭"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr "评论"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
-msgstr "必须登录才能评论"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+msgstr "您必须登录才能评论。"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr "现在登陆"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr "隐藏"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] "%d条评论"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
+#: kallithea/templates/changeset/changeset_file_comment.html:154
 #, fuzzy, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] "(%d内嵌)"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:155
+#, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-#, fuzzy
-msgid "Use @username inside this text to notify another user."
-msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "修订集状态"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "无变更"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-#, fuzzy
-msgid "Close"
-msgstr "已关闭"
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
-msgstr "%s修订集"
-
-#: kallithea/templates/changeset/changeset_range.html:56
+msgstr "%s 修订集"
+
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr "修订集状态:%s"
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr "影响文件"
 
-#: kallithea/templates/changeset/diff_block.html:21
-#: kallithea/templates/files/diff_2way.html:43
-msgid "Show full diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
-msgid "Show full side-by-side diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:38
-msgid "Show inline comments"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:86
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "文件已删除"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "未修改"
+
+#: kallithea/templates/changeset/diff_block.html:42
 #, fuzzy
 msgid "Deleted"
 msgstr "删除"
 
-#: kallithea/templates/changeset/diff_block.html:89
+#: kallithea/templates/changeset/diff_block.html:44
 #, fuzzy
 msgid "Renamed"
 msgstr "读"
 
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "未知版本%s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "No filename"
+msgid "No file after"
+msgstr "无文件名"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "文件已添加"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
-msgstr "无修订"
-
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+msgstr "无修订集"
+
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
-#, fuzzy, python-format
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
 msgid "%s changesets"
-msgstr "%s修订集"
-
-#: kallithea/templates/compare/compare_cs.html:100
+msgstr "%s 修订集"
+
+#: kallithea/templates/compare/compare_cs.html:56
 #, fuzzy
 msgid "behind"
 msgstr "重新索引"
@@ -5214,129 +4667,185 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] "显示%s个提交"
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "公共版本库"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
-msgstr "无修订"
-
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+msgstr "尚无任何修订集"
+
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "订阅%s的RSS"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "订阅%s的Atom"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr "%(user)s 已评论修订集在 %(age)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "%(user)s commented on changeset %(age)s"
+msgid "Comment on Changeset \"%s\""
+msgstr "%(user)s 已评论修订集在 %(age)s"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changeset"
+msgid "Changeset on"
+msgstr "修订集"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branch"
+msgid "branch"
+msgstr "分支"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "文件已更改"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "版本库未锁定"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 #, fuzzy
 msgid "We have received a request to reset the password for your account."
 msgstr "我们收到重置你用户密码的请求。"
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
+#: kallithea/templates/email_templates/pull_request.html:4
 #, fuzzy, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, fuzzy, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#| msgid "[commented] on pull request for"
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr "[评论]拉取请求"
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+msgid "Pull request"
+msgstr "拉取请求#%s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, fuzzy, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-#, fuzzy
-msgid "The comment closed the pull request with status"
+#| msgid "%(user)s commented on pull request %(age)s"
+msgid "Mention in Comment on Pull Request %s \"%s\""
 msgstr "[评论]拉取请求"
 
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr "查看用户"
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s from %s#%s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "拉取请求#%s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "[commented] on pull request for"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "[评论]拉取请求"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "组名"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5354,7 +4863,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5364,74 +4873,76 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "位置"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr "或者"
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr ""
+#, fuzzy
+msgid "New file type"
+msgstr "未知包类型"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "提交修改"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "加载文件列表..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "大小"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr "最后修订号"
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 #, fuzzy
 msgid "Last Modified"
 msgstr "最后修改于"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 #, fuzzy
 msgid "Last Committer"
 msgstr "最后提交者"
@@ -5442,7 +4953,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5455,75 +4966,81 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr "编辑文件"
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
 msgid_plural "%s authors"
 msgstr[0] "%s个作者"
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, fuzzy, python-format
+#| msgid "Deleted branch: %s"
+msgid "Edit on Branch: %s"
+msgstr "已经删除分支%s"
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "二进制文件(%s)"
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "文件过大,不能显示"
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "文件过大,不能显示"
+msgid "Show as raw."
+msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5543,50 +5060,50 @@
 msgstr "%s个关注者"
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "关注者"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
-msgstr "开始关注 - "
+msgstr "开始关注 -"
 
 #: kallithea/templates/forks/fork.html:5
 #, python-format
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "复刻名称"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 #, fuzzy
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr "文件浏览、下载、whoosh和README的默认修订版本"
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "私有"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr "拷贝权限"
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr "从被复刻版本库拷贝权限"
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr "克隆后更新"
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr "完成克隆后检出源代码"
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5596,40 +5113,40 @@
 msgstr "%s个复刻"
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr "复刻"
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "无复刻"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr "订阅日志ATOM"
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr "订阅日志RSS"
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr "没有条目"
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr "订阅公共日志ATOM"
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr "订阅公共日志RSS"
 
@@ -5639,31 +5156,36 @@
 msgid "New Pull Request"
 msgstr "新建拉取请求"
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr "标题"
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "修订"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5677,6 +5199,10 @@
 msgid "Vote"
 msgstr "移除"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5700,7 +5226,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr "已关闭"
 
@@ -5728,113 +5254,106 @@
 msgid "Pull request %s from %s#%s"
 msgstr "拉取请求#%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 #, fuzzy
 msgid "Summarize the changes"
 msgstr "提交修改"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr "还未检视的检视人员"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] "%d个检视者"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "没有任何分支"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 #, fuzzy
 msgid "on"
 msgstr "无"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-#, fuzzy
-msgid "Update"
-msgstr "[更新]用户"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
+#, fuzzy
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "注册"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
 msgid "Current revision - no change"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-#, fuzzy
-msgid "Pull Request Reviewers"
-msgstr "拉取请求检视人员"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
+msgid "Save Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
+#, fuzzy
+msgid "Cancel Changes"
+msgstr "无变更"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+#, fuzzy
+#| msgid "%d reviewer"
+#| msgid_plural "%d reviewers"
+msgid "Reviewers"
+msgstr "%d个检视者"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
 #, fuzzy
 msgid "Remove reviewer"
 msgstr "检视者"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
 msgid "Type name of reviewer to add"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
 #, fuzzy
 msgid "Potential Reviewers"
 msgstr "%d个检视者"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
 msgid "Click to add the repository owner as reviewer:"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
-msgid "Save Changes"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-#, fuzzy
-msgid "Save as New Pull Request"
-msgstr "新建拉取请求"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
-#, fuzzy
-msgid "Cancel Changes"
-msgstr "无变更"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 #, fuzzy
 msgid "Pull Request Content"
 msgstr "拉取请求"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5842,7 +5361,6 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull Requests from %s'"
 msgid "Pull Requests from '%s'"
 msgstr "拉取请求#%s"
 
@@ -5851,37 +5369,43 @@
 msgid "Pull Requests to '%s'"
 msgstr "拉取请求#%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 #, fuzzy
 msgid "Open New Pull Request"
 msgstr "新建拉取请求"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, fuzzy, python-format
 msgid "Show Pull Requests to %s"
 msgstr "拉取请求#%s"
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
-#, fuzzy, python-format
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
+#, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 #, fuzzy
 msgid "Pull Requests Created by Me"
 msgstr "拉取请求检视人员"
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+#, fuzzy
+#| msgid "Pull Request Reviewers"
+msgid "Pull Requests Needing My Review"
+msgstr "拉取请求检视人员"
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 #, fuzzy
 msgid "Pull Requests I Participate In"
 msgstr "我参与的"
@@ -5897,29 +5421,29 @@
 msgid "Search in All Repositories"
 msgstr "在所有的版本库中搜索"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "搜索短语"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "搜索范围"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "文件内容"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr "提交信息"
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "文件名"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "权限不足"
 
@@ -5929,80 +5453,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
-msgstr "已收集的统计:"
-
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+msgstr "已收集的统计: "
+
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "文件"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "提交"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "文件已添加"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "文件已更改"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "文件已删除"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "提交"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "文件已添加"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "文件已更改"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "文件已删除"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr "%s概要"
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr "版本库由%s锁定"
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr "版本库未锁定"
 
@@ -6010,93 +5534,202 @@
 msgid "Fork of"
 msgstr "复刻自"
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "克隆自"
 
-#: kallithea/templates/summary/summary.html:72
-#, fuzzy
-msgid "Clone URL"
-msgstr "克隆地址"
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr "按ID显示"
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr "以名字显示"
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr "按ID显示"
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr "文件趋势图"
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "下载"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "无下载"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "这个版本库的下载已经禁用"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr "zip打包下载"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr "勾选以下载包含子版本库的压缩包"
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 #, fuzzy
 msgid "With subrepos"
 msgstr "包括子版本库"
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "文件已更改"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 #, fuzzy
 msgid "Quick Start"
 msgstr "快速入门"
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr "通过Kallithea直接添加或者上传文件"
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "推送新版本库"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr "已有版本库?"
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "下载%s为%s包"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr "%s标签"
-
-#: kallithea/templates/tags/tags.html:26
-#, fuzzy
-msgid "Compare Tags"
-msgstr "比较标签"
-
-#~ msgid "increase diff context to %(num)s lines"
-#~ msgstr ""
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "没有索引用于搜索。请运行whoosh索引器"
+
+#~ msgid "%(user)s commented on changeset %(age)s"
+#~ msgstr "%(user)s 已评论修订集在 %(age)s"
+
+#, fuzzy
+#~ msgid "%(user)s opened new pull request %(age)s"
+#~ msgstr "成功提交拉取请求"
+
+#, fuzzy
+#~ msgid "%(user)s commented on pull request %(age)s"
+#~ msgstr "[评论]拉取请求"
+
+#~ msgid "%(user)s commented on changeset at %(when)s"
+#~ msgstr "%(user)s 已评论修订集于 %(when)s"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "版本库组"
+
+#~ msgid "My Notifications"
+#~ msgstr "我的通知"
+
+#~ msgid "All"
+#~ msgstr "全部"
+
+#~ msgid "Comments"
+#~ msgstr "评论"
+
+#, fuzzy
+#~ msgid "Mark All Read"
+#~ msgstr "全部标记为已读"
+
+#, fuzzy
+#~| msgid "Mark All Read"
+#~ msgid "Mark as read"
+#~ msgstr "全部标记为已读"
+
+#~ msgid "No notifications here yet"
+#~ msgstr "无通知"
+
+#, fuzzy
+#~ msgid "Show Notification"
+#~ msgstr "显示通知"
+
+#~ msgid "Notifications"
+#~ msgstr "通知"
+
+#~ msgid "Home"
+#~ msgstr "首页"
+
+#~ msgid "with"
+#~ msgstr "有"
+
+#~ msgid "members"
+#~ msgstr "成员"
+
+#~ msgid "Changeset has comments"
+#~ msgstr "修订集有评论"
+
+#~ msgid "Author"
+#~ msgstr "作者"
+
+#, fuzzy
+#~ msgid "Commenting on line."
+#~ msgstr "在{1}行上评论"
+
+#, fuzzy
+#~| msgid "on pull request"
+#~ msgid "Pull request from"
+#~ msgstr "[评论]拉取请求"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "日期"
+
+#~ msgid "Still not reviewed by"
+#~ msgstr "还未检视的检视人员"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "没有任何分支"
+
+#, fuzzy
+#~ msgid "Pull Request Reviewers"
+#~ msgstr "拉取请求检视人员"
+
+#~ msgid "Dashboard"
+#~ msgstr "控制面板"
+
+#~ msgid "Remember me"
+#~ msgstr "记住密码"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "修改你的头像"
+
+#~ msgid "Using"
+#~ msgstr "使用"
+
+#~ msgid "Web"
+#~ msgstr "网络"
+
+#~ msgid "quick filter..."
+#~ msgstr "快速过滤..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "忘记密码?"
+
+#, fuzzy
+#~ msgid "The comment closed the pull request with status"
+#~ msgstr "[评论]拉取请求"
+
+#~ msgid "View this user here"
+#~ msgstr "查看用户"
 
 #~ msgid "No comments."
 #~ msgstr "%d条评论"
@@ -6107,27 +5740,6 @@
 #~ msgid "journal"
 #~ msgstr "日志"
 
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked"
-#~ msgstr ""
-
-#~ msgid "Locked"
-#~ msgstr ""
-
 #~ msgid "Repository has been %s"
 #~ msgstr "版本库已被%s"
 
@@ -6137,12 +5749,6 @@
 #~ msgid "No Files"
 #~ msgstr "没有文件"
 
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
 #~ msgid "Username \"%(username)s\" is forbidden"
 #~ msgstr "不允许用户名 \"%(username)s\""
 
@@ -6155,72 +5761,29 @@
 #~ msgid "invalid clone URL"
 #~ msgstr "无效的克隆地址"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
+#~ msgid ""
+#~ "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
 #~ msgstr "无效的克隆地址,提供一个有效的克隆 http(s)或svn+http(s)地址"
 
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
+#~ msgid ""
+#~ "Revisions %(revs)s are already part of pull request or have set status"
 #~ msgstr "修订%(revs)s已经包含在拉取请求中或者或者已经设置状态"
 
 #~ msgid "Defaults"
 #~ msgstr "默认设置"
 
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
 #~ msgid "Watched"
 #~ msgstr "关注的"
 
 #~ msgid "My Permissions"
 #~ msgstr "我的权限"
 
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
 #~ msgid "delete"
 #~ msgstr "删除"
 
-#~ msgid "current IP"
-#~ msgstr ""
-
 #~ msgid "Permissions Administration"
 #~ msgstr "权限管理"
 
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
 #~ msgid "Default User Permissions Overview"
 #~ msgstr "默认权限"
 
@@ -6236,15 +5799,6 @@
 #~ msgid "admin"
 #~ msgstr "管理"
 
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
 #~ msgid "Optional URL from which repository should be cloned."
 #~ msgstr "可选的,指定版本库应该从哪个http[s]地址克隆。"
 
@@ -6254,138 +5808,146 @@
 #~ msgid "Pull Changes from Remote Location"
 #~ msgstr "从远程路径拉取修订集"
 
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
 #~ msgid "edit"
 #~ msgstr "编辑"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "默认权限"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
 #~ msgid "Status change from pull request"
 #~ msgstr "状态修改为%s"
 
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
 #~ msgid "Mimetype"
 #~ msgstr "MIME类型"
 
 #~ msgid "My Repos"
 #~ msgstr "我的版本库"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
 #~ msgid "owner"
 #~ msgstr "所有者"
 
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
 #~ msgid "Open New Pull Request for Selected Changesets"
 #~ msgstr "为选择的变更集创建新的拉取请求"
 
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
 #~ msgid "You can generate it by clicking following URL"
 #~ msgstr "点击下面的链接以重新生成密码:"
 
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
 #~ msgid "Created by"
 #~ msgstr "创建于"
 
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
+#~ msgid "Closed, replaced by %s ."
+#~ msgstr "已关闭,被 %s 替换。"
+
+#~ msgid "Closing."
+#~ msgstr "关闭。"
+
+#~ msgid "Changeset not found"
+#~ msgstr "未找到修订集"
+
+#~ msgid "Repository no access"
+#~ msgstr "无版本库访问权限"
+
+#~ msgid "Repository read access"
+#~ msgstr "版本库读取权限"
+
+#~ msgid "Repository write access"
+#~ msgstr "版本库写入权限"
+
+#~ msgid "Repository admin access"
+#~ msgstr "版本库管理权限"
+
+#~ msgid "Repository Group no access"
+#~ msgstr "无版本库组访问权限"
+
+#~ msgid "Repository Group read access"
+#~ msgstr "版本库组读取权限"
+
+#~ msgid "Repository Group write access"
+#~ msgstr "版本库组写入"
+
+#~ msgid "Repository Group admin access"
+#~ msgstr "版本库组管理权限"
+
+#~ msgid "Repository creation disabled"
+#~ msgstr "禁用创建版本库"
+
+#~ msgid "Repository creation enabled"
+#~ msgstr "允许创建版本库"
+
+#~ msgid "Repository forking disabled"
+#~ msgstr "禁用复刻版本库"
+
+#~ msgid "Repository forking enabled"
+#~ msgstr "允许复刻版本库"
+
+#~ msgid "Register disabled"
+#~ msgstr "禁用注册"
+
+#~ msgid "Register new user with Kallithea with manual activation"
+#~ msgstr "用手动激活注册新用户"
+
+#~ msgid "Register new user with Kallithea with auto activation"
+#~ msgstr "用自动激活注册新用户"
+
+#~ msgid "Not Reviewed"
+#~ msgstr "未检视"
+
+#~ msgid "Rejected"
+#~ msgstr "驳回"
+
+#~ msgid "Under Review"
+#~ msgstr "检视中"
+
+#~ msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
+#~ msgstr "[评论]拉取请求"
+
+#~ msgid "There are no branches yet"
+#~ msgstr "没有任何分支"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "没有任何标签"
+
+#~ msgid "There are no bookmarks yet"
+#~ msgstr "无书签"
+
+#~ msgid "%s Bookmarks"
+#~ msgstr "%s书签"
+
+#~ msgid "%s Branches"
+#~ msgstr "%s分支"
+
+#~ msgid "Editing file"
+#~ msgstr "编辑文件"
+
+#~ msgid "Update"
+#~ msgstr "[更新]用户"
+
+#~ msgid "Save Updates as New Pull Request"
+#~ msgstr "新建拉取请求"
+
+#~ msgid "%s Tags"
+#~ msgstr "%s标签"
+
+#~ msgid "Compare Tags"
+#~ msgstr "比较标签"
+
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "确认清除版本库缓存"
+
+#~ msgid "Commenting on line {1}."
+#~ msgstr "在{1}行上评论"
+
+#~ msgid "Comments parsed using %s syntax with %s support."
+#~ msgstr "评论使用%s语法并支持%s"
+
+#~ msgid "Use @username inside this text to notify another user"
+#~ msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户"
+
+#~ msgid "Use @username inside this text to notify another user."
+#~ msgstr "在文本中使用 @用户名 以发送通知到该Kallithea用户"
+
+#~ msgid "File is too big to display"
+#~ msgstr "文件过大,不能显示"
+
+#~ msgid "Save as New Pull Request"
+#~ msgstr "新建拉取请求"
--- a/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po	Sun Mar 31 21:28:56 2019 +0200
@@ -7,454 +7,469 @@
 msgstr ""
 "Project-Id-Version: Kallithea 0.3\n"
 "Report-Msgid-Bugs-To: translations@kallithea-scm.org\n"
-"POT-Creation-Date: 2017-07-25 16:37+0200\n"
+"POT-Creation-Date: 2019-03-26 22:07+0100\n"
 "PO-Revision-Date: 2017-03-10 18:26+0000\n"
 "Last-Translator: mao <mao@lins.fju.edu.tw>\n"
-"Language-Team: Chinese (Traditional) "
-"<https://hosted.weblate.org/projects/kallithea/stable/zh_Hant/>\n"
+"Language-Team: Chinese (Traditional) <https://hosted.weblate.org/projects/"
+"kallithea/kallithea/zh_Hant/>\n"
 "Language: zh_TW\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 "X-Generator: Weblate 2.12\n"
-
-#: kallithea/controllers/changelog.py:86
-#: kallithea/controllers/pullrequests.py:238 kallithea/lib/base.py:512
+"Generated-By: Babel 1.3\n"
+
+#: kallithea/controllers/changelog.py:67
+#: kallithea/controllers/pullrequests.py:252 kallithea/lib/base.py:605
 msgid "There are no changesets yet"
 msgstr ""
 
-#: kallithea/controllers/changelog.py:165
-#: kallithea/controllers/admin/permissions.py:61
-#: kallithea/controllers/admin/permissions.py:65
-#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:62
+#: kallithea/controllers/admin/permissions.py:66
+#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/changelog.py:136
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:104
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:88
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:7
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:7
 #: kallithea/templates/base/perms_summary.html:14
 msgid "None"
 msgstr "無"
 
-#: kallithea/controllers/changelog.py:168 kallithea/controllers/files.py:196
+#: kallithea/controllers/changelog.py:139 kallithea/controllers/files.py:197
 msgid "(closed)"
 msgstr "(已關閉)"
 
-#: kallithea/controllers/changeset.py:89
+#: kallithea/controllers/changeset.py:83
 msgid "Show whitespace"
 msgstr "顯示空格"
 
-#: kallithea/controllers/changeset.py:96 kallithea/controllers/changeset.py:103
+#: kallithea/controllers/changeset.py:90
+#: kallithea/controllers/changeset.py:97
 #: kallithea/templates/files/diff_2way.html:55
 msgid "Ignore whitespace"
 msgstr "忽略空格"
 
-#: kallithea/controllers/changeset.py:169
+#: kallithea/controllers/changeset.py:163
 #, python-format
 msgid "Increase diff context to %(num)s lines"
 msgstr "增加 diff 上下文至 %(num)s 行"
 
-#: kallithea/controllers/changeset.py:212 kallithea/controllers/files.py:96
-#: kallithea/controllers/files.py:116 kallithea/controllers/files.py:742
+#: kallithea/controllers/changeset.py:203
+#, fuzzy
+msgid "No permission to change status"
+msgstr "尚未有任何變更"
+
+#: kallithea/controllers/changeset.py:214
+#, fuzzy, python-format
+msgid "Successfully deleted pull request %s"
+msgstr "成功遞交至 %s"
+
+#: kallithea/controllers/changeset.py:321 kallithea/controllers/files.py:97
+#: kallithea/controllers/files.py:117 kallithea/controllers/files.py:727
 msgid "Such revision does not exist for this repository"
 msgstr ""
 
-#: kallithea/controllers/changeset.py:383
-msgid ""
-"Changing status on a changeset associated with a closed pull request is "
-"not allowed"
-msgstr ""
-
-#: kallithea/controllers/compare.py:161 kallithea/templates/base/root.html:41
-msgid "Select changeset"
-msgstr ""
-
-#: kallithea/controllers/compare.py:261
+#: kallithea/controllers/compare.py:66
+#, fuzzy, python-format
+#| msgid "Go to tip of repository"
+msgid "Could not find other repository %s"
+msgstr "Git 版本庫"
+
+#: kallithea/controllers/compare.py:72
+msgid "Cannot compare repositories of different types"
+msgstr ""
+
+#: kallithea/controllers/compare.py:244
+msgid "Cannot show empty diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:246
+msgid "No ancestor found for merge diff"
+msgstr ""
+
+#: kallithea/controllers/compare.py:250
+msgid "Multiple merge ancestors found for merge compare"
+msgstr ""
+
+#: kallithea/controllers/compare.py:266
 msgid "Cannot compare repositories without using common ancestor"
 msgstr ""
 
-#: kallithea/controllers/error.py:71
+#: kallithea/controllers/error.py:70
 msgid "No response"
 msgstr "未回應"
 
-#: kallithea/controllers/error.py:72
+#: kallithea/controllers/error.py:71
 msgid "Unknown error"
 msgstr ""
 
-#: kallithea/controllers/error.py:100
-msgid "The request could not be understood by the server due to malformed syntax."
-msgstr ""
-
-#: kallithea/controllers/error.py:103
+#: kallithea/controllers/error.py:84
+msgid ""
+"The request could not be understood by the server due to malformed syntax."
+msgstr ""
+
+#: kallithea/controllers/error.py:87
 msgid "Unauthorized access to resource"
 msgstr ""
 
-#: kallithea/controllers/error.py:105
+#: kallithea/controllers/error.py:89
 msgid "You don't have permission to view this page"
 msgstr "您沒有權限瀏覽這個頁面"
 
-#: kallithea/controllers/error.py:107
+#: kallithea/controllers/error.py:91
 msgid "The resource could not be found"
 msgstr "找不到這個資源"
 
-#: kallithea/controllers/error.py:109
+#: kallithea/controllers/error.py:93
 msgid ""
 "The server encountered an unexpected condition which prevented it from "
 "fulfilling the request."
 msgstr ""
 
-#: kallithea/controllers/feed.py:55
-#, python-format
-msgid "Changes on %s repository"
-msgstr "修改於版本庫 %s"
-
-#: kallithea/controllers/feed.py:56
-#, python-format
-msgid "%s %s feed"
-msgstr ""
-
-#: kallithea/controllers/feed.py:87
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
-msgid "Changeset was too big and was cut off..."
-msgstr ""
-
-#: kallithea/controllers/feed.py:91
+#: kallithea/controllers/feed.py:63
 #, python-format
 msgid "%s committed on %s"
 msgstr "%s 評論於 %s"
 
-#: kallithea/controllers/files.py:91
-msgid "Click here to add new file"
-msgstr ""
+#: kallithea/controllers/feed.py:88
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/compare/compare_diff.html:95
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: kallithea/controllers/feed.py:111 kallithea/controllers/feed.py:143
+#, python-format
+msgid "%s %s feed"
+msgstr ""
+
+#: kallithea/controllers/feed.py:113 kallithea/controllers/feed.py:145
+#, python-format
+msgid "Changes on %s repository"
+msgstr "修改於版本庫 %s"
 
 #: kallithea/controllers/files.py:92
+msgid "Click here to add new file"
+msgstr ""
+
+#: kallithea/controllers/files.py:93
 #, python-format
 msgid "There are no files yet. %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:193
+#: kallithea/controllers/files.py:194
 #, python-format
 msgid "%s at %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:305 kallithea/controllers/files.py:365
-#: kallithea/controllers/files.py:432
+#: kallithea/controllers/files.py:300 kallithea/controllers/files.py:360
+#: kallithea/controllers/files.py:427
 #, python-format
 msgid "This repository has been locked by %s on %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:317
+#: kallithea/controllers/files.py:312
 msgid "You can only delete files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:328
+#: kallithea/controllers/files.py:323
 #, python-format
 msgid "Deleted file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:350
+#: kallithea/controllers/files.py:345
 #, python-format
 msgid "Successfully deleted file %s"
 msgstr ""
 
-#: kallithea/controllers/files.py:354 kallithea/controllers/files.py:420
-#: kallithea/controllers/files.py:501
+#: kallithea/controllers/files.py:349 kallithea/controllers/files.py:415
+#: kallithea/controllers/files.py:496
 msgid "Error occurred during commit"
 msgstr ""
 
-#: kallithea/controllers/files.py:377
+#: kallithea/controllers/files.py:372
 msgid "You can only edit files with revision being a valid branch"
 msgstr ""
 
-#: kallithea/controllers/files.py:391
+#: kallithea/controllers/files.py:386
 #, python-format
 msgid "Edited file %s via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:407
+#: kallithea/controllers/files.py:402
 msgid "No changes"
 msgstr "沒有修改"
 
-#: kallithea/controllers/files.py:416 kallithea/controllers/files.py:490
+#: kallithea/controllers/files.py:411 kallithea/controllers/files.py:485
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "成功遞交至 %s"
 
-#: kallithea/controllers/files.py:443
+#: kallithea/controllers/files.py:438
 msgid "Added file via Kallithea"
 msgstr ""
 
-#: kallithea/controllers/files.py:464
+#: kallithea/controllers/files.py:459
 msgid "No content"
 msgstr ""
 
-#: kallithea/controllers/files.py:468
+#: kallithea/controllers/files.py:463
 msgid "No filename"
 msgstr ""
 
-#: kallithea/controllers/files.py:493
+#: kallithea/controllers/files.py:488
 msgid "Location must be relative path and must not contain .. in path"
 msgstr ""
 
-#: kallithea/controllers/files.py:526
+#: kallithea/controllers/files.py:520
 msgid "Downloads disabled"
 msgstr ""
 
-#: kallithea/controllers/files.py:537
+#: kallithea/controllers/files.py:531
 #, python-format
 msgid "Unknown revision %s"
 msgstr "未知修訂 %s"
 
-#: kallithea/controllers/files.py:539
+#: kallithea/controllers/files.py:533
 msgid "Empty repository"
 msgstr "空的版本庫"
 
-#: kallithea/controllers/files.py:541
+#: kallithea/controllers/files.py:535
 msgid "Unknown archive type"
 msgstr "未知的存檔類型"
 
-#: kallithea/controllers/files.py:771
+#: kallithea/controllers/files.py:756
 #: kallithea/templates/changeset/changeset_range.html:9
-#: kallithea/templates/email_templates/pull_request.html:15
-#: kallithea/templates/pullrequests/pullrequest.html:97
+#: kallithea/templates/email_templates/pull_request.html:64
+#: kallithea/templates/pullrequests/pullrequest.html:84
 msgid "Changesets"
 msgstr "變更"
 
-#: kallithea/controllers/files.py:772 kallithea/controllers/pullrequests.py:176
-#: kallithea/model/scm.py:820 kallithea/templates/switch_to_list.html:3
-#: kallithea/templates/branches/branches.html:10
+#: kallithea/controllers/files.py:757
+#: kallithea/controllers/pullrequests.py:184 kallithea/model/scm.py:706
 msgid "Branches"
 msgstr "分支"
 
-#: kallithea/controllers/files.py:773 kallithea/controllers/pullrequests.py:177
-#: kallithea/model/scm.py:831 kallithea/templates/switch_to_list.html:25
-#: kallithea/templates/tags/tags.html:10
+#: kallithea/controllers/files.py:758
+#: kallithea/controllers/pullrequests.py:185 kallithea/model/scm.py:717
 msgid "Tags"
 msgstr "標籤"
 
-#: kallithea/controllers/forks.py:186
+#: kallithea/controllers/forks.py:174
 #, python-format
 msgid "An error occurred during repository forking %s"
 msgstr ""
 
-#: kallithea/controllers/home.py:84
+#: kallithea/controllers/home.py:78
 msgid "Groups"
 msgstr ""
 
-#: kallithea/controllers/home.py:89
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:106
+#: kallithea/controllers/home.py:88
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:90
 #: kallithea/templates/admin/repos/repo_add.html:12
 #: kallithea/templates/admin/repos/repo_add.html:16
 #: kallithea/templates/admin/repos/repos.html:9
 #: kallithea/templates/admin/users/user_edit_advanced.html:6
-#: kallithea/templates/base/base.html:60 kallithea/templates/base/base.html:77
-#: kallithea/templates/base/base.html:124
-#: kallithea/templates/base/base.html:390
-#: kallithea/templates/base/base.html:562
+#: kallithea/templates/base/base.html:56
+#: kallithea/templates/base/base.html:73
+#: kallithea/templates/base/base.html:444 kallithea/templates/index.html:5
 msgid "Repositories"
 msgstr "版本庫"
 
-#: kallithea/controllers/home.py:130
+#: kallithea/controllers/home.py:121
 #: kallithea/templates/files/files_add.html:32
 #: kallithea/templates/files/files_delete.html:23
 #: kallithea/templates/files/files_edit.html:32
 msgid "Branch"
 msgstr ""
 
-#: kallithea/controllers/home.py:136
+#: kallithea/controllers/home.py:127
+msgid "Closed Branches"
+msgstr ""
+
+#: kallithea/controllers/home.py:133
 msgid "Tag"
 msgstr ""
 
-#: kallithea/controllers/home.py:142
+#: kallithea/controllers/home.py:139
 msgid "Bookmark"
 msgstr ""
 
-#: kallithea/controllers/journal.py:111 kallithea/controllers/journal.py:153
+#: kallithea/controllers/journal.py:113 kallithea/controllers/journal.py:155
 #: kallithea/templates/journal/public_journal.html:4
-#: kallithea/templates/journal/public_journal.html:21
+#: kallithea/templates/journal/public_journal.html:18
 msgid "Public Journal"
 msgstr "開放日誌"
 
-#: kallithea/controllers/journal.py:115 kallithea/controllers/journal.py:157
-#: kallithea/templates/base/base.html:222
-#: kallithea/templates/journal/journal.html:4
-#: kallithea/templates/journal/journal.html:12
+#: kallithea/controllers/journal.py:117 kallithea/controllers/journal.py:159
+#: kallithea/templates/base/base.html:297
+#: kallithea/templates/journal/journal.html:5
+#: kallithea/templates/journal/journal.html:13
 msgid "Journal"
 msgstr "日誌"
 
-#: kallithea/controllers/login.py:146 kallithea/controllers/login.py:192
+#: kallithea/controllers/login.py:139 kallithea/controllers/login.py:184
 msgid "Bad captcha"
 msgstr ""
 
-#: kallithea/controllers/login.py:152
-msgid "You have successfully registered into Kallithea"
-msgstr ""
-
-#: kallithea/controllers/login.py:197
+#: kallithea/controllers/login.py:145
+#, python-format
+msgid "You have successfully registered with %s"
+msgstr ""
+
+#: kallithea/controllers/login.py:189
 msgid "A password reset confirmation code has been sent"
 msgstr "密碼重設的確認碼已寄出"
 
-#: kallithea/controllers/login.py:246
+#: kallithea/controllers/login.py:238
 msgid "Invalid password reset token"
 msgstr "無效的密碼重設確認碼"
 
-#: kallithea/controllers/login.py:251
-#: kallithea/controllers/admin/my_account.py:167
+#: kallithea/controllers/admin/my_account.py:155
+#: kallithea/controllers/login.py:243
 msgid "Successfully updated password"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:124
-#, python-format
-msgid "%s (closed)"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:152
-#: kallithea/templates/changeset/changeset.html:12
-#: kallithea/templates/email_templates/changeset_comment.html:17
-msgid "Changeset"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:173
-msgid "Special"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:174
-msgid "Peer branches"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:175 kallithea/model/scm.py:826
-#: kallithea/templates/switch_to_list.html:38
-#: kallithea/templates/bookmarks/bookmarks.html:10
-msgid "Bookmarks"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:310
-#, python-format
-msgid "Error creating pull request: %s"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:356
-#: kallithea/controllers/pullrequests.py:503
-msgid "No description"
-msgstr "無描述"
-
-#: kallithea/controllers/pullrequests.py:363
-msgid "Successfully opened new pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:366
-#: kallithea/controllers/pullrequests.py:453
-#: kallithea/controllers/pullrequests.py:510
+#: kallithea/controllers/pullrequests.py:71
 #, python-format
 msgid "Invalid reviewer \"%s\" specified"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:369
-#: kallithea/controllers/pullrequests.py:456
+#: kallithea/controllers/pullrequests.py:133
+#, python-format
+msgid "%s (closed)"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:160
+#: kallithea/templates/changeset/changeset.html:12
+msgid "Changeset"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:181
+msgid "Special"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:182
+msgid "Peer branches"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:183 kallithea/model/scm.py:712
+msgid "Bookmarks"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:320
+#, python-format
+msgid "Error creating pull request: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:347
+#: kallithea/controllers/pullrequests.py:370
 msgid "Error occurred while creating pull request"
 msgstr ""
 
-#: kallithea/controllers/pullrequests.py:401
-msgid "Missing changesets since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:408
-#, python-format
-msgid "New changesets on %s %s since the previous pull request:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:415
-msgid "Ancestor didn't change - show diff since previous version:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:422
+#: kallithea/controllers/pullrequests.py:352
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:375
+msgid "New pull request iteration created"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:403
+#, python-format
+msgid "Meanwhile, the following reviewers have been added: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:407
+#, python-format
+msgid "Meanwhile, the following reviewers have been removed: %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:423
+#: kallithea/model/pull_request.py:234
+msgid "No description"
+msgstr "無描述"
+
+#: kallithea/controllers/pullrequests.py:432
+msgid "Pull request updated"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:445
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:481
+#, python-format
+msgid "Revision %s not found in %s"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:508
+#, python-format
+msgid "Error: changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:522
+#, python-format
+msgid "This pull request has already been merged to %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:524
+msgid "This pull request has been closed and can not be updated."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:543
+#, python-format
+msgid "The following additional changes are available on %s:"
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:545
+#: kallithea/controllers/pullrequests.py:549
+msgid "No additional changesets found for iterating on this pull request."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:557
+#, python-format
+msgid "Note: Branch %s has another head: %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:564
+msgid "Git pull requests don't support iterating yet."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:566
 #, python-format
 msgid ""
-"This pull request is based on another %s revision and there is no simple "
-"diff."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:424
-#, python-format
-msgid "No changes found on %s %s since previous version."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:462
-#, python-format
-msgid "Closed, replaced by %s ."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:470
-msgid "Pull request update created"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:514
-msgid "Pull request updated"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:529
-msgid "Successfully deleted pull request"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:595
-#, python-format
-msgid "This pull request has already been merged to %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:597
-msgid "This pull request has been closed and can not be updated."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:615
-#, python-format
-msgid "This pull request can be updated with changes on %s:"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:619
-msgid "No changesets found for updating this pull request."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:627
-#, python-format
-msgid "Note: Branch %s has another head: %s."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:633
-msgid "Git pull requests don't support updates yet."
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:724
-msgid "No permission to change pull request status"
-msgstr ""
-
-#: kallithea/controllers/pullrequests.py:729
-msgid "Closing."
-msgstr "關閉中。"
-
-#: kallithea/controllers/search.py:135
+"Error: some changesets not found when displaying pull request from %s."
+msgstr ""
+
+#: kallithea/controllers/pullrequests.py:590
+msgid "The diff can't be shown - the PR revisions could not be found."
+msgstr ""
+
+#: kallithea/controllers/search.py:136
 msgid "Invalid search query. Try quoting it."
 msgstr "無效的查詢。請使用跳脫字元。"
 
 #: kallithea/controllers/search.py:140
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr "沒有任何索引可以搜尋。請執行 whoosh 建立索引"
-
-#: kallithea/controllers/search.py:144
+msgid "The server has no search index."
+msgstr ""
+
+#: kallithea/controllers/search.py:143
 msgid "An error occurred during search operation."
 msgstr ""
 
-#: kallithea/controllers/summary.py:180
-#: kallithea/templates/summary/summary.html:384
+#: kallithea/controllers/summary.py:179
+#: kallithea/templates/summary/summary.html:395
 msgid "No data ready yet"
 msgstr ""
 
-#: kallithea/controllers/summary.py:183
-#: kallithea/templates/summary/summary.html:98
+#: kallithea/controllers/summary.py:182
+#: kallithea/templates/summary/summary.html:89
 msgid "Statistics are disabled for this repository"
 msgstr "這個版本庫的統計功能已停用"
 
@@ -466,149 +481,151 @@
 msgid "error occurred during update of auth settings"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:97
+#: kallithea/controllers/admin/defaults.py:75
 msgid "Default settings updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/defaults.py:112
+#: kallithea/controllers/admin/defaults.py:90
 msgid "Error occurred during update of defaults"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:58
+#: kallithea/controllers/admin/my_account.py:230
+#: kallithea/controllers/admin/users.py:248
+msgid "Forever"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:59
-#: kallithea/controllers/admin/my_account.py:243
-#: kallithea/controllers/admin/users.py:285
-msgid "Forever"
+#: kallithea/controllers/admin/my_account.py:231
+#: kallithea/controllers/admin/users.py:249
+msgid "5 minutes"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:60
-#: kallithea/controllers/admin/my_account.py:244
-#: kallithea/controllers/admin/users.py:286
-msgid "5 minutes"
+#: kallithea/controllers/admin/my_account.py:232
+#: kallithea/controllers/admin/users.py:250
+msgid "1 hour"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:61
-#: kallithea/controllers/admin/my_account.py:245
-#: kallithea/controllers/admin/users.py:287
-msgid "1 hour"
+#: kallithea/controllers/admin/my_account.py:233
+#: kallithea/controllers/admin/users.py:251
+msgid "1 day"
 msgstr ""
 
 #: kallithea/controllers/admin/gists.py:62
-#: kallithea/controllers/admin/my_account.py:246
-#: kallithea/controllers/admin/users.py:288
-msgid "1 day"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:63
-#: kallithea/controllers/admin/my_account.py:247
-#: kallithea/controllers/admin/users.py:289
+#: kallithea/controllers/admin/my_account.py:234
+#: kallithea/controllers/admin/users.py:252
 msgid "1 month"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:67
-#: kallithea/controllers/admin/my_account.py:249
-#: kallithea/controllers/admin/users.py:291
+#: kallithea/controllers/admin/gists.py:66
+#: kallithea/controllers/admin/my_account.py:236
+#: kallithea/controllers/admin/users.py:254
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:65
+#: kallithea/templates/admin/users/user_edit_api_keys.html:65
 msgid "Lifetime"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:146
+#: kallithea/controllers/admin/gists.py:140
 msgid "Error occurred during gist creation"
 msgstr ""
 
-#: kallithea/controllers/admin/gists.py:184
+#: kallithea/controllers/admin/gists.py:156
 #, python-format
 msgid "Deleted gist %s"
 msgstr ""
 
+#: kallithea/controllers/admin/gists.py:196
+msgid "Unmodified"
+msgstr "未修改"
+
+#: kallithea/controllers/admin/gists.py:225
+msgid "Successfully updated gist content"
+msgstr ""
+
+#: kallithea/controllers/admin/gists.py:230
+msgid "Successfully updated gist data"
+msgstr ""
+
 #: kallithea/controllers/admin/gists.py:233
-msgid "Unmodified"
-msgstr "未修改"
-
-#: kallithea/controllers/admin/gists.py:262
-msgid "Successfully updated gist content"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:267
-msgid "Successfully updated gist data"
-msgstr ""
-
-#: kallithea/controllers/admin/gists.py:270
 #, python-format
 msgid "Error occurred during update of gist %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:70 kallithea/model/user.py:215
-#: kallithea/model/user.py:237
+#: kallithea/controllers/admin/my_account.py:68 kallithea/model/user.py:214
+#: kallithea/model/user.py:235
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:129
+#: kallithea/controllers/admin/my_account.py:117
 msgid "Your account was updated successfully"
 msgstr "您的帳號已更新完成"
 
-#: kallithea/controllers/admin/my_account.py:144
-#: kallithea/controllers/admin/users.py:202
+#: kallithea/controllers/admin/my_account.py:132
+#: kallithea/controllers/admin/users.py:181
 #, python-format
 msgid "Error occurred during update of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:178
+#: kallithea/controllers/admin/my_account.py:166
 msgid "Error occurred during update of user password"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:220
-#: kallithea/controllers/admin/users.py:415
+#: kallithea/controllers/admin/my_account.py:207
+#: kallithea/controllers/admin/users.py:369
 #, python-format
 msgid "Added email %s to user"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:226
-#: kallithea/controllers/admin/users.py:421
+#: kallithea/controllers/admin/my_account.py:213
+#: kallithea/controllers/admin/users.py:375
 msgid "An error occurred during email saving"
 msgstr ""
 
-#: kallithea/controllers/admin/my_account.py:235
-#: kallithea/controllers/admin/users.py:433
+#: kallithea/controllers/admin/my_account.py:222
+#: kallithea/controllers/admin/users.py:385
 msgid "Removed email from user"
 msgstr ""
 
+#: kallithea/controllers/admin/my_account.py:246
+#: kallithea/controllers/admin/users.py:271
+msgid "API key successfully created"
+msgstr ""
+
+#: kallithea/controllers/admin/my_account.py:255
+#: kallithea/controllers/admin/users.py:281
+msgid "API key successfully reset"
+msgstr ""
+
 #: kallithea/controllers/admin/my_account.py:259
-#: kallithea/controllers/admin/users.py:308
-msgid "API key successfully created"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:271
-#: kallithea/controllers/admin/users.py:321
-msgid "API key successfully reset"
-msgstr ""
-
-#: kallithea/controllers/admin/my_account.py:275
-#: kallithea/controllers/admin/users.py:325
+#: kallithea/controllers/admin/users.py:285
 msgid "API key successfully deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:62
-#: kallithea/controllers/admin/permissions.py:66
-#: kallithea/controllers/admin/permissions.py:70
+#: kallithea/controllers/admin/permissions.py:63
+#: kallithea/controllers/admin/permissions.py:67
+#: kallithea/controllers/admin/permissions.py:71
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:8
 #: kallithea/templates/base/perms_summary.html:15
 msgid "Read"
 msgstr "讀"
 
-#: kallithea/controllers/admin/permissions.py:63
-#: kallithea/controllers/admin/permissions.py:67
-#: kallithea/controllers/admin/permissions.py:71
+#: kallithea/controllers/admin/permissions.py:64
+#: kallithea/controllers/admin/permissions.py:68
+#: kallithea/controllers/admin/permissions.py:72
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:9
 #: kallithea/templates/base/perms_summary.html:16
 msgid "Write"
 msgstr "寫"
 
-#: kallithea/controllers/admin/permissions.py:64
-#: kallithea/controllers/admin/permissions.py:68
-#: kallithea/controllers/admin/permissions.py:72
+#: kallithea/controllers/admin/permissions.py:65
+#: kallithea/controllers/admin/permissions.py:69
+#: kallithea/controllers/admin/permissions.py:73
 #: kallithea/templates/admin/auth/auth_settings.html:9
 #: kallithea/templates/admin/defaults/defaults.html:9
 #: kallithea/templates/admin/permissions/permissions.html:9
@@ -616,621 +633,618 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:9
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:47
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
 #: kallithea/templates/admin/repos/repo_add.html:10
 #: kallithea/templates/admin/repos/repo_add.html:14
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
-#: kallithea/templates/admin/repos/repos.html:9
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:10
 #: kallithea/templates/admin/settings/settings.html:9
 #: kallithea/templates/admin/user_groups/user_group_add.html:8
 #: kallithea/templates/admin/user_groups/user_group_edit.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:9
 #: kallithea/templates/admin/users/user_add.html:8
 #: kallithea/templates/admin/users/user_edit.html:9
-#: kallithea/templates/admin/users/user_edit_profile.html:105
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/admin/users/users.html:55
-#: kallithea/templates/base/base.html:252
-#: kallithea/templates/base/base.html:253
-#: kallithea/templates/base/base.html:259
-#: kallithea/templates/base/base.html:260
+#: kallithea/templates/admin/users/user_edit_profile.html:81
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/admin/users/users.html:43
+#: kallithea/templates/base/base.html:327
+#: kallithea/templates/base/base.html:328
+#: kallithea/templates/base/base.html:334
+#: kallithea/templates/base/base.html:335
 #: kallithea/templates/base/perms_summary.html:17
 msgid "Admin"
 msgstr "管理"
 
-#: kallithea/controllers/admin/permissions.py:75
-#: kallithea/controllers/admin/permissions.py:86
-#: kallithea/controllers/admin/permissions.py:91
-#: kallithea/controllers/admin/permissions.py:94
-#: kallithea/controllers/admin/permissions.py:97
-#: kallithea/controllers/admin/permissions.py:100
-#: kallithea/templates/admin/auth/auth_settings.html:40
-msgid "Disabled"
-msgstr "停用"
-
-#: kallithea/controllers/admin/permissions.py:77
-msgid "Allowed with manual account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:79
-msgid "Allowed with automatic account activation"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:82
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1439
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1485
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1542
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1564
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1603
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1655
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1682 kallithea/model/db.py:1702
-msgid "Manual activation of external account"
-msgstr ""
-
-#: kallithea/controllers/admin/permissions.py:83
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1440
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1486
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1565
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1604
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1656
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1683 kallithea/model/db.py:1703
-msgid "Automatic activation of external account"
-msgstr ""
-
+#: kallithea/controllers/admin/permissions.py:76
 #: kallithea/controllers/admin/permissions.py:87
-#: kallithea/controllers/admin/permissions.py:90
+#: kallithea/controllers/admin/permissions.py:92
 #: kallithea/controllers/admin/permissions.py:95
 #: kallithea/controllers/admin/permissions.py:98
 #: kallithea/controllers/admin/permissions.py:101
-#: kallithea/templates/admin/auth/auth_settings.html:40
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:50
+msgid "Disabled"
+msgstr "停用"
+
+#: kallithea/controllers/admin/permissions.py:78
+msgid "Allowed with manual account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:80
+msgid "Allowed with automatic account activation"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:83 kallithea/model/db.py:1739
+msgid "Manual activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:84 kallithea/model/db.py:1740
+msgid "Automatic activation of external account"
+msgstr ""
+
+#: kallithea/controllers/admin/permissions.py:88
+#: kallithea/controllers/admin/permissions.py:91
+#: kallithea/controllers/admin/permissions.py:96
+#: kallithea/controllers/admin/permissions.py:99
+#: kallithea/controllers/admin/permissions.py:102
+#: kallithea/templates/admin/auth/auth_settings.html:42
+#: kallithea/templates/base/root.html:49
 msgid "Enabled"
 msgstr "啟用"
 
-#: kallithea/controllers/admin/permissions.py:124
+#: kallithea/controllers/admin/permissions.py:125
 msgid "Global permissions updated successfully"
 msgstr ""
 
-#: kallithea/controllers/admin/permissions.py:139
+#: kallithea/controllers/admin/permissions.py:140
 msgid "Error occurred during update of permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:188
+#: kallithea/controllers/admin/repo_groups.py:174
 #, python-format
 msgid "Error occurred during creation of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:193
+#: kallithea/controllers/admin/repo_groups.py:179
 #, python-format
 msgid "Created repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:250
+#: kallithea/controllers/admin/repo_groups.py:226
 #, python-format
 msgid "Updated repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:266
+#: kallithea/controllers/admin/repo_groups.py:242
 #, python-format
 msgid "Error occurred during update of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:284
+#: kallithea/controllers/admin/repo_groups.py:252
 #, python-format
 msgid "This group contains %s repositories and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:291
+#: kallithea/controllers/admin/repo_groups.py:259
 #, python-format
 msgid "This group contains %s subgroups and cannot be deleted"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:297
+#: kallithea/controllers/admin/repo_groups.py:265
 #, python-format
 msgid "Removed repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:302
+#: kallithea/controllers/admin/repo_groups.py:270
 #, python-format
 msgid "Error occurred during deletion of repository group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:405
-#: kallithea/controllers/admin/repo_groups.py:440
-#: kallithea/controllers/admin/user_groups.py:340
+#: kallithea/controllers/admin/repo_groups.py:354
+#: kallithea/controllers/admin/repo_groups.py:384
+#: kallithea/controllers/admin/user_groups.py:299
 msgid "Cannot revoke permission for yourself as admin"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:420
+#: kallithea/controllers/admin/repo_groups.py:369
 msgid "Repository group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repo_groups.py:457
-#: kallithea/controllers/admin/repos.py:398
-#: kallithea/controllers/admin/user_groups.py:352
+#: kallithea/controllers/admin/repo_groups.py:401
+#: kallithea/controllers/admin/repos.py:357
+#: kallithea/controllers/admin/user_groups.py:311
 msgid "An error occurred during revoking of permission"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:152
+#: kallithea/controllers/admin/repos.py:137
 #, python-format
 msgid "Error creating repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:213
+#: kallithea/controllers/admin/repos.py:195
 #, python-format
 msgid "Created repository %s from %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:222
+#: kallithea/controllers/admin/repos.py:204
 #, python-format
 msgid "Forked repository %s as %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:225
+#: kallithea/controllers/admin/repos.py:207
 #, python-format
 msgid "Created repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:262
+#: kallithea/controllers/admin/repos.py:236
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "版本庫 %s 更新完成"
 
-#: kallithea/controllers/admin/repos.py:283
+#: kallithea/controllers/admin/repos.py:256
 #, python-format
 msgid "Error occurred during update of repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:310
+#: kallithea/controllers/admin/repos.py:274
 #, python-format
 msgid "Detached %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:313
+#: kallithea/controllers/admin/repos.py:277
 #, python-format
 msgid "Deleted %s forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:318
+#: kallithea/controllers/admin/repos.py:282
 #, python-format
 msgid "Deleted repository %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:321
+#: kallithea/controllers/admin/repos.py:285
 #, python-format
 msgid "Cannot delete repository %s which still has forks"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:326
+#: kallithea/controllers/admin/repos.py:290
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:374
+#: kallithea/controllers/admin/repos.py:330
 msgid "Repository permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:430
-msgid "An error occurred during creation of field"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:444
+#: kallithea/controllers/admin/repos.py:387
+#, python-format
+msgid "Field validation error: %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:390
+#, python-format
+msgid "An error occurred during creation of field: %r"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:401
 msgid "An error occurred during removal of field"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:460
+#: kallithea/controllers/admin/repos.py:415
 msgid "-- Not a fork --"
 msgstr ""
 
+#: kallithea/controllers/admin/repos.py:446
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:450
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:466
+msgid "Nothing"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:468
+#, python-format
+msgid "Marked repository %s as fork of %s"
+msgstr ""
+
+#: kallithea/controllers/admin/repos.py:475
+msgid "An error occurred during this operation"
+msgstr ""
+
 #: kallithea/controllers/admin/repos.py:491
-msgid "Updated repository visibility in public journal"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:495
-msgid "An error occurred during setting this repository in public journal"
-msgstr ""
-
 #: kallithea/controllers/admin/repos.py:512
-msgid "Nothing"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:514
-#, python-format
-msgid "Marked repository %s as fork of %s"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:521
-msgid "An error occurred during this operation"
-msgstr ""
-
-#: kallithea/controllers/admin/repos.py:537
-#: kallithea/controllers/admin/repos.py:564
 msgid "Repository has been locked"
 msgstr "儲存所已被鎖定"
 
-#: kallithea/controllers/admin/repos.py:540
-#: kallithea/controllers/admin/repos.py:561
+#: kallithea/controllers/admin/repos.py:494
+#: kallithea/controllers/admin/repos.py:509
 msgid "Repository has been unlocked"
 msgstr "儲存所已打開"
 
-#: kallithea/controllers/admin/repos.py:543
-#: kallithea/controllers/admin/repos.py:568
+#: kallithea/controllers/admin/repos.py:497
+#: kallithea/controllers/admin/repos.py:516
 msgid "An error occurred during unlocking"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:582
+#: kallithea/controllers/admin/repos.py:528
 msgid "Cache invalidation successful"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:586
+#: kallithea/controllers/admin/repos.py:532
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:601
+#: kallithea/controllers/admin/repos.py:545
 msgid "Pulled from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:604
+#: kallithea/controllers/admin/repos.py:548
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: kallithea/controllers/admin/repos.py:637
+#: kallithea/controllers/admin/repos.py:579
 msgid "An error occurred during deletion of repository stats"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:170
+#: kallithea/controllers/admin/settings.py:135
 msgid "Updated VCS settings"
 msgstr ""
 
+#: kallithea/controllers/admin/settings.py:139 kallithea/lib/utils.py:231
+msgid ""
+"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
+"missing"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:145
+#: kallithea/controllers/admin/settings.py:234
+msgid "Error occurred while updating application settings"
+msgstr ""
+
 #: kallithea/controllers/admin/settings.py:174
-msgid ""
-"Unable to activate hgsubversion support. The \"hgsubversion\" library is "
-"missing"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:180
-#: kallithea/controllers/admin/settings.py:284
-msgid "Error occurred while updating application settings"
-msgstr ""
-
-#: kallithea/controllers/admin/settings.py:211
 #, python-format
 msgid "Repositories successfully rescanned. Added: %s. Removed: %s."
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:226
+#: kallithea/controllers/admin/settings.py:189
 #, fuzzy, python-format
 #| msgid "Invalidate Repository Cache"
 msgid "Invalidated %s repositories"
 msgstr "確認廢止版本庫快取"
 
-#: kallithea/controllers/admin/settings.py:280
+#: kallithea/controllers/admin/settings.py:230
 msgid "Updated application settings"
 msgstr "更新應用設定"
 
-#: kallithea/controllers/admin/settings.py:337
+#: kallithea/controllers/admin/settings.py:283
 msgid "Updated visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:342
+#: kallithea/controllers/admin/settings.py:288
 msgid "Error occurred during updating visualisation settings"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:368
+#: kallithea/controllers/admin/settings.py:312
 msgid "Please enter email address"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:383
+#: kallithea/controllers/admin/settings.py:327
 msgid "Send email task created"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:414
+#: kallithea/controllers/admin/settings.py:355
+msgid "Hook already exists"
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:357
+msgid "Builtin hooks are read-only. Please use another hook name."
+msgstr ""
+
+#: kallithea/controllers/admin/settings.py:360
 msgid "Added new hook"
 msgstr "新增hook"
 
-#: kallithea/controllers/admin/settings.py:428
+#: kallithea/controllers/admin/settings.py:376
 msgid "Updated hooks"
 msgstr "更新hook"
 
-#: kallithea/controllers/admin/settings.py:432
+#: kallithea/controllers/admin/settings.py:380
 msgid "Error occurred during hook creation"
 msgstr ""
 
-#: kallithea/controllers/admin/settings.py:458
+#: kallithea/controllers/admin/settings.py:404
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh 重新索引工作排程"
 
-#: kallithea/controllers/admin/user_groups.py:150
+#: kallithea/controllers/admin/user_groups.py:143
 #, python-format
 msgid "Created user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:163
+#: kallithea/controllers/admin/user_groups.py:156
 #, python-format
 msgid "Error occurred during creation of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:201
+#: kallithea/controllers/admin/user_groups.py:184
 #, python-format
 msgid "Updated user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:224
+#: kallithea/controllers/admin/user_groups.py:206
 #, python-format
 msgid "Error occurred during update of user group %s"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:242
+#: kallithea/controllers/admin/user_groups.py:217
 msgid "Successfully deleted user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:247
+#: kallithea/controllers/admin/user_groups.py:222
 msgid "An error occurred during deletion of user group"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:314
+#: kallithea/controllers/admin/user_groups.py:278
 msgid "Target group cannot be the same"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:320
+#: kallithea/controllers/admin/user_groups.py:284
 msgid "User group permissions updated"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:440
-#: kallithea/controllers/admin/users.py:384
+#: kallithea/controllers/admin/user_groups.py:395
+#: kallithea/controllers/admin/users.py:340
 msgid "Updated permissions"
 msgstr ""
 
-#: kallithea/controllers/admin/user_groups.py:444
-#: kallithea/controllers/admin/users.py:388
+#: kallithea/controllers/admin/user_groups.py:399
+#: kallithea/controllers/admin/users.py:344
 msgid "An error occurred during permissions saving"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:134
+#: kallithea/controllers/admin/users.py:123
 #, python-format
 msgid "Created user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:149
+#: kallithea/controllers/admin/users.py:138
 #, python-format
 msgid "Error occurred during creation of user %s"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:182
+#: kallithea/controllers/admin/users.py:162
 msgid "User updated successfully"
 msgstr "使用者更新完成"
 
-#: kallithea/controllers/admin/users.py:218
+#: kallithea/controllers/admin/users.py:190
 msgid "Successfully deleted user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:223
+#: kallithea/controllers/admin/users.py:195
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:236
+#: kallithea/controllers/admin/users.py:203
 msgid "The default user cannot be edited"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:463
+#: kallithea/controllers/admin/users.py:412
 #, python-format
 msgid "Added IP address %s to user whitelist"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:469
+#: kallithea/controllers/admin/users.py:418
 msgid "An error occurred while adding IP address"
 msgstr ""
 
-#: kallithea/controllers/admin/users.py:483
+#: kallithea/controllers/admin/users.py:430
 msgid "Removed IP address from user whitelist"
 msgstr ""
 
-#: kallithea/lib/auth.py:744
-#, python-format
-msgid "IP %s not allowed"
-msgstr ""
-
-#: kallithea/lib/auth.py:757
-msgid "Invalid API key"
-msgstr ""
-
-#: kallithea/lib/auth.py:785
-msgid "CSRF token leak has been detected - all form tokens have been expired"
-msgstr ""
-
-#: kallithea/lib/auth.py:832
+#: kallithea/lib/auth.py:824
 msgid "You need to be a registered user to perform this action"
 msgstr "您必須是註冊使用者才能執行這個動作"
 
-#: kallithea/lib/auth.py:864
+#: kallithea/lib/auth.py:852
 msgid "You need to be signed in to view this page"
 msgstr "您必須登入後才能瀏覽這個頁面"
 
-#: kallithea/lib/base.py:490
+#: kallithea/lib/base.py:444
+msgid "Invalid API key"
+msgstr ""
+
+#: kallithea/lib/base.py:495
+msgid ""
+"CSRF token leak has been detected - all form tokens have been expired"
+msgstr ""
+
+#: kallithea/lib/base.py:583
 msgid "Repository not found in the filesystem"
 msgstr ""
 
-#: kallithea/lib/base.py:516
+#: kallithea/lib/base.py:608
 #, python-format
 msgid "Changeset for %s %s not found in %s"
 msgstr ""
 
-#: kallithea/lib/diffs.py:66
+#: kallithea/lib/diffs.py:193
 msgid "Binary file"
 msgstr ""
 
-#: kallithea/lib/diffs.py:82
-msgid "Changeset was too big and was cut off, use diff menu to display this diff"
-msgstr ""
-
-#: kallithea/lib/diffs.py:92
+#: kallithea/lib/diffs.py:213
+msgid ""
+"Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: kallithea/lib/diffs.py:223
 msgid "No changes detected"
 msgstr "尚未有任何變更"
 
-#: kallithea/lib/helpers.py:610
+#: kallithea/lib/helpers.py:612
 #, python-format
 msgid "Deleted branch: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:612
+#: kallithea/lib/helpers.py:614
 #, python-format
 msgid "Created tag: %s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:623
+#: kallithea/lib/helpers.py:625
 #, fuzzy, python-format
 #| msgid "Set changeset status"
 msgid "Changeset %s not found"
 msgstr "尚未有任何變更"
 
-#: kallithea/lib/helpers.py:672
+#: kallithea/lib/helpers.py:674
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: kallithea/lib/helpers.py:678
+#: kallithea/lib/helpers.py:680
 msgid "Compare view"
 msgstr ""
 
-#: kallithea/lib/helpers.py:697
+#: kallithea/lib/helpers.py:699
 msgid "and"
 msgstr "和"
 
-#: kallithea/lib/helpers.py:698
+#: kallithea/lib/helpers.py:700
 #, python-format
 msgid "%s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:699 kallithea/templates/changelog/changelog.html:44
+#: kallithea/lib/helpers.py:701
+#: kallithea/templates/changelog/changelog.html:43
 msgid "revisions"
 msgstr "修訂"
 
-#: kallithea/lib/helpers.py:723
+#: kallithea/lib/helpers.py:725
 #, python-format
 msgid "Fork name %s"
 msgstr "分支名稱 %s"
 
-#: kallithea/lib/helpers.py:743
+#: kallithea/lib/helpers.py:746
 #, python-format
 msgid "Pull request %s"
 msgstr "提取要求 %s"
 
-#: kallithea/lib/helpers.py:753
+#: kallithea/lib/helpers.py:756
 msgid "[deleted] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:755 kallithea/lib/helpers.py:767
+#: kallithea/lib/helpers.py:758 kallithea/lib/helpers.py:770
 msgid "[created] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:757
+#: kallithea/lib/helpers.py:760
 msgid "[created] repository as fork"
 msgstr ""
 
-#: kallithea/lib/helpers.py:759 kallithea/lib/helpers.py:769
+#: kallithea/lib/helpers.py:762 kallithea/lib/helpers.py:772
 msgid "[forked] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:761 kallithea/lib/helpers.py:771
+#: kallithea/lib/helpers.py:764 kallithea/lib/helpers.py:774
 msgid "[updated] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:763
+#: kallithea/lib/helpers.py:766
 msgid "[downloaded] archive from repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:765
+#: kallithea/lib/helpers.py:768
 msgid "[delete] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:773
+#: kallithea/lib/helpers.py:776
 msgid "[created] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:775
+#: kallithea/lib/helpers.py:778
 msgid "[updated] user"
 msgstr ""
 
-#: kallithea/lib/helpers.py:777
+#: kallithea/lib/helpers.py:780
 msgid "[created] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:779
+#: kallithea/lib/helpers.py:782
 msgid "[updated] user group"
 msgstr ""
 
-#: kallithea/lib/helpers.py:781
+#: kallithea/lib/helpers.py:784
 msgid "[commented] on revision in repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:783
+#: kallithea/lib/helpers.py:786
 msgid "[commented] on pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:785
+#: kallithea/lib/helpers.py:788
 msgid "[closed] pull request for"
 msgstr ""
 
-#: kallithea/lib/helpers.py:787
+#: kallithea/lib/helpers.py:790
 msgid "[pushed] into"
 msgstr ""
 
-#: kallithea/lib/helpers.py:789
+#: kallithea/lib/helpers.py:792
 msgid "[committed via Kallithea] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:791
+#: kallithea/lib/helpers.py:794
 msgid "[pulled from remote] into repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:793
+#: kallithea/lib/helpers.py:796
 msgid "[pulled] from"
 msgstr ""
 
-#: kallithea/lib/helpers.py:795
+#: kallithea/lib/helpers.py:798
 msgid "[started following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:797
+#: kallithea/lib/helpers.py:800
 msgid "[stopped following] repository"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1125
+#: kallithea/lib/helpers.py:928
 #, python-format
 msgid " and %s more"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1129
-#: kallithea/templates/compare/compare_diff.html:65
-#: kallithea/templates/pullrequests/pullrequest_show.html:326
+#: kallithea/lib/helpers.py:932
+#: kallithea/templates/compare/compare_diff.html:69
+#: kallithea/templates/pullrequests/pullrequest_show.html:297
 msgid "No files"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1195
+#: kallithea/lib/helpers.py:957
 msgid "new file"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1198
+#: kallithea/lib/helpers.py:960
 msgid "mod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1201
+#: kallithea/lib/helpers.py:963
 msgid "del"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1204
+#: kallithea/lib/helpers.py:966
 msgid "rename"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1209
+#: kallithea/lib/helpers.py:971
 msgid "chmod"
 msgstr ""
 
-#: kallithea/lib/helpers.py:1445
+#: kallithea/lib/helpers.py:1264
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -1238,1153 +1252,649 @@
 "repositories"
 msgstr ""
 
-#: kallithea/lib/utils2.py:415
+#: kallithea/lib/utils2.py:333
 #, python-format
 msgid "%d year"
 msgid_plural "%d years"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:416
+#: kallithea/lib/utils2.py:334
 #, python-format
 msgid "%d month"
 msgid_plural "%d months"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:417
+#: kallithea/lib/utils2.py:335
 #, python-format
 msgid "%d day"
 msgid_plural "%d days"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:418
+#: kallithea/lib/utils2.py:336
 #, python-format
 msgid "%d hour"
 msgid_plural "%d hours"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:419
+#: kallithea/lib/utils2.py:337
 #, python-format
 msgid "%d minute"
 msgid_plural "%d minutes"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:420
+#: kallithea/lib/utils2.py:338
 #, python-format
 msgid "%d second"
 msgid_plural "%d seconds"
 msgstr[0] ""
 
-#: kallithea/lib/utils2.py:436
+#: kallithea/lib/utils2.py:354
 #, python-format
 msgid "in %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:438
+#: kallithea/lib/utils2.py:356
 #, python-format
 msgid "%s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:440
+#: kallithea/lib/utils2.py:358
 #, python-format
 msgid "in %s and %s"
 msgstr ""
 
-#: kallithea/lib/utils2.py:443
+#: kallithea/lib/utils2.py:361
 #, python-format
 msgid "%s and %s ago"
 msgstr ""
 
-#: kallithea/lib/utils2.py:446
+#: kallithea/lib/utils2.py:364
 msgid "just now"
 msgstr "現在"
 
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1163
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1303
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1388
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1408
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1454
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1511
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1572
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1622
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1649
-msgid "Repository no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1164
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1183
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1304
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1389
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1409
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1455
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1512
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1573
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1650
-msgid "Repository read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1165
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1184
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1305
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1390
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1410
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1456
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1513
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1574
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1651
-msgid "Repository write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1166
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1185
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1306
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1391
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1411
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1457
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1514
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1515
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1575
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1652
-msgid "Repository admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1168
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1187
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1308
-msgid "Repository Group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1169
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1188
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1309
-msgid "Repository Group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1170
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1189
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1310
-msgid "Repository Group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1171
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1190
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1311
-msgid "Repository Group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1173
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1192
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1313
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1398
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1406
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1452
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1509
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1510
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1570
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1620
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1647 kallithea/model/db.py:1666
-msgid "Kallithea Administrator"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1174
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1193
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1314
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1399
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1429
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1475
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1532
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1554
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1593
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1643
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1670
-msgid "Repository creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1175
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1194
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1315
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1400
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1430
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1476
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1533
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1534
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1555
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1594
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1644
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1671
-msgid "Repository creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1176
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1195
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1316
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1401
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1432
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1478
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1535
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1557
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1596
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1648
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1675
-msgid "Repository forking disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1177
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1196
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1317
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1402
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1433
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1479
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1536
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1537
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1558
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1597
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1649
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1676
-msgid "Repository forking enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1178
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1197
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1318
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1403
-msgid "Register disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1179
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1198
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1319
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1404
-msgid "Register new user with Kallithea with manual activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1182
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1201
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1322
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1407
-msgid "Register new user with Kallithea with auto activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1623
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1650
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1763
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1838
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1934
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1980
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2040
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2062
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2101
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2154
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2200
-msgid "Not Reviewed"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1624
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1764
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1839
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1935
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1981
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2041
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2063
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2102
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2155
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2201 kallithea/model/db.py:2239
-msgid "Approved"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1625
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1765
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1840
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1936
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1982
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2042
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2064
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2103
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2156
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2202 kallithea/model/db.py:2240
-msgid "Rejected"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_4_0.py:1626
-#: kallithea/lib/dbmigrate/schema/db_1_5_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_1_5_2.py:1766
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1841
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1937
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1983
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:2043
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:2044
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:2065
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:2104
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:2157
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:2203
-msgid "Under Review"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1252
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1270
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1300
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1357
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1358
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1379
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1471
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1498 kallithea/model/db.py:1515
-msgid "top level"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1393
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1413
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1459
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1516
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1577
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1627
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1654
-msgid "Repository group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1394
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1414
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1460
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1517
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1578
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1628
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1655
-msgid "Repository group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1395
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1415
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1461
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1518
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1579
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1629
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1656
-msgid "Repository group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_6_0.py:1396
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1416
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1462
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1519
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1520
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1580
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1630
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1657
-msgid "Repository group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1418
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1464
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1521
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1543
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1582
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1632
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1659
-msgid "User group no access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1419
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1465
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1522
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1544
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1583
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1633
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1660
-msgid "User group read access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1420
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1466
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1523
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1545
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1584
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1634
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1661
-msgid "User group write access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1421
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1467
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1524
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1525
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1546
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1585
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1635
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1662
-msgid "User group admin access"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1423
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1469
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1526
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1548
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1587
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1637
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1664
-msgid "Repository Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1424
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1470
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1527
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1528
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1549
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1588
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1638
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1665
-msgid "Repository Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1426
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1472
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1529
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1551
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1590
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1640
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1667
-msgid "User Group creation disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1427
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1473
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1530
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1531
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1552
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1591
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1641
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1668
-msgid "User Group creation enabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1435
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1481
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1538
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1560
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1599
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1651
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1678 kallithea/model/db.py:1698
-msgid "Registration disabled"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1436
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1482
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1539
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1561
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1600
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1652
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1679
-msgid "User Registration with manual account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_1_7_0.py:1437
-#: kallithea/lib/dbmigrate/schema/db_1_8_0.py:1483
-#: kallithea/lib/dbmigrate/schema/db_2_0_0.py:1540
-#: kallithea/lib/dbmigrate/schema/db_2_0_1.py:1541
-#: kallithea/lib/dbmigrate/schema/db_2_0_2.py:1562
-#: kallithea/lib/dbmigrate/schema/db_2_1_0.py:1601
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1653
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1680
-msgid "User Registration with automatic account activation"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1645
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1672 kallithea/model/db.py:1692
-msgid "Repository creation enabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/lib/dbmigrate/schema/db_2_2_0.py:1646
-#: kallithea/lib/dbmigrate/schema/db_2_2_3.py:1673 kallithea/model/db.py:1693
-msgid "Repository creation disabled with write permission to a repository group"
-msgstr ""
-
-#: kallithea/model/comment.py:72
+#: kallithea/model/comment.py:68
 #, python-format
 msgid "on line %s"
 msgstr ""
 
-#: kallithea/model/comment.py:217 kallithea/model/pull_request.py:169
+#: kallithea/model/comment.py:221 kallithea/model/pull_request.py:117
 msgid "[Mention]"
 msgstr ""
 
-#: kallithea/model/db.py:1668
+#: kallithea/model/db.py:1562
+msgid "top level"
+msgstr ""
+
+#: kallithea/model/db.py:1703
+msgid "Kallithea Administrator"
+msgstr ""
+
+#: kallithea/model/db.py:1705
 msgid "Default user has no access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1669
+#: kallithea/model/db.py:1706
 msgid "Default user has read access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1670
+#: kallithea/model/db.py:1707
 msgid "Default user has write access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1671
+#: kallithea/model/db.py:1708
 msgid "Default user has admin access to new repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1673
+#: kallithea/model/db.py:1710
 msgid "Default user has no access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1674
+#: kallithea/model/db.py:1711
 msgid "Default user has read access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1675
+#: kallithea/model/db.py:1712
 msgid "Default user has write access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1676
+#: kallithea/model/db.py:1713
 msgid "Default user has admin access to new repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1678
+#: kallithea/model/db.py:1715
 msgid "Default user has no access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1679
+#: kallithea/model/db.py:1716
 msgid "Default user has read access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1680
+#: kallithea/model/db.py:1717
 msgid "Default user has write access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1681
+#: kallithea/model/db.py:1718
 msgid "Default user has admin access to new user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1683
+#: kallithea/model/db.py:1720
 msgid "Only admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1684
+#: kallithea/model/db.py:1721
 msgid "Non-admins can create repository groups"
 msgstr ""
 
-#: kallithea/model/db.py:1686
+#: kallithea/model/db.py:1723
 msgid "Only admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1687
+#: kallithea/model/db.py:1724
 msgid "Non-admins can create user groups"
 msgstr ""
 
-#: kallithea/model/db.py:1689
+#: kallithea/model/db.py:1726
 msgid "Only admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1690
+#: kallithea/model/db.py:1727
 msgid "Non-admins can create top level repositories"
 msgstr ""
 
-#: kallithea/model/db.py:1695
+#: kallithea/model/db.py:1729
+msgid ""
+"Repository creation enabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1730
+msgid ""
+"Repository creation disabled with write permission to a repository group"
+msgstr ""
+
+#: kallithea/model/db.py:1732
 msgid "Only admins can fork repositories"
 msgstr "祗有管理者才能分歧版本庫"
 
-#: kallithea/model/db.py:1696
+#: kallithea/model/db.py:1733
 #, fuzzy
-#| msgid "Non-admins can can fork repositories"
 msgid "Non-admins can fork repositories"
-msgstr "非管理者可以建立分歧版本庫"
-
-#: kallithea/model/db.py:1699
+msgstr "建立版本庫"
+
+#: kallithea/model/db.py:1735
+msgid "Registration disabled"
+msgstr ""
+
+#: kallithea/model/db.py:1736
 msgid "User registration with manual account activation"
 msgstr ""
 
-#: kallithea/model/db.py:1700
+#: kallithea/model/db.py:1737
 msgid "User registration with automatic account activation"
 msgstr ""
 
-#: kallithea/model/db.py:2238
+#: kallithea/model/db.py:2263
 msgid "Not reviewed"
 msgstr "未審核"
 
-#: kallithea/model/db.py:2241
+#: kallithea/model/db.py:2264
 msgid "Under review"
 msgstr "審核中"
 
-#: kallithea/model/forms.py:57
+#: kallithea/model/db.py:2265
+msgid "Not approved"
+msgstr ""
+
+#: kallithea/model/db.py:2266
+msgid "Approved"
+msgstr ""
+
+#: kallithea/model/forms.py:58
 msgid "Please enter a login"
 msgstr "請登入"
 
-#: kallithea/model/forms.py:58
+#: kallithea/model/forms.py:59
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr ""
 
-#: kallithea/model/forms.py:66
+#: kallithea/model/forms.py:67
 msgid "Please enter a password"
 msgstr "請輸入密碼"
 
-#: kallithea/model/forms.py:67
+#: kallithea/model/forms.py:68
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr ""
 
-#: kallithea/model/forms.py:160
+#: kallithea/model/forms.py:170
 msgid "Name must not contain only digits"
 msgstr ""
 
-#: kallithea/model/notification.py:255
-#, python-format
-msgid "%(user)s commented on changeset %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:256
-#, python-format
-msgid "%(user)s sent message %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:257
-#, python-format
-msgid "%(user)s mentioned you %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:258
-#, python-format
-msgid "%(user)s registered in Kallithea %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:259
-#, python-format
-msgid "%(user)s opened new pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:260
-#, python-format
-msgid "%(user)s commented on pull request %(age)s"
-msgstr ""
-
-#: kallithea/model/notification.py:267
-#, python-format
-msgid "%(user)s commented on changeset at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:268
-#, python-format
-msgid "%(user)s sent message at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:269
-#, python-format
-msgid "%(user)s mentioned you at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:270
-#, python-format
-msgid "%(user)s registered in Kallithea at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:271
-#, python-format
-msgid "%(user)s opened new pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:272
-#, python-format
-msgid "%(user)s commented on pull request at %(when)s"
-msgstr ""
-
-#: kallithea/model/notification.py:303
-#, python-format
-msgid "[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s"
-msgstr ""
-
-#: kallithea/model/notification.py:306
+#: kallithea/model/notification.py:165
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s changeset %(short_id)s \"%(message_short)s\" on "
+"%(branch)s"
+msgstr ""
+
+#: kallithea/model/notification.py:168
 #, python-format
 msgid "New user %(new_username)s registered"
 msgstr ""
 
-#: kallithea/model/notification.py:308
-#, python-format
-msgid "[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:309
-#, python-format
-msgid "[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s"
-msgstr ""
-
-#: kallithea/model/notification.py:322
+#: kallithea/model/notification.py:170
+#, python-format
+msgid ""
+"[Review] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:171
+#, python-format
+msgid ""
+"[Comment] %(repo_name)s PR %(pr_nice_id)s \"%(pr_title_short)s\" from "
+"%(pr_source_branch)s by %(pr_owner_username)s"
+msgstr ""
+
+#: kallithea/model/notification.py:184
 msgid "Closing"
 msgstr "關閉中"
 
-#: kallithea/model/pull_request.py:137
-#, python-format
-msgid "%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
-msgstr ""
-
-#: kallithea/model/scm.py:812
+#: kallithea/model/pull_request.py:76
+#, python-format
+msgid ""
+"%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:211
+msgid "Cannot create empty pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:219
+#, python-format
+msgid ""
+"Cannot create pull request - criss cross merge detected, please merge a "
+"later %s revision to %s"
+msgstr ""
+
+#: kallithea/model/pull_request.py:247 kallithea/model/pull_request.py:382
+msgid "You are not authorized to create the pull request"
+msgstr ""
+
+#: kallithea/model/pull_request.py:341
+msgid "Missing changesets since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:348
+#, python-format
+msgid "New changesets on %s %s since the previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:355
+msgid "Ancestor didn't change - diff since previous iteration:"
+msgstr ""
+
+#: kallithea/model/pull_request.py:362
+#, python-format
+msgid ""
+"This iteration is based on another %s revision and there is no simple "
+"diff."
+msgstr ""
+
+#: kallithea/model/pull_request.py:364
+#, python-format
+msgid "No changes found on %s %s since previous iteration."
+msgstr ""
+
+#: kallithea/model/pull_request.py:390
+#, python-format
+msgid "Closed, next iteration: %s ."
+msgstr ""
+
+#: kallithea/model/scm.py:698
 msgid "latest tip"
 msgstr ""
 
-#: kallithea/model/user.py:192
+#: kallithea/model/user.py:189
 msgid "New user registration"
 msgstr ""
 
-#: kallithea/model/user.py:256
-msgid "You can't remove this user since it is crucial for the entire application"
+#: kallithea/model/user.py:253
+msgid ""
+"You can't remove this user since it is crucial for the entire application"
 msgstr "您無法移除這個使用者,因為係供整個應用使用"
 
-#: kallithea/model/user.py:261
+#: kallithea/model/user.py:258
 #, python-format
 msgid ""
 "User \"%s\" still owns %s repositories and cannot be removed. Switch "
 "owners or remove those repositories: %s"
 msgstr ""
 
-#: kallithea/model/user.py:266
+#: kallithea/model/user.py:263
 #, python-format
 msgid ""
-"User \"%s\" still owns %s repository groups and cannot be removed. Switch"
-" owners or remove those repository groups: %s"
-msgstr ""
-
-#: kallithea/model/user.py:273
+"User \"%s\" still owns %s repository groups and cannot be removed. Switch "
+"owners or remove those repository groups: %s"
+msgstr ""
+
+#: kallithea/model/user.py:270
 #, python-format
 msgid ""
 "User \"%s\" still owns %s user groups and cannot be removed. Switch "
 "owners or remove those user groups: %s"
 msgstr ""
 
-#: kallithea/model/user.py:360
+#: kallithea/model/user.py:364
 msgid "Password reset link"
 msgstr ""
 
-#: kallithea/model/user.py:408
+#: kallithea/model/user.py:413
 msgid "Password reset notification"
 msgstr ""
 
-#: kallithea/model/user.py:409
+#: kallithea/model/user.py:414
 #, python-format
 msgid ""
 "The password to your account %s has been changed using password reset "
 "form."
 msgstr ""
 
-#: kallithea/model/validators.py:77 kallithea/model/validators.py:78
+#: kallithea/model/validators.py:54 kallithea/model/validators.py:55
 msgid "Value cannot be an empty list"
 msgstr ""
 
-#: kallithea/model/validators.py:95
+#: kallithea/model/validators.py:74
 #, python-format
 msgid "Username \"%(username)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:97
+#: kallithea/model/validators.py:76
 #, python-format
 msgid "Username \"%(username)s\" cannot be used"
 msgstr ""
 
-#: kallithea/model/validators.py:99
+#: kallithea/model/validators.py:78
 msgid ""
-"Username may only contain alphanumeric characters underscores, periods or"
-" dashes and must begin with an alphanumeric character or underscore"
-msgstr ""
-
-#: kallithea/model/validators.py:126
+"Username may only contain alphanumeric characters underscores, periods or "
+"dashes and must begin with an alphanumeric character or underscore"
+msgstr ""
+
+#: kallithea/model/validators.py:105
 msgid "The input is not valid"
 msgstr ""
 
+#: kallithea/model/validators.py:112
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
 #: kallithea/model/validators.py:133
-#, python-format
-msgid "Username %(username)s is not valid"
-msgstr ""
-
-#: kallithea/model/validators.py:152
 msgid "Invalid user group name"
 msgstr ""
 
-#: kallithea/model/validators.py:153
+#: kallithea/model/validators.py:134
 #, python-format
 msgid "User group \"%(usergroup)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:155
+#: kallithea/model/validators.py:136
 msgid ""
 "user group name may only contain alphanumeric characters underscores, "
 "periods or dashes and must begin with alphanumeric character"
-msgstr "使用者羣組名稱可以包括文數字字元、底線、句點或破折號,必須以文數字啟頭"
-
-#: kallithea/model/validators.py:193
+msgstr ""
+"使用者羣組名稱可以包括文數字字元、底線、句點或破折號,必須以文數字啟頭"
+
+#: kallithea/model/validators.py:176
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:194
+#: kallithea/model/validators.py:177
 #, python-format
 msgid "Group \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:196
+#: kallithea/model/validators.py:179
 #, python-format
 msgid "Repository with name \"%(group_name)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:254
+#: kallithea/model/validators.py:235
 msgid "Invalid characters (non-ascii) in password"
 msgstr ""
 
-#: kallithea/model/validators.py:269
+#: kallithea/model/validators.py:250
 msgid "Invalid old password"
 msgstr ""
 
-#: kallithea/model/validators.py:285
+#: kallithea/model/validators.py:266
 msgid "Passwords do not match"
 msgstr "密碼不相符"
 
-#: kallithea/model/validators.py:300
+#: kallithea/model/validators.py:281
 msgid "Invalid username or password"
 msgstr "無效的用戶名稱或密碼"
 
-#: kallithea/model/validators.py:331
+#: kallithea/model/validators.py:312
 msgid "Token mismatch"
 msgstr ""
 
-#: kallithea/model/validators.py:345
+#: kallithea/model/validators.py:328
 #, python-format
 msgid "Repository name %(repo)s is not allowed"
 msgstr ""
 
-#: kallithea/model/validators.py:347
+#: kallithea/model/validators.py:330
 #, python-format
 msgid "Repository named %(repo)s already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:348
+#: kallithea/model/validators.py:331
 #, python-format
 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr ""
 
-#: kallithea/model/validators.py:350
+#: kallithea/model/validators.py:333
 #, python-format
 msgid "Repository group with name \"%(repo)s\" already exists"
 msgstr ""
 
-#: kallithea/model/validators.py:465
+#: kallithea/model/validators.py:419
 msgid "Invalid repository URL"
 msgstr "無效的版本庫 URL"
 
-#: kallithea/model/validators.py:466
+#: kallithea/model/validators.py:420
 msgid ""
 "Invalid repository URL. It must be a valid http, https, ssh, svn+http or "
 "svn+https URL"
 msgstr ""
 
-#: kallithea/model/validators.py:489
+#: kallithea/model/validators.py:445
 msgid "Fork has to be the same type as parent"
 msgstr ""
 
-#: kallithea/model/validators.py:504
+#: kallithea/model/validators.py:460
 msgid "You don't have permissions to create repository in this group"
 msgstr ""
 
-#: kallithea/model/validators.py:506
+#: kallithea/model/validators.py:462
 msgid "no permission to create repository in root location"
 msgstr ""
 
-#: kallithea/model/validators.py:556
+#: kallithea/model/validators.py:512
 msgid "You don't have permissions to create a group in this location"
 msgstr ""
 
-#: kallithea/model/validators.py:597
+#: kallithea/model/validators.py:552
 msgid "This username or user group name is not valid"
 msgstr ""
 
-#: kallithea/model/validators.py:690
+#: kallithea/model/validators.py:645
 msgid "This is not a valid path"
 msgstr "不是一個有效的路徑"
 
-#: kallithea/model/validators.py:705
+#: kallithea/model/validators.py:662
 #, fuzzy
 msgid "This email address is already in use"
 msgstr "這個郵件位址已經使用了"
 
-#: kallithea/model/validators.py:725
+#: kallithea/model/validators.py:682
 #, python-format
 msgid "Email address \"%(email)s\" not found"
 msgstr ""
 
-#: kallithea/model/validators.py:762
+#: kallithea/model/validators.py:719
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
 "of the attribute that is equivalent to \"username\""
 msgstr ""
 
-#: kallithea/model/validators.py:774
+#: kallithea/model/validators.py:731
 msgid "Please enter a valid IPv4 or IPv6 address"
 msgstr ""
 
-#: kallithea/model/validators.py:775
-#, python-format
-msgid "The network size (bits) must be within the range of 0-32 (not %(bits)r)"
-msgstr ""
-
-#: kallithea/model/validators.py:808
+#: kallithea/model/validators.py:732
+#, python-format
+msgid ""
+"The network size (bits) must be within the range of 0-32 (not %(bits)r)"
+msgstr ""
+
+#: kallithea/model/validators.py:765
 msgid "Key name can only consist of letters, underscore, dash or numbers"
 msgstr ""
 
-#: kallithea/model/validators.py:822
+#: kallithea/model/validators.py:779
 msgid "Filename cannot be inside a directory"
 msgstr ""
 
-#: kallithea/model/validators.py:838
+#: kallithea/model/validators.py:795
 #, python-format
 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
 msgstr ""
 
-#: kallithea/templates/about.html:4 kallithea/templates/about.html:17
+#: kallithea/templates/about.html:4 kallithea/templates/about.html:13
 msgid "About"
 msgstr ""
 
-#: kallithea/templates/index.html:5
-msgid "Dashboard"
-msgstr "儀表板"
-
-#: kallithea/templates/index_base.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:3
-#: kallithea/templates/admin/my_account/my_account_watched.html:3
-#: kallithea/templates/admin/repo_groups/repo_groups.html:9
-#: kallithea/templates/admin/repos/repos.html:9
-#: kallithea/templates/admin/user_groups/user_groups.html:9
-#: kallithea/templates/admin/users/users.html:9
-#: kallithea/templates/bookmarks/bookmarks.html:9
-#: kallithea/templates/branches/branches.html:9
-#: kallithea/templates/journal/journal.html:9
-#: kallithea/templates/journal/journal.html:48
-#: kallithea/templates/journal/journal.html:49
-#: kallithea/templates/tags/tags.html:9
-msgid "quick filter..."
-msgstr "快速過濾..."
-
-#: kallithea/templates/index_base.html:6
-msgid "repositories"
-msgstr "個版本庫"
-
-#: kallithea/templates/index_base.html:20
-#: kallithea/templates/index_base.html:25
 #: kallithea/templates/admin/repos/repo_add.html:5
 #: kallithea/templates/admin/repos/repo_add.html:19
-#: kallithea/templates/admin/repos/repos.html:22
+#: kallithea/templates/admin/repos/repos.html:23
+#: kallithea/templates/index_base.html:25
+#: kallithea/templates/index_base.html:30
 msgid "Add Repository"
 msgstr ""
 
-#: kallithea/templates/index_base.html:22
-#: kallithea/templates/index_base.html:27
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:5
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:13
-#: kallithea/templates/admin/repo_groups/repo_groups.html:26
-msgid "Add Repository Group"
-msgstr ""
-
+#: kallithea/templates/admin/repo_groups/repo_groups.html:25
+#: kallithea/templates/index_base.html:27
 #: kallithea/templates/index_base.html:32
+msgid "Add Repository Group"
+msgstr ""
+
+#: kallithea/templates/index_base.html:37
 msgid "You have admin right to this group, and can edit it"
 msgstr ""
 
-#: kallithea/templates/index_base.html:32
+#: kallithea/templates/index_base.html:37
 msgid "Edit Repository Group"
 msgstr ""
 
-#: kallithea/templates/index_base.html:45
-msgid "Group Name"
-msgstr ""
-
-#: kallithea/templates/index_base.html:46
-#: kallithea/templates/index_base.html:127
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:64
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:17
-#: kallithea/templates/admin/repo_groups/repo_groups.html:47
-#: kallithea/templates/admin/repos/repo_add_base.html:28
-#: kallithea/templates/admin/repos/repo_edit_settings.html:65
-#: kallithea/templates/admin/repos/repos.html:48
-#: kallithea/templates/admin/user_groups/user_group_add.html:40
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:15
-#: kallithea/templates/admin/user_groups/user_groups.html:47
-#: kallithea/templates/admin/users/user_edit_api_keys.html:64
-#: kallithea/templates/email_templates/changeset_comment.html:18
-#: kallithea/templates/email_templates/pull_request.html:12
-#: kallithea/templates/forks/fork.html:38
-#: kallithea/templates/pullrequests/pullrequest.html:40
+#: kallithea/templates/admin/admin_log.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:14
+#: kallithea/templates/index_base.html:53
+msgid "Repository"
+msgstr "版本庫"
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:59
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:35
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:12
+#: kallithea/templates/admin/repo_groups/repo_groups.html:40
+#: kallithea/templates/admin/repos/repo_add_base.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:49
+#: kallithea/templates/admin/repos/repos.html:39
+#: kallithea/templates/admin/user_groups/user_group_add.html:33
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:10
+#: kallithea/templates/admin/user_groups/user_groups.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:59
+#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/email_templates/pull_request.html:37
+#: kallithea/templates/forks/fork.html:34
+#: kallithea/templates/index_base.html:58
+#: kallithea/templates/pullrequests/pullrequest.html:33
 #: kallithea/templates/pullrequests/pullrequest_show.html:38
-#: kallithea/templates/pullrequests/pullrequest_show.html:63
-#: kallithea/templates/summary/summary.html:85
+#: kallithea/templates/pullrequests/pullrequest_show.html:59
+#: kallithea/templates/summary/summary.html:79
 msgid "Description"
 msgstr "描述"
 
-#: kallithea/templates/index_base.html:125
-#: kallithea/templates/admin/my_account/my_account_repos.html:46
-#: kallithea/templates/admin/my_account/my_account_watched.html:46
-#: kallithea/templates/admin/repo_groups/repo_groups.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:9
-#: kallithea/templates/admin/repos/repo_edit_settings.html:7
-#: kallithea/templates/admin/repos/repos.html:47
-#: kallithea/templates/admin/user_groups/user_groups.html:46
-#: kallithea/templates/base/perms_summary.html:53
-#: kallithea/templates/bookmarks/bookmarks.html:49
-#: kallithea/templates/bookmarks/bookmarks_data.html:7
-#: kallithea/templates/branches/branches.html:49
-#: kallithea/templates/branches/branches_data.html:7
-#: kallithea/templates/files/files_browser.html:60
-#: kallithea/templates/journal/journal.html:187
-#: kallithea/templates/journal/journal.html:278
-#: kallithea/templates/tags/tags.html:49
-#: kallithea/templates/tags/tags_data.html:7
-msgid "Name"
-msgstr "名稱"
-
-#: kallithea/templates/index_base.html:128
+#: kallithea/templates/index_base.html:60
 msgid "Last Change"
 msgstr ""
 
-#: kallithea/templates/index_base.html:130
-#: kallithea/templates/admin/my_account/my_account_repos.html:48
-#: kallithea/templates/admin/my_account/my_account_watched.html:48
-#: kallithea/templates/admin/repos/repos.html:49
-#: kallithea/templates/journal/journal.html:189
-#: kallithea/templates/journal/journal.html:280
+#: kallithea/templates/admin/my_account/my_account_repos.html:15
+#: kallithea/templates/admin/my_account/my_account_watched.html:15
+#: kallithea/templates/admin/repos/repos.html:41
+#: kallithea/templates/index_base.html:62
 msgid "Tip"
 msgstr ""
 
-#: kallithea/templates/index_base.html:132
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:10
-#: kallithea/templates/admin/repo_groups/repo_groups.html:49
-#: kallithea/templates/admin/repos/repo_edit_settings.html:53
-#: kallithea/templates/admin/repos/repos.html:50
+#: kallithea/templates/admin/repo_groups/repo_groups.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:42
+#: kallithea/templates/admin/repos/repos.html:42
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:8
-#: kallithea/templates/admin/user_groups/user_groups.html:50
+#: kallithea/templates/admin/user_groups/user_groups.html:42
+#: kallithea/templates/index_base.html:63
 #: kallithea/templates/pullrequests/pullrequest_data.html:16
-#: kallithea/templates/pullrequests/pullrequest_show.html:156
-#: kallithea/templates/pullrequests/pullrequest_show.html:233
-#: kallithea/templates/summary/summary.html:134
+#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:219
+#: kallithea/templates/summary/summary.html:124
 msgid "Owner"
 msgstr "擁有者"
 
-#: kallithea/templates/index_base.html:140
-#: kallithea/templates/admin/my_account/my_account_repos.html:57
-#: kallithea/templates/admin/my_account/my_account_watched.html:57
-#: kallithea/templates/base/root.html:43
-#: kallithea/templates/bookmarks/bookmarks.html:79
-#: kallithea/templates/branches/branches.html:79
-#: kallithea/templates/journal/journal.html:198
-#: kallithea/templates/journal/journal.html:289
-#: kallithea/templates/tags/tags.html:79
-msgid "Click to sort ascending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:141
-#: kallithea/templates/admin/my_account/my_account_repos.html:58
-#: kallithea/templates/admin/my_account/my_account_watched.html:58
-#: kallithea/templates/base/root.html:44
-#: kallithea/templates/bookmarks/bookmarks.html:80
-#: kallithea/templates/branches/branches.html:80
-#: kallithea/templates/journal/journal.html:199
-#: kallithea/templates/journal/journal.html:290
-#: kallithea/templates/tags/tags.html:80
-msgid "Click to sort descending"
-msgstr ""
-
-#: kallithea/templates/index_base.html:142
-msgid "No repositories found."
-msgstr ""
-
-#: kallithea/templates/index_base.html:143
-#: kallithea/templates/admin/my_account/my_account_repos.html:60
-#: kallithea/templates/admin/my_account/my_account_watched.html:60
-#: kallithea/templates/base/root.html:46
-#: kallithea/templates/bookmarks/bookmarks.html:82
-#: kallithea/templates/branches/branches.html:82
-#: kallithea/templates/journal/journal.html:201
-#: kallithea/templates/journal/journal.html:292
-#: kallithea/templates/tags/tags.html:82
-msgid "Data error."
-msgstr ""
-
-#: kallithea/templates/index_base.html:144
-#: kallithea/templates/admin/my_account/my_account_repos.html:61
-#: kallithea/templates/admin/my_account/my_account_watched.html:61
-#: kallithea/templates/base/base.html:140 kallithea/templates/base/root.html:47
-#: kallithea/templates/bookmarks/bookmarks.html:83
-#: kallithea/templates/branches/branches.html:83
-#: kallithea/templates/journal/journal.html:202
-#: kallithea/templates/journal/journal.html:293
-#: kallithea/templates/tags/tags.html:83
-msgid "Loading..."
-msgstr ""
-
-#: kallithea/templates/login.html:5 kallithea/templates/login.html:15
-#: kallithea/templates/base/base.html:326
+#: kallithea/templates/base/base.html:387 kallithea/templates/login.html:5
+#: kallithea/templates/login.html:19
 msgid "Log In"
 msgstr ""
 
-#: kallithea/templates/login.html:13
+#: kallithea/templates/login.html:17
 #, python-format
 msgid "Log In to %s"
 msgstr ""
 
-#: kallithea/templates/login.html:26 kallithea/templates/register.html:24
 #: kallithea/templates/admin/admin_log.html:5
-#: kallithea/templates/admin/my_account/my_account_profile.html:25
-#: kallithea/templates/admin/users/user_add.html:32
-#: kallithea/templates/admin/users/user_edit_profile.html:24
-#: kallithea/templates/admin/users/users.html:50
-#: kallithea/templates/base/base.html:302
-#: kallithea/templates/pullrequests/pullrequest_show.html:166
+#: kallithea/templates/admin/my_account/my_account_profile.html:18
+#: kallithea/templates/admin/users/user_add.html:27
+#: kallithea/templates/admin/users/user_edit_profile.html:18
+#: kallithea/templates/admin/users/users.html:37
+#: kallithea/templates/base/base.html:371
+#: kallithea/templates/email_templates/registration.html:11
+#: kallithea/templates/login.html:28 kallithea/templates/register.html:31
 msgid "Username"
 msgstr "帳號"
 
-#: kallithea/templates/login.html:33 kallithea/templates/register.html:33
-#: kallithea/templates/admin/my_account/my_account.html:37
-#: kallithea/templates/admin/users/user_add.html:41
-#: kallithea/templates/base/base.html:311
+#: kallithea/templates/admin/my_account/my_account.html:27
+#: kallithea/templates/admin/users/user_add.html:34
+#: kallithea/templates/base/base.html:375 kallithea/templates/login.html:34
+#: kallithea/templates/register.html:38
 msgid "Password"
 msgstr "密碼"
 
 #: kallithea/templates/login.html:44
-msgid "Remember me"
-msgstr ""
-
-#: kallithea/templates/login.html:53
+msgid "Stay logged in after browser restart"
+msgstr ""
+
+#: kallithea/templates/login.html:52
 msgid "Forgot your password ?"
 msgstr "忘記您的密碼?"
 
-#: kallithea/templates/login.html:56 kallithea/templates/base/base.html:322
+#: kallithea/templates/login.html:55
 msgid "Don't have an account ?"
 msgstr "沒有帳號?"
 
-#: kallithea/templates/login.html:59
+#: kallithea/templates/login.html:62
 msgid "Sign In"
 msgstr "登入"
 
@@ -2392,182 +1902,151 @@
 msgid "Password Reset"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:12
-#: kallithea/templates/password_reset_confirmation.html:12
-#, fuzzy, python-format
+#: kallithea/templates/password_reset.html:21
+#: kallithea/templates/password_reset_confirmation.html:16
+#, python-format
 msgid "Reset Your Password to %s"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:14
+#: kallithea/templates/password_reset.html:23
 #: kallithea/templates/password_reset_confirmation.html:5
-#: kallithea/templates/password_reset_confirmation.html:14
+#: kallithea/templates/password_reset_confirmation.html:18
 #, fuzzy
 msgid "Reset Your Password"
 msgstr "忘記您的密碼?"
 
-#: kallithea/templates/password_reset.html:25
+#: kallithea/templates/password_reset.html:30
 msgid "Email Address"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:35
-#: kallithea/templates/register.html:79
+#: kallithea/templates/password_reset.html:38
+#: kallithea/templates/register.html:74
 msgid "Captcha"
 msgstr ""
 
-#: kallithea/templates/password_reset.html:46
-msgid "Send Password Reset Email"
-msgstr ""
-
 #: kallithea/templates/password_reset.html:47
+msgid "Send Password Reset Email"
+msgstr ""
+
+#: kallithea/templates/password_reset.html:52
 #, fuzzy
 msgid ""
 "A password reset link will be sent to the specified email address if it "
 "is registered in the system."
 msgstr "密碼重設連結已郵寄至您的信箱"
 
-#: kallithea/templates/password_reset_confirmation.html:19
+#: kallithea/templates/password_reset_confirmation.html:23
 #, python-format
 msgid "You are about to set a new password for the email address %s."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:20
+#: kallithea/templates/password_reset_confirmation.html:24
 msgid ""
 "Note that you must use the same browser session for this as the one used "
 "to request the password reset."
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:30
+#: kallithea/templates/password_reset_confirmation.html:29
 msgid "Code you received in the email"
 msgstr ""
 
-#: kallithea/templates/password_reset_confirmation.html:39
+#: kallithea/templates/password_reset_confirmation.html:36
 #, fuzzy
 msgid "New Password"
 msgstr "新密碼"
 
-#: kallithea/templates/password_reset_confirmation.html:48
+#: kallithea/templates/password_reset_confirmation.html:43
 #, fuzzy
 msgid "Confirm New Password"
 msgstr "新密碼"
 
-#: kallithea/templates/password_reset_confirmation.html:56
+#: kallithea/templates/password_reset_confirmation.html:51
 msgid "Confirm"
 msgstr ""
 
-#: kallithea/templates/register.html:5 kallithea/templates/register.html:14
-#: kallithea/templates/register.html:90
+#: kallithea/templates/register.html:5 kallithea/templates/register.html:24
+#: kallithea/templates/register.html:83
 msgid "Sign Up"
 msgstr "登入"
 
-#: kallithea/templates/register.html:12
+#: kallithea/templates/register.html:22
 #, python-format
 msgid "Sign Up to %s"
 msgstr ""
 
-#: kallithea/templates/register.html:42
+#: kallithea/templates/register.html:45
 msgid "Re-enter password"
 msgstr "確認密碼"
 
-#: kallithea/templates/register.html:51
-#: kallithea/templates/admin/my_account/my_account_profile.html:34
-#: kallithea/templates/admin/users/user_add.html:59
-#: kallithea/templates/admin/users/user_edit_profile.html:78
-#: kallithea/templates/admin/users/users.html:51
+#: kallithea/templates/admin/my_account/my_account_profile.html:25
+#: kallithea/templates/admin/users/user_add.html:48
+#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/users/users.html:38
+#: kallithea/templates/register.html:52
 msgid "First Name"
 msgstr "名"
 
-#: kallithea/templates/register.html:60
-#: kallithea/templates/admin/my_account/my_account_profile.html:43
-#: kallithea/templates/admin/users/user_add.html:68
-#: kallithea/templates/admin/users/user_edit_profile.html:87
-#: kallithea/templates/admin/users/users.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:32
+#: kallithea/templates/admin/users/user_add.html:55
+#: kallithea/templates/admin/users/user_edit_profile.html:67
+#: kallithea/templates/admin/users/users.html:39
+#: kallithea/templates/register.html:59
 msgid "Last Name"
 msgstr "姓"
 
-#: kallithea/templates/register.html:69
-#: kallithea/templates/admin/my_account/my_account_profile.html:52
+#: kallithea/templates/admin/my_account/my_account_profile.html:39
 #: kallithea/templates/admin/settings/settings.html:31
-#: kallithea/templates/admin/users/user_add.html:77
-#: kallithea/templates/admin/users/user_edit_profile.html:33
+#: kallithea/templates/admin/users/user_add.html:62
+#: kallithea/templates/admin/users/user_edit_profile.html:25
+#: kallithea/templates/email_templates/registration.html:33
+#: kallithea/templates/register.html:66
 msgid "Email"
 msgstr "電子郵件"
 
-#: kallithea/templates/register.html:92
+#: kallithea/templates/register.html:85
 msgid "Registered accounts are ready to use and need no further action."
 msgstr ""
 
-#: kallithea/templates/register.html:94
+#: kallithea/templates/register.html:87
 msgid "Please wait for an administrator to activate your account."
 msgstr ""
 
-#: kallithea/templates/switch_to_list.html:10
-#: kallithea/templates/branches/branches_data.html:69
-msgid "There are no branches yet"
-msgstr "沒有任何分支"
-
-#: kallithea/templates/switch_to_list.html:16
-msgid "Closed Branches"
-msgstr ""
-
-#: kallithea/templates/switch_to_list.html:32
-#: kallithea/templates/tags/tags_data.html:44
-msgid "There are no tags yet"
-msgstr "沒有任何標籤"
-
-#: kallithea/templates/switch_to_list.html:45
-#: kallithea/templates/bookmarks/bookmarks_data.html:43
-msgid "There are no bookmarks yet"
-msgstr ""
-
 #: kallithea/templates/admin/admin.html:5
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:55
 #, fuzzy
 msgid "Admin Journal"
 msgstr "管理員日誌"
 
 #: kallithea/templates/admin/admin.html:10
+#: kallithea/templates/journal/journal.html:10
 msgid "journal filter..."
 msgstr ""
 
 #: kallithea/templates/admin/admin.html:12
-#: kallithea/templates/journal/journal.html:11
+#: kallithea/templates/journal/journal.html:12
 #, fuzzy
 msgid "Filter"
 msgstr "檔案"
 
 #: kallithea/templates/admin/admin.html:13
-#: kallithea/templates/journal/journal.html:12
-#, fuzzy, python-format
+#: kallithea/templates/journal/journal.html:13
+#, python-format
 msgid "%s Entry"
 msgid_plural "%s Entries"
 msgstr[0] ""
 
 #: kallithea/templates/admin/admin_log.html:6
-#: kallithea/templates/admin/my_account/my_account_repos.html:50
-#: kallithea/templates/admin/my_account/my_account_watched.html:50
-#: kallithea/templates/admin/repo_groups/repo_groups.html:50
-#: kallithea/templates/admin/repos/repo_edit_fields.html:8
-#: kallithea/templates/admin/repos/repos.html:52
-#: kallithea/templates/admin/user_groups/user_groups.html:51
-#: kallithea/templates/admin/users/users.html:57
-#: kallithea/templates/journal/journal.html:191
-#: kallithea/templates/journal/journal.html:282
+#: kallithea/templates/admin/my_account/my_account_repos.html:16
+#: kallithea/templates/admin/repo_groups/repo_groups.html:43
+#: kallithea/templates/admin/repos/repo_edit_fields.html:9
+#: kallithea/templates/admin/repos/repos.html:44
+#: kallithea/templates/admin/user_groups/user_groups.html:43
+#: kallithea/templates/admin/users/users.html:45
 msgid "Action"
 msgstr "動作"
 
-#: kallithea/templates/admin/admin_log.html:7
-#: kallithea/templates/admin/permissions/permissions_globals.html:18
-msgid "Repository"
-msgstr "版本庫"
-
 #: kallithea/templates/admin/admin_log.html:8
-#: kallithea/templates/bookmarks/bookmarks.html:51
-#: kallithea/templates/bookmarks/bookmarks_data.html:9
-#: kallithea/templates/branches/branches.html:51
-#: kallithea/templates/branches/branches_data.html:9
-#: kallithea/templates/tags/tags.html:51
-#: kallithea/templates/tags/tags_data.html:9
 msgid "Date"
 msgstr "時間"
 
@@ -2575,7 +2054,7 @@
 msgid "From IP"
 msgstr "來源IP"
 
-#: kallithea/templates/admin/admin_log.html:63
+#: kallithea/templates/admin/admin_log.html:61
 msgid "No actions yet"
 msgstr ""
 
@@ -2584,109 +2063,109 @@
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:11
-#: kallithea/templates/base/base.html:65
+#: kallithea/templates/base/base.html:61
 msgid "Authentication"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:28
+#: kallithea/templates/admin/auth/auth_settings.html:27
 msgid "Authentication Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:31
+#: kallithea/templates/admin/auth/auth_settings.html:29
 msgid "Enabled Plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:33
+#: kallithea/templates/admin/auth/auth_settings.html:32
 msgid ""
 "Comma-separated list of plugins; Kallithea will try user authentication "
 "in plugin order"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:34
+#: kallithea/templates/admin/auth/auth_settings.html:36
 msgid "Available built-in plugins"
 msgstr ""
 
-#: kallithea/templates/admin/auth/auth_settings.html:51
+#: kallithea/templates/admin/auth/auth_settings.html:53
 msgid "Plugin"
 msgstr ""
 
 #: kallithea/templates/admin/auth/auth_settings.html:101
-#: kallithea/templates/admin/defaults/defaults.html:82
-#: kallithea/templates/admin/my_account/my_account_password.html:36
-#: kallithea/templates/admin/my_account/my_account_profile.html:60
-#: kallithea/templates/admin/permissions/permissions_globals.html:112
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:69
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:114
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:42
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:101
-#: kallithea/templates/admin/repos/repo_edit_settings.html:127
-#: kallithea/templates/admin/settings/settings_hooks.html:53
-#: kallithea/templates/admin/user_groups/user_group_add.html:57
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:104
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:60
-#: kallithea/templates/admin/users/user_add.html:96
-#: kallithea/templates/admin/users/user_edit_profile.html:113
-#: kallithea/templates/base/default_perms_box.html:64
+#: kallithea/templates/admin/defaults/defaults.html:67
+#: kallithea/templates/admin/my_account/my_account_password.html:30
+#: kallithea/templates/admin/my_account/my_account_profile.html:47
+#: kallithea/templates/admin/permissions/permissions_globals.html:95
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:98
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:35
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:84
+#: kallithea/templates/admin/repos/repo_edit_settings.html:101
+#: kallithea/templates/admin/settings/settings_hooks.html:46
+#: kallithea/templates/admin/user_groups/user_group_add.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:88
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:46
+#: kallithea/templates/admin/users/user_add.html:80
+#: kallithea/templates/admin/users/user_edit_profile.html:89
+#: kallithea/templates/base/default_perms_box.html:56
 msgid "Save"
 msgstr "儲存"
 
 #: kallithea/templates/admin/defaults/defaults.html:5
 #: kallithea/templates/admin/defaults/defaults.html:11
-#: kallithea/templates/base/base.html:66
+#: kallithea/templates/base/base.html:62
 #, fuzzy
 msgid "Repository Defaults"
 msgstr "個版本庫"
 
-#: kallithea/templates/admin/defaults/defaults.html:33
-#: kallithea/templates/admin/repos/repo_add_base.html:55
-#: kallithea/templates/admin/repos/repo_edit_fields.html:7
+#: kallithea/templates/admin/defaults/defaults.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:42
+#: kallithea/templates/admin/repos/repo_edit_fields.html:8
 msgid "Type"
 msgstr "類型"
 
-#: kallithea/templates/admin/defaults/defaults.html:42
-#: kallithea/templates/admin/repos/repo_add_base.html:73
-#: kallithea/templates/admin/repos/repo_edit_settings.html:75
-#: kallithea/templates/data_table/_dt_elements.html:72
+#: kallithea/templates/admin/defaults/defaults.html:34
+#: kallithea/templates/admin/repos/repo_add_base.html:56
+#: kallithea/templates/admin/repos/repo_edit_settings.html:57
+#: kallithea/templates/data_table/_dt_elements.html:21
 msgid "Private repository"
 msgstr "私有的版本庫"
 
-#: kallithea/templates/admin/defaults/defaults.html:46
-#: kallithea/templates/admin/repos/repo_add_base.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:79
-#: kallithea/templates/forks/fork.html:72
+#: kallithea/templates/admin/defaults/defaults.html:37
+#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_edit_settings.html:60
+#: kallithea/templates/forks/fork.html:61
 msgid ""
 "Private repositories are only visible to people explicitly added as "
 "collaborators."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:53
-#: kallithea/templates/admin/repos/repo_edit_settings.html:84
+#: kallithea/templates/admin/defaults/defaults.html:42
+#: kallithea/templates/admin/repos/repo_edit_settings.html:64
 msgid "Enable statistics"
 msgstr "啟用統計"
 
-#: kallithea/templates/admin/defaults/defaults.html:57
-#: kallithea/templates/admin/repos/repo_edit_settings.html:88
+#: kallithea/templates/admin/defaults/defaults.html:45
+#: kallithea/templates/admin/repos/repo_edit_settings.html:67
 msgid "Enable statistics window on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:63
-#: kallithea/templates/admin/repos/repo_edit_settings.html:93
+#: kallithea/templates/admin/defaults/defaults.html:50
+#: kallithea/templates/admin/repos/repo_edit_settings.html:71
 msgid "Enable downloads"
 msgstr "啟用下載"
 
-#: kallithea/templates/admin/defaults/defaults.html:67
-#: kallithea/templates/admin/repos/repo_edit_settings.html:97
+#: kallithea/templates/admin/defaults/defaults.html:53
+#: kallithea/templates/admin/repos/repo_edit_settings.html:74
 msgid "Enable download menu on summary page."
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:73
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:34
-#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/defaults/defaults.html:58
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repos/repo_edit_settings.html:78
 msgid "Enable locking"
 msgstr ""
 
-#: kallithea/templates/admin/defaults/defaults.html:77
-#: kallithea/templates/admin/repos/repo_edit_settings.html:106
+#: kallithea/templates/admin/defaults/defaults.html:61
+#: kallithea/templates/admin/repos/repo_edit_settings.html:81
 msgid "Enable lock-by-pulling on repository."
 msgstr ""
 
@@ -2695,45 +2174,45 @@
 msgid "Edit Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:36
+#: kallithea/templates/admin/gists/edit.html:35
 #, python-format
 msgid ""
 "Gist was update since you started editing. Copy your changes and click "
 "%(here)s to reload new version."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:55
-#: kallithea/templates/admin/gists/new.html:39
+#: kallithea/templates/admin/gists/edit.html:51
+#: kallithea/templates/admin/gists/new.html:35
 msgid "Gist description ..."
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:57
-#: kallithea/templates/admin/gists/new.html:41
+#: kallithea/templates/admin/gists/edit.html:54
+#: kallithea/templates/admin/gists/new.html:38
 msgid "Gist lifetime"
 msgstr ""
 
+#: kallithea/templates/admin/gists/edit.html:59
 #: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/edit.html:63
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/index.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/index.html:56
+#: kallithea/templates/admin/gists/show.html:45
 #: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/gists/show.html:49
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:32
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:32
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:31
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:31
 msgid "Expires"
 msgstr ""
 
-#: kallithea/templates/admin/gists/edit.html:61
-#: kallithea/templates/admin/gists/index.html:57
-#: kallithea/templates/admin/gists/show.html:47
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:8
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:27
-#: kallithea/templates/admin/users/user_edit_api_keys.html:8
-#: kallithea/templates/admin/users/user_edit_api_keys.html:27
+#: kallithea/templates/admin/gists/edit.html:59
+#: kallithea/templates/admin/gists/index.html:54
+#: kallithea/templates/admin/gists/show.html:45
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:26
+#: kallithea/templates/admin/users/user_edit_api_keys.html:7
+#: kallithea/templates/admin/users/user_edit_api_keys.html:26
 #, fuzzy
 msgid "Never"
 msgstr "擁有者"
@@ -2743,7 +2222,8 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/edit.html:146
-#: kallithea/templates/changeset/changeset_file_comment.html:81
+#: kallithea/templates/base/root.html:27
+#: kallithea/templates/changeset/changeset_file_comment.html:130
 msgid "Cancel"
 msgstr ""
 
@@ -2766,16 +2246,16 @@
 
 #: kallithea/templates/admin/gists/index.html:37
 #: kallithea/templates/admin/gists/show.html:25
-#: kallithea/templates/base/base.html:237
+#: kallithea/templates/base/base.html:312
 msgid "Create New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:54
-#: kallithea/templates/data_table/_dt_elements.html:141
+#: kallithea/templates/admin/gists/index.html:51
+#: kallithea/templates/data_table/_dt_elements.html:78
 msgid "Created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/index.html:74
+#: kallithea/templates/admin/gists/index.html:66
 msgid "There are no gists yet"
 msgstr ""
 
@@ -2784,45 +2264,45 @@
 msgid "New Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:47
-msgid "name this file..."
-msgstr ""
-
-#: kallithea/templates/admin/gists/new.html:56
+#: kallithea/templates/admin/gists/new.html:45
+msgid "Name this gist ..."
+msgstr ""
+
+#: kallithea/templates/admin/gists/new.html:53
 msgid "Create Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:57
+#: kallithea/templates/admin/gists/new.html:54
 msgid "Create Public Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/new.html:58
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:15
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:70
-#: kallithea/templates/admin/my_account/my_account_emails.html:46
-#: kallithea/templates/admin/my_account/my_account_password.html:37
-#: kallithea/templates/admin/my_account/my_account_profile.html:61
-#: kallithea/templates/admin/permissions/permissions_globals.html:113
-#: kallithea/templates/admin/permissions/permissions_ips.html:39
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:115
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:43
-#: kallithea/templates/admin/repos/repo_edit_fields.html:59
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:102
-#: kallithea/templates/admin/repos/repo_edit_settings.html:128
-#: kallithea/templates/admin/settings/settings_global.html:57
-#: kallithea/templates/admin/settings/settings_vcs.html:81
-#: kallithea/templates/admin/settings/settings_visual.html:117
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:105
-#: kallithea/templates/admin/users/user_edit_api_keys.html:15
-#: kallithea/templates/admin/users/user_edit_api_keys.html:70
-#: kallithea/templates/admin/users/user_edit_emails.html:46
-#: kallithea/templates/admin/users/user_edit_ips.html:50
-#: kallithea/templates/admin/users/user_edit_profile.html:114
-#: kallithea/templates/base/default_perms_box.html:65
-#: kallithea/templates/files/files_add.html:65
-#: kallithea/templates/files/files_delete.html:44
-#: kallithea/templates/files/files_edit.html:68
-#: kallithea/templates/pullrequests/pullrequest.html:89
+#: kallithea/templates/admin/gists/new.html:55
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:73
+#: kallithea/templates/admin/my_account/my_account_emails.html:47
+#: kallithea/templates/admin/my_account/my_account_password.html:31
+#: kallithea/templates/admin/my_account/my_account_profile.html:48
+#: kallithea/templates/admin/permissions/permissions_globals.html:96
+#: kallithea/templates/admin/permissions/permissions_ips.html:34
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:99
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:36
+#: kallithea/templates/admin/repos/repo_edit_fields.html:54
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:85
+#: kallithea/templates/admin/repos/repo_edit_settings.html:102
+#: kallithea/templates/admin/settings/settings_global.html:50
+#: kallithea/templates/admin/settings/settings_vcs.html:78
+#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:89
+#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/users/user_edit_api_keys.html:73
+#: kallithea/templates/admin/users/user_edit_emails.html:47
+#: kallithea/templates/admin/users/user_edit_ips.html:45
+#: kallithea/templates/admin/users/user_edit_profile.html:90
+#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/files/files_add.html:69
+#: kallithea/templates/files/files_delete.html:41
+#: kallithea/templates/files/files_edit.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:78
 msgid "Reset"
 msgstr "重設"
 
@@ -2832,112 +2312,110 @@
 msgstr ""
 
 #: kallithea/templates/admin/gists/show.html:10
-#: kallithea/templates/email_templates/changeset_comment.html:15
-#: kallithea/templates/email_templates/pull_request.html:10
-#: kallithea/templates/email_templates/pull_request_comment.html:15
 msgid "URL"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:35
+msgid "Public Gist"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:37
-msgid "Public Gist"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:39
 msgid "Private Gist"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:56
-#: kallithea/templates/admin/my_account/my_account_emails.html:19
-#: kallithea/templates/admin/permissions/permissions_ips.html:12
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:75
-#: kallithea/templates/admin/repos/repo_edit_fields.html:18
-#: kallithea/templates/admin/settings/settings_hooks.html:36
-#: kallithea/templates/admin/users/user_edit_emails.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:22
+#: kallithea/templates/admin/gists/show.html:54
+#: kallithea/templates/admin/my_account/my_account_emails.html:23
+#: kallithea/templates/admin/permissions/permissions_ips.html:11
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/settings/settings_hooks.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:23
+#: kallithea/templates/admin/users/user_edit_ips.html:21
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#: kallithea/templates/data_table/_dt_elements.html:129
-#: kallithea/templates/data_table/_dt_elements.html:157
-#: kallithea/templates/data_table/_dt_elements.html:173
-#: kallithea/templates/data_table/_dt_elements.html:189
-#: kallithea/templates/files/files_source.html:39
-#: kallithea/templates/files/files_source.html:42
-#: kallithea/templates/files/files_source.html:45
+#: kallithea/templates/changeset/changeset_file_comment.html:121
+#: kallithea/templates/data_table/_dt_elements.html:69
+#: kallithea/templates/data_table/_dt_elements.html:89
+#: kallithea/templates/data_table/_dt_elements.html:91
+#: kallithea/templates/data_table/_dt_elements.html:101
+#: kallithea/templates/data_table/_dt_elements.html:103
+#: kallithea/templates/data_table/_dt_elements.html:120
+#: kallithea/templates/data_table/_dt_elements.html:122
+#: kallithea/templates/files/files_source.html:35
+#: kallithea/templates/files/files_source.html:38
+#: kallithea/templates/files/files_source.html:41
 #: kallithea/templates/pullrequests/pullrequest_data.html:20
 msgid "Delete"
 msgstr "移除"
 
-#: kallithea/templates/admin/gists/show.html:56
+#: kallithea/templates/admin/gists/show.html:54
 msgid "Confirm to delete this Gist"
 msgstr ""
 
+#: kallithea/templates/admin/gists/show.html:61
+#: kallithea/templates/base/perms_summary.html:44
+#: kallithea/templates/base/perms_summary.html:81
+#: kallithea/templates/base/perms_summary.html:83
+#: kallithea/templates/data_table/_dt_elements.html:63
+#: kallithea/templates/data_table/_dt_elements.html:64
+#: kallithea/templates/data_table/_dt_elements.html:85
+#: kallithea/templates/data_table/_dt_elements.html:86
+#: kallithea/templates/data_table/_dt_elements.html:97
+#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:116
+#: kallithea/templates/data_table/_dt_elements.html:117
+#: kallithea/templates/files/diff_2way.html:56
+#: kallithea/templates/files/files_source.html:37
+#: kallithea/templates/files/files_source.html:40
+#: kallithea/templates/pullrequests/pullrequest_show.html:41
+msgid "Edit"
+msgstr ""
+
 #: kallithea/templates/admin/gists/show.html:63
-#: kallithea/templates/base/perms_summary.html:43
-#: kallithea/templates/base/perms_summary.html:79
-#: kallithea/templates/base/perms_summary.html:81
-#: kallithea/templates/changeset/changeset_file_comment.html:83
-#: kallithea/templates/changeset/changeset_file_comment.html:192
-#: kallithea/templates/data_table/_dt_elements.html:122
-#: kallithea/templates/data_table/_dt_elements.html:123
-#: kallithea/templates/data_table/_dt_elements.html:150
-#: kallithea/templates/data_table/_dt_elements.html:151
-#: kallithea/templates/data_table/_dt_elements.html:165
-#: kallithea/templates/data_table/_dt_elements.html:167
-#: kallithea/templates/data_table/_dt_elements.html:181
-#: kallithea/templates/data_table/_dt_elements.html:183
-#: kallithea/templates/files/diff_2way.html:56
-#: kallithea/templates/files/files_source.html:41
-#: kallithea/templates/files/files_source.html:44
-#: kallithea/templates/pullrequests/pullrequest_show.html:41
-msgid "Edit"
-msgstr ""
-
-#: kallithea/templates/admin/gists/show.html:65
-#: kallithea/templates/files/files_edit.html:49
-#: kallithea/templates/files/files_source.html:34
+#: kallithea/templates/files/files_edit.html:52
+#: kallithea/templates/files/files_source.html:30
 msgid "Show as Raw"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:73
+#: kallithea/templates/admin/gists/show.html:69
 msgid "created"
 msgstr ""
 
-#: kallithea/templates/admin/gists/show.html:86
-#: kallithea/templates/files/files_source.html:73
+#: kallithea/templates/admin/gists/show.html:82
 msgid "Show as raw"
 msgstr ""
 
 #: kallithea/templates/admin/my_account/my_account.html:5
 #: kallithea/templates/admin/my_account/my_account.html:9
-#: kallithea/templates/base/base.html:343
+#: kallithea/templates/base/base.html:397
 msgid "My Account"
 msgstr "我的帳號"
 
-#: kallithea/templates/admin/my_account/my_account.html:35
+#: kallithea/templates/admin/my_account/my_account.html:25
 #: kallithea/templates/admin/users/user_edit.html:29
 msgid "Profile"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:36
+#: kallithea/templates/admin/my_account/my_account.html:26
 msgid "Email Addresses"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:38
+#: kallithea/templates/admin/my_account/my_account.html:28
 #: kallithea/templates/admin/users/user_edit.html:31
 msgid "API Keys"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account.html:39
+#: kallithea/templates/admin/my_account/my_account.html:29
 #, fuzzy
 msgid "Owned Repositories"
 msgstr "個版本庫"
 
-#: kallithea/templates/admin/my_account/my_account.html:40
-#: kallithea/templates/journal/journal.html:53
+#: kallithea/templates/admin/my_account/my_account.html:30
+#: kallithea/templates/journal/journal.html:33
 #, fuzzy
 msgid "Watched Repositories"
 msgstr "建立版本庫"
 
-#: kallithea/templates/admin/my_account/my_account.html:41
+#: kallithea/templates/admin/my_account/my_account.html:31
 #: kallithea/templates/admin/permissions/permissions.html:30
 #: kallithea/templates/admin/user_groups/user_group_edit.html:32
 #: kallithea/templates/admin/users/user_edit.html:34
@@ -2945,73 +2423,90 @@
 msgid "Show Permissions"
 msgstr "權限"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:6
-#: kallithea/templates/admin/users/user_edit_api_keys.html:6
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:5
+#: kallithea/templates/admin/users/user_edit_api_keys.html:5
 msgid "Built-in"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:14
-#: kallithea/templates/admin/users/user_edit_api_keys.html:14
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:13
+#: kallithea/templates/admin/users/user_edit_api_keys.html:13
 #, python-format
 msgid "Confirm to reset this API key: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:30
-#: kallithea/templates/admin/users/user_edit_api_keys.html:30
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:29
+#: kallithea/templates/admin/users/user_edit_api_keys.html:29
 msgid "Expired"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:40
-#: kallithea/templates/admin/users/user_edit_api_keys.html:40
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:39
+#: kallithea/templates/admin/users/user_edit_api_keys.html:39
 #, fuzzy, python-format
 msgid "Confirm to remove this API key: %s"
 msgstr "確認移除目前的統計"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:42
-#: kallithea/templates/admin/users/user_edit_api_keys.html:42
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:41
+#: kallithea/templates/admin/users/user_edit_api_keys.html:41
 #, fuzzy
 msgid "Remove"
 msgstr "移除檔案"
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:49
-#: kallithea/templates/admin/users/user_edit_api_keys.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:48
+#: kallithea/templates/admin/users/user_edit_api_keys.html:48
 msgid "No additional API keys specified"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:61
-#: kallithea/templates/admin/users/user_edit_api_keys.html:61
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:56
+#: kallithea/templates/admin/users/user_edit_api_keys.html:56
 msgid "New API key"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_api_keys.html:69
-#: kallithea/templates/admin/my_account/my_account_emails.html:45
-#: kallithea/templates/admin/permissions/permissions_ips.html:38
-#: kallithea/templates/admin/repos/repo_add_base.html:81
-#: kallithea/templates/admin/repos/repo_edit_fields.html:58
-#: kallithea/templates/admin/users/user_edit_api_keys.html:69
-#: kallithea/templates/admin/users/user_edit_emails.html:45
-#: kallithea/templates/admin/users/user_edit_ips.html:49
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:72
+#: kallithea/templates/admin/my_account/my_account_emails.html:46
+#: kallithea/templates/admin/permissions/permissions_ips.html:33
+#: kallithea/templates/admin/repos/repo_add_base.html:64
+#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/users/user_edit_api_keys.html:72
+#: kallithea/templates/admin/users/user_edit_emails.html:46
+#: kallithea/templates/admin/users/user_edit_ips.html:44
 msgid "Add"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:7
-#: kallithea/templates/admin/users/user_edit_emails.html:7
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:81
+#, python-format
+msgid ""
+"\n"
+"API keys are used to let scripts or services access %s using your\n"
+"account, as if you had provided the script or service with your actual\n"
+"password.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_api_keys.html:86
+msgid ""
+"\n"
+"Like passwords, API keys should therefore never be shared with others,\n"
+"nor passed to untrusted scripts or services. If such sharing should\n"
+"happen anyway, reset the API key on this page to prevent further use.\n"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_emails.html:9
+#: kallithea/templates/admin/users/user_edit_emails.html:9
 msgid "Primary"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:20
-#: kallithea/templates/admin/users/user_edit_emails.html:20
+#: kallithea/templates/admin/my_account/my_account_emails.html:24
+#: kallithea/templates/admin/users/user_edit_emails.html:24
 #, python-format
 msgid "Confirm to delete this email: %s"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:26
-#: kallithea/templates/admin/users/user_edit_emails.html:26
+#: kallithea/templates/admin/my_account/my_account_emails.html:30
+#: kallithea/templates/admin/users/user_edit_emails.html:30
 msgid "No additional emails specified."
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_emails.html:38
-#: kallithea/templates/admin/users/user_edit_emails.html:38
+#: kallithea/templates/admin/my_account/my_account_emails.html:39
+#: kallithea/templates/admin/users/user_edit_emails.html:39
 msgid "New email address"
 msgstr ""
 
@@ -3020,107 +2515,71 @@
 msgid "Change Your Account Password"
 msgstr "忘記您的密碼?"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:10
+#: kallithea/templates/admin/my_account/my_account_password.html:8
 msgid "Current password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:19
-#: kallithea/templates/admin/users/user_edit_profile.html:60
+#: kallithea/templates/admin/my_account/my_account_password.html:15
+#: kallithea/templates/admin/users/user_edit_profile.html:46
 msgid "New password"
 msgstr "新密碼"
 
-#: kallithea/templates/admin/my_account/my_account_password.html:28
+#: kallithea/templates/admin/my_account/my_account_password.html:22
 msgid "Confirm new password"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_password.html:45
-#, python-format
-msgid "This account is managed with %s and the password cannot be changed here"
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:11
-msgid "Change your avatar at"
+#: kallithea/templates/admin/my_account/my_account_password.html:39
+#, python-format
+msgid ""
+"This account is managed with %s and the password cannot be changed here"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_perms.html:3
+msgid "Current IP"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:4
+#: kallithea/templates/admin/users/user_edit_profile.html:4
+msgid "Gravatar"
+msgstr ""
+
+#: kallithea/templates/admin/my_account/my_account_profile.html:10
+#: kallithea/templates/admin/users/user_edit_profile.html:10
+#, fuzzy, python-format
+#| msgid "Change your avatar at"
+msgid "Change %s avatar at"
 msgstr "修改您的頭像於"
 
 #: kallithea/templates/admin/my_account/my_account_profile.html:12
-#: kallithea/templates/admin/users/user_edit_profile.html:9
-msgid "Using"
-msgstr "使用中"
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:14
-#: kallithea/templates/admin/users/user_edit_profile.html:11
+#: kallithea/templates/admin/users/user_edit_profile.html:12
 msgid "Avatars are disabled"
 msgstr ""
 
-#: kallithea/templates/admin/my_account/my_account_profile.html:15
-msgid "Missing email, please update your user email address."
-msgstr ""
-
-#: kallithea/templates/admin/my_account/my_account_profile.html:16
-#: kallithea/templates/admin/users/user_edit_profile.html:15
-msgid "Current IP"
-msgstr ""
-
 #: kallithea/templates/admin/my_account/my_account_repos.html:1
 #, fuzzy
 msgid "Repositories You Own"
 msgstr "個版本庫"
 
-#: kallithea/templates/admin/my_account/my_account_repos.html:59
-#: kallithea/templates/admin/my_account/my_account_watched.html:59
-#: kallithea/templates/base/root.html:45
-#: kallithea/templates/bookmarks/bookmarks.html:81
-#: kallithea/templates/branches/branches.html:81
-#: kallithea/templates/journal/journal.html:200
-#: kallithea/templates/journal/journal.html:291
-#: kallithea/templates/tags/tags.html:81
-msgid "No records found."
-msgstr ""
+#: kallithea/templates/admin/my_account/my_account_repos.html:13
+#: kallithea/templates/admin/my_account/my_account_watched.html:13
+#: kallithea/templates/admin/repo_groups/repo_groups.html:39
+#: kallithea/templates/admin/repos/repo_add_base.html:6
+#: kallithea/templates/admin/repos/repo_edit_settings.html:4
+#: kallithea/templates/admin/repos/repos.html:38
+#: kallithea/templates/admin/user_groups/user_groups.html:38
+#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/files/files_browser.html:51
+msgid "Name"
+msgstr "名稱"
 
 #: kallithea/templates/admin/my_account/my_account_watched.html:1
 #, fuzzy
 msgid "Repositories You are Watching"
 msgstr "版本庫路徑"
 
-#: kallithea/templates/admin/notifications/notifications.html:5
-#: kallithea/templates/admin/notifications/notifications.html:9
-msgid "My Notifications"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:24
-msgid "All"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:25
-msgid "Comments"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:26
-#: kallithea/templates/base/base.html:183
-msgid "Pull Requests"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications.html:30
-msgid "Mark All Read"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/notifications_data.html:40
-msgid "No notifications here yet"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:5
-#: kallithea/templates/admin/notifications/show_notification.html:11
-msgid "Show Notification"
-msgstr ""
-
-#: kallithea/templates/admin/notifications/show_notification.html:9
-#: kallithea/templates/base/base.html:342
-msgid "Notifications"
-msgstr ""
-
 #: kallithea/templates/admin/permissions/permissions.html:5
 #: kallithea/templates/admin/permissions/permissions.html:11
-#: kallithea/templates/base/base.html:64
+#: kallithea/templates/base/base.html:60
 #, fuzzy
 msgid "Default Permissions"
 msgstr "預設權限"
@@ -3135,201 +2594,208 @@
 msgid "IP Whitelist"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:7
+#: kallithea/templates/admin/permissions/permissions_globals.html:4
 msgid "Anonymous access"
 msgstr "訪客權限"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:13
+#: kallithea/templates/admin/permissions/permissions_globals.html:8
+#, fuzzy
+#| msgid "Anonymous access"
+msgid "Allow anonymous access"
+msgstr "訪客權限"
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:10
 #, python-format
 msgid ""
 "Allow access to Kallithea without needing to log in. Anonymous users use "
 "%s user permissions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:25
+#: kallithea/templates/admin/permissions/permissions_globals.html:19
 msgid ""
 "All default permissions on each repository will be reset to chosen "
 "permission, note that all custom default permission on repositories will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:26
+#: kallithea/templates/admin/permissions/permissions_globals.html:20
 #, fuzzy
 msgid "Apply to all existing repositories"
 msgstr "建立版本庫"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/permissions/permissions_globals.html:23
 msgid "Permissions for the Default user on new repositories."
 msgstr ""
 
+#: kallithea/templates/admin/permissions/permissions_globals.html:27
+#: kallithea/templates/admin/repos/repo_add_base.html:28
+#: kallithea/templates/admin/repos/repo_edit_settings.html:28
+#: kallithea/templates/data_table/_dt_elements.html:134
+#: kallithea/templates/forks/fork.html:42
+msgid "Repository group"
+msgstr "版本庫群組"
+
 #: kallithea/templates/admin/permissions/permissions_globals.html:32
-#: kallithea/templates/admin/repos/repo_add_base.html:37
-#: kallithea/templates/admin/repos/repo_edit_settings.html:35
-#: kallithea/templates/data_table/_dt_elements.html:202
-#: kallithea/templates/forks/fork.html:48
-msgid "Repository group"
-msgstr "版本庫群組"
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:39
 msgid ""
 "All default permissions on each repository group will be reset to chosen "
 "permission, note that all custom default permission on repository groups "
 "will be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/admin/permissions/permissions_globals.html:33
 msgid "Apply to all existing repository groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:41
+#: kallithea/templates/admin/permissions/permissions_globals.html:36
 msgid "Permissions for the Default user on new repository groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:46
-#: kallithea/templates/data_table/_dt_elements.html:209
+#: kallithea/templates/admin/permissions/permissions_globals.html:40
+#: kallithea/templates/data_table/_dt_elements.html:141
 msgid "User group"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:53
+#: kallithea/templates/admin/permissions/permissions_globals.html:45
 msgid ""
 "All default permissions on each user group will be reset to chosen "
 "permission, note that all custom default permission on user groups will "
 "be lost"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:54
+#: kallithea/templates/admin/permissions/permissions_globals.html:46
 msgid "Apply to all existing user groups"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:55
+#: kallithea/templates/admin/permissions/permissions_globals.html:49
 msgid "Permissions for the Default user on new user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:60
+#: kallithea/templates/admin/permissions/permissions_globals.html:53
 #, fuzzy
 msgid "Top level repository creation"
 msgstr "版本庫建立"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:64
-msgid "Enable this to allow non-admins to create repositories at the top level."
-msgstr ""
-
-#: kallithea/templates/admin/permissions/permissions_globals.html:65
+#: kallithea/templates/admin/permissions/permissions_globals.html:56
+msgid ""
+"Enable this to allow non-admins to create repositories at the top level."
+msgstr ""
+
+#: kallithea/templates/admin/permissions/permissions_globals.html:57
 msgid ""
 "Note: This will also give all users API access to create repositories "
 "everywhere. That might change in future versions."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:70
+#: kallithea/templates/admin/permissions/permissions_globals.html:61
 msgid "Repository creation with group write access"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:74
+#: kallithea/templates/admin/permissions/permissions_globals.html:64
 msgid ""
 "With this, write permission to a repository group allows creating "
 "repositories inside that group. Without this, group write permissions "
 "mean nothing."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:79
+#: kallithea/templates/admin/permissions/permissions_globals.html:68
 msgid "User group creation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:83
+#: kallithea/templates/admin/permissions/permissions_globals.html:71
 msgid "Enable this to allow non-admins to create user groups."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:88
+#: kallithea/templates/admin/permissions/permissions_globals.html:75
 msgid "Repository forking"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:92
+#: kallithea/templates/admin/permissions/permissions_globals.html:78
 msgid "Enable this to allow non-admins to fork repositories."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:97
+#: kallithea/templates/admin/permissions/permissions_globals.html:82
 msgid "Registration"
 msgstr "註冊"
 
-#: kallithea/templates/admin/permissions/permissions_globals.html:105
+#: kallithea/templates/admin/permissions/permissions_globals.html:88
 msgid "External auth account activation"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:13
-#: kallithea/templates/admin/users/user_edit_ips.html:23
-#, fuzzy, python-format
+#: kallithea/templates/admin/permissions/permissions_ips.html:12
+#: kallithea/templates/admin/users/user_edit_ips.html:22
+#, python-format
 msgid "Confirm to delete this IP address: %s"
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:19
-#: kallithea/templates/admin/users/user_edit_ips.html:30
+#: kallithea/templates/admin/permissions/permissions_ips.html:18
+#: kallithea/templates/admin/users/user_edit_ips.html:29
 msgid "All IP addresses are allowed."
 msgstr ""
 
-#: kallithea/templates/admin/permissions/permissions_ips.html:30
-#: kallithea/templates/admin/users/user_edit_ips.html:42
+#: kallithea/templates/admin/permissions/permissions_ips.html:25
+#: kallithea/templates/admin/users/user_edit_ips.html:37
 msgid "New IP address"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_add.html:11
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:11
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:105
-#: kallithea/templates/admin/repo_groups/repo_groups.html:10
-#: kallithea/templates/base/base.html:61 kallithea/templates/base/base.html:80
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:89
+#: kallithea/templates/admin/repo_groups/repo_groups.html:9
+#: kallithea/templates/base/base.html:57
+#: kallithea/templates/base/base.html:76
 msgid "Repository Groups"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:33
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:8
-#: kallithea/templates/admin/user_groups/user_group_add.html:32
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:7
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:28
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:5
+#: kallithea/templates/admin/user_groups/user_group_add.html:27
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:4
 msgid "Group name"
 msgstr "群組名稱"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:51
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:26
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:42
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:19
 msgid "Group parent"
 msgstr "父群組"
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:60
-#: kallithea/templates/admin/repos/repo_add_base.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:49
+#: kallithea/templates/admin/repos/repo_add_base.html:35
 msgid "Copy parent group permissions"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_add.html:64
-#: kallithea/templates/admin/repos/repo_add_base.html:50
+#: kallithea/templates/admin/repo_groups/repo_group_add.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:38
 msgid "Copy permission set from parent repository group."
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit.html:5
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Group Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:21
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:29
 msgid "Add Child Group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:40
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:36
 #: kallithea/templates/admin/repos/repo_edit.html:12
-#: kallithea/templates/admin/repos/repo_edit.html:40
+#: kallithea/templates/admin/repos/repo_edit.html:25
 #: kallithea/templates/admin/settings/settings.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit.html:29
-#: kallithea/templates/base/base.html:67 kallithea/templates/base/base.html:151
-#: kallithea/templates/data_table/_dt_elements.html:45
-#: kallithea/templates/data_table/_dt_elements.html:49
+#: kallithea/templates/base/base.html:63
+#: kallithea/templates/base/base.html:152
 msgid "Settings"
 msgstr "設定"
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:41
-#: kallithea/templates/admin/repos/repo_edit.html:46
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:37
+#: kallithea/templates/admin/repos/repo_edit.html:31
 #: kallithea/templates/admin/user_groups/user_group_edit.html:30
 #: kallithea/templates/admin/users/user_edit.html:33
 msgid "Advanced"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit.html:42
-#: kallithea/templates/admin/repos/repo_edit.html:43
+#: kallithea/templates/admin/repo_groups/repo_group_edit.html:38
+#: kallithea/templates/admin/repos/repo_edit.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit.html:31
 msgid "Permissions"
 msgstr "權限"
@@ -3354,12 +2820,12 @@
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:9
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:7
 #: kallithea/templates/admin/users/user_edit_advanced.html:8
-#: kallithea/templates/pullrequests/pullrequest_show.html:148
+#: kallithea/templates/pullrequests/pullrequest_show.html:118
 msgid "Created on"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:190
+#: kallithea/templates/data_table/_dt_elements.html:121
 #, python-format
 msgid "Confirm to delete this group: %s with %s repository"
 msgid_plural "Confirm to delete this group: %s with %s repositories"
@@ -3369,16 +2835,36 @@
 msgid "Delete this repository group"
 msgstr ""
 
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:7
+msgid "Not visible"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:8
+#, fuzzy
+#| msgid "Disabled"
+msgid "Visible"
+msgstr "停用"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:9
+#, fuzzy
+#| msgid "No response"
+msgid "Add repos"
+msgstr "未回應"
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:10
+msgid "Add/Edit groups"
+msgstr ""
+
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:11
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:12
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:11
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:11
 msgid "User/User Group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:28
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:45
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:24
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:37
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:23
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:36
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:28
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:45
 #, fuzzy
@@ -3387,112 +2873,105 @@
 
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:34
 #: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:71
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:43
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:68
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:42
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:67
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:34
 #: kallithea/templates/admin/user_groups/user_group_edit_perms.html:71
 msgid "Revoke"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:97
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:94
-#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:97
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:81
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:77
+#: kallithea/templates/admin/user_groups/user_group_edit_perms.html:81
 msgid "Add new"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:103
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:87
 msgid "Apply to children"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:107
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:91
 msgid "Both"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:108
+#: kallithea/templates/admin/repo_groups/repo_group_edit_perms.html:92
 msgid ""
 "Set or revoke permission to all children of that group, including non-"
 "private repositories and other groups if selected."
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:38
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:29
 msgid ""
-"Enable lock-by-pulling on group. This option will be applied to all other"
-" groups and repositories inside"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
+"Enable lock-by-pulling on group. This option will be applied to all other "
+"groups and repositories inside"
+msgstr ""
+
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Remove this group"
 msgstr ""
 
-#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:53
-#, fuzzy
+#: kallithea/templates/admin/repo_groups/repo_group_edit_settings.html:46
 msgid "Confirm to delete this group"
 msgstr ""
 
 #: kallithea/templates/admin/repo_groups/repo_group_show.html:4
-#, python-format
-msgid "%s Repository group dashboard"
-msgstr ""
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:9
-msgid "Home"
-msgstr "首頁"
-
-#: kallithea/templates/admin/repo_groups/repo_group_show.html:13
-msgid "with"
-msgstr ""
+#, fuzzy, python-format
+#| msgid "Repository group"
+msgid "Repository group %s"
+msgstr "版本庫群組"
 
 #: kallithea/templates/admin/repo_groups/repo_groups.html:5
 #, fuzzy
 msgid "Repository Groups Administration"
 msgstr "版本庫管理員"
 
-#: kallithea/templates/admin/repo_groups/repo_groups.html:48
+#: kallithea/templates/admin/repo_groups/repo_groups.html:41
 #, fuzzy
 msgid "Number of Top-level Repositories"
 msgstr "建立版本庫"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:17
+#: kallithea/templates/admin/repos/repo_add_base.html:12
 #, fuzzy
 msgid "Clone remote repository"
 msgstr "私有版本庫"
 
-#: kallithea/templates/admin/repos/repo_add_base.html:22
+#: kallithea/templates/admin/repos/repo_add_base.html:16
 msgid ""
 "Optional: URL of a remote repository. If set, the repository will be "
 "created as a clone from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:32
-#: kallithea/templates/admin/repos/repo_edit_settings.html:69
-#: kallithea/templates/forks/fork.html:42
-msgid "Keep it short and to the point. Use a README file for longer descriptions."
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_add_base.html:41
-#: kallithea/templates/admin/repos/repo_edit_settings.html:39
-#: kallithea/templates/forks/fork.html:52
+#: kallithea/templates/admin/repos/repo_add_base.html:24
+#: kallithea/templates/admin/repos/repo_edit_settings.html:52
+#: kallithea/templates/forks/fork.html:37
+msgid ""
+"Keep it short and to the point. Use a README file for longer descriptions."
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_add_base.html:31
+#: kallithea/templates/admin/repos/repo_edit_settings.html:31
+#: kallithea/templates/forks/fork.html:45
 msgid "Optionally select a group to put this repository into."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:59
+#: kallithea/templates/admin/repos/repo_add_base.html:45
 msgid "Type of repository to create."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:64
-#: kallithea/templates/admin/repos/repo_edit_settings.html:44
-#: kallithea/templates/forks/fork.html:58
+#: kallithea/templates/admin/repos/repo_add_base.html:49
+#: kallithea/templates/admin/repos/repo_edit_settings.html:35
+#: kallithea/templates/forks/fork.html:50
 msgid "Landing revision"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_add_base.html:68
+#: kallithea/templates/admin/repos/repo_add_base.html:52
 msgid ""
 "Default revision for files page, downloads, full text search index and "
 "readme generation"
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_creating.html:9
-#, fuzzy, python-format
+#, python-format
 msgid "%s Creating Repository"
 msgstr ""
 
@@ -3514,26 +2993,26 @@
 msgstr ""
 
 #: kallithea/templates/admin/repos/repo_edit.html:8
-#, fuzzy, python-format
+#, python-format
 msgid "%s Repository Settings"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:49
+#: kallithea/templates/admin/repos/repo_edit.html:34
 msgid "Extra Fields"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:52
+#: kallithea/templates/admin/repos/repo_edit.html:37
 msgid "Caches"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit.html:55
+#: kallithea/templates/admin/repos/repo_edit.html:40
 msgid "Remote"
 msgstr "遠端"
 
-#: kallithea/templates/admin/repos/repo_edit.html:58
+#: kallithea/templates/admin/repos/repo_edit.html:43
 #: kallithea/templates/summary/statistics.html:8
-#: kallithea/templates/summary/summary.html:171
-#: kallithea/templates/summary/summary.html:172
+#: kallithea/templates/summary/summary.html:161
+#: kallithea/templates/summary/summary.html:162
 msgid "Statistics"
 msgstr "統計"
 
@@ -3543,70 +3022,68 @@
 msgstr "父群組"
 
 #: kallithea/templates/admin/repos/repo_edit_advanced.html:5
-#: kallithea/templates/admin/repos/repo_edit_fork.html:5
 msgid "Set"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:8
-#: kallithea/templates/admin/repos/repo_edit_fork.html:9
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:7
 msgid "Manually set this repository as a fork of another from the list."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:22
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:20
 #, fuzzy
 msgid "Public Journal Visibility"
 msgstr "公開日誌"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:29
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:27
 msgid "Remove from public journal"
 msgstr "從公開日誌移除"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:34
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:32
 #, fuzzy
 msgid "Add to Public Journal"
 msgstr "公開日誌"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:40
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:37
 msgid ""
 "All actions done in this repository will be visible to everyone in the "
 "public journal."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:46
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:42
 #, fuzzy
 msgid "Change Locking"
 msgstr "修改紀錄"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:48
 #, fuzzy
 msgid "Confirm to unlock repository."
 msgstr "確認廢止版本庫快取"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:54
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:50
 #, fuzzy
 msgid "Unlock Repository"
 msgstr "公開的版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:52
 #, python-format
 msgid "Locked by %s on %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:56
 #, fuzzy
 msgid "Confirm to lock repository."
 msgstr "確認廢止版本庫快取"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:62
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:58
 #, fuzzy
 msgid "Lock Repository"
 msgstr "公開的版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:64
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:60
 msgid "Repository is not locked"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:68
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:63
 msgid ""
 "Force locking on the repository. Works only when anonymous access is "
 "disabled. Triggering a pull locks the repository.  The user who is "
@@ -3614,32 +3091,32 @@
 "unlock it by doing a push."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:79
-#: kallithea/templates/data_table/_dt_elements.html:130
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:72
+#: kallithea/templates/data_table/_dt_elements.html:68
 #, python-format
 msgid "Confirm to delete this repository: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:81
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:74
 #, fuzzy
 msgid "Delete this Repository"
 msgstr "公開的版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
-#, fuzzy, python-format
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:77
+#, python-format
 msgid "This repository has %s fork"
 msgid_plural "This repository has %s forks"
 msgstr[0] ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:85
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:80
 msgid "Detach forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:86
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:84
 msgid "Delete forks"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_advanced.html:90
+#: kallithea/templates/admin/repos/repo_edit_advanced.html:88
 msgid ""
 "The deleted repository will be moved away and hidden until the "
 "administrator expires it. The administrator can both permanently delete "
@@ -3651,111 +3128,112 @@
 msgid "Invalidate Repository Cache"
 msgstr "確認廢止版本庫快取"
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:4
-#, fuzzy
-msgid "Confirm to invalidate repository cache."
-msgstr "確認廢止版本庫快取"
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:7
+#: kallithea/templates/admin/repos/repo_edit_caches.html:6
 msgid ""
 "Manually invalidate cache for this repository. On first access, the "
 "repository will be cached again."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_caches.html:9
+msgid "List of Cached Values"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_caches.html:12
-msgid "List of Cached Values"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_caches.html:15
 msgid "Prefix"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:16
-#: kallithea/templates/admin/repos/repo_edit_fields.html:6
+#: kallithea/templates/admin/repos/repo_edit_caches.html:13
+#: kallithea/templates/admin/repos/repo_edit_fields.html:7
 msgid "Key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_caches.html:17
-#: kallithea/templates/admin/user_groups/user_group_add.html:49
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:24
-#: kallithea/templates/admin/user_groups/user_groups.html:49
-#: kallithea/templates/admin/users/user_add.html:86
-#: kallithea/templates/admin/users/user_edit_profile.html:96
-#: kallithea/templates/admin/users/users.html:54
+#: kallithea/templates/admin/repos/repo_edit_caches.html:14
+#: kallithea/templates/admin/user_groups/user_group_add.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:17
+#: kallithea/templates/admin/user_groups/user_groups.html:41
+#: kallithea/templates/admin/users/user_add.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:74
+#: kallithea/templates/admin/users/users.html:42
 msgid "Active"
 msgstr "啟用"
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:5
+#: kallithea/templates/admin/repos/repo_edit_fields.html:6
 msgid "Label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:19
+#: kallithea/templates/admin/repos/repo_edit_fields.html:20
 #, python-format
 msgid "Confirm to delete this field: %s"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:33
+#: kallithea/templates/admin/repos/repo_edit_fields.html:31
 msgid "New field key"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:41
+#: kallithea/templates/admin/repos/repo_edit_fields.html:38
 msgid "New field label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:44
+#: kallithea/templates/admin/repos/repo_edit_fields.html:40
 msgid "Enter short label"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:50
+#: kallithea/templates/admin/repos/repo_edit_fields.html:45
 msgid "New field description"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:53
+#: kallithea/templates/admin/repos/repo_edit_fields.html:47
 msgid "Enter description of a field"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_fields.html:66
+#: kallithea/templates/admin/repos/repo_edit_fields.html:61
 msgid "Extra fields are disabled."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_permissions.html:21
+#: kallithea/templates/admin/repos/repo_edit_permissions.html:20
 #, fuzzy
 msgid "Private Repository"
 msgstr "私有版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:3
+#: kallithea/templates/admin/repos/repo_edit_remote.html:4
+#, fuzzy
+#| msgid "Go to tip of repository"
+msgid "Fork of repository"
+msgstr "Git 版本庫"
+
+#: kallithea/templates/admin/repos/repo_edit_remote.html:7
 #, fuzzy
 msgid "Remote repository URL"
 msgstr "私有版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:9
+#: kallithea/templates/admin/repos/repo_edit_remote.html:15
 #, fuzzy
 msgid "Pull Changes from Remote Repository"
 msgstr "修改於版本庫 %s"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:11
+#: kallithea/templates/admin/repos/repo_edit_remote.html:17
 #, fuzzy
 msgid "Confirm to pull changes from remote repository."
 msgstr "確認廢止版本庫快取"
 
-#: kallithea/templates/admin/repos/repo_edit_remote.html:17
+#: kallithea/templates/admin/repos/repo_edit_remote.html:23
 msgid "This repository does not have a remote repository URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 #, fuzzy
 msgid "Permanent Repository ID"
 msgstr "私有版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:11
+#: kallithea/templates/admin/repos/repo_edit_settings.html:7
 msgid "What is that?"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:13
+#: kallithea/templates/admin/repos/repo_edit_settings.html:9
 msgid "URL by id"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:14
+#: kallithea/templates/admin/repos/repo_edit_settings.html:10
 msgid ""
 "In case this repository is renamed or moved into another group the "
 "repository URL changes.\n"
@@ -3765,35 +3243,40 @@
 "other cases that you need to hardcode the URL into a 3rd party service."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:21
+#: kallithea/templates/admin/repos/repo_edit_settings.html:16
 #, fuzzy
 msgid "Remote repository"
 msgstr "私有版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:25
+#: kallithea/templates/admin/repos/repo_edit_settings.html:19
 #, fuzzy
 msgid "Repository URL"
 msgstr "版本庫"
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:29
+#: kallithea/templates/admin/repos/repo_edit_settings.html:23
 msgid ""
 "Optional: URL of a remote repository. If set, the repository can be "
 "pulled from this URL."
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:48
+#: kallithea/templates/admin/repos/repo_edit_settings.html:38
 msgid "Default revision for files page, downloads, whoosh and readme"
 msgstr ""
 
-#: kallithea/templates/admin/repos/repo_edit_settings.html:58
+#: kallithea/templates/admin/repos/repo_edit_settings.html:44
+#: kallithea/templates/pullrequests/pullrequest_show.html:131
+msgid "Type name of user"
+msgstr ""
+
+#: kallithea/templates/admin/repos/repo_edit_settings.html:45
 msgid "Change owner of this repository."
 msgstr ""
 
+#: kallithea/templates/admin/repos/repo_edit_statistics.html:5
+msgid "Processed commits"
+msgstr ""
+
 #: kallithea/templates/admin/repos/repo_edit_statistics.html:6
-msgid "Processed commits"
-msgstr ""
-
-#: kallithea/templates/admin/repos/repo_edit_statistics.html:7
 msgid "Processed progress"
 msgstr ""
 
@@ -3812,7 +3295,7 @@
 msgid "Repositories Administration"
 msgstr "版本庫管理員"
 
-#: kallithea/templates/admin/repos/repos.html:51
+#: kallithea/templates/admin/repos/repos.html:43
 msgid "State"
 msgstr ""
 
@@ -3834,7 +3317,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings.html:32
-#: kallithea/templates/admin/settings/settings_vcs.html:19
+#: kallithea/templates/admin/settings/settings_vcs.html:4
 msgid "Hooks"
 msgstr ""
 
@@ -3846,280 +3329,272 @@
 msgid "System Info"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:7
+#: kallithea/templates/admin/settings/settings_email.html:4
 msgid "Send test email to"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_email.html:15
+#: kallithea/templates/admin/settings/settings_email.html:12
 msgid "Send"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:8
+#: kallithea/templates/admin/settings/settings_global.html:4
 msgid "Site branding"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_global.html:7
+msgid "Set a custom title for your Kallithea Service."
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_global.html:12
-msgid "Set a custom title for your Kallithea Service."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:18
 msgid "HTTP authentication realm"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:27
-msgid "Analytics HTML block"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:31
+#: kallithea/templates/admin/settings/settings_global.html:19
+msgid "HTML/JavaScript/CSS customization block"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:22
 msgid ""
-"HTML with JavaScript for web analytics systems like Google Analytics or "
-"Piwik. This will be added at the bottom of every page."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_global.html:37
+"HTML (possibly with                         JavaScript and/or CSS) that "
+"will be added to the bottom                         of every page. This "
+"can be used for web analytics                         systems, but also "
+"to                         perform instance-specific customizations like "
+"adding a                         project banner at the top of every page."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_global.html:32
 msgid "ReCaptcha public key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:41
+#: kallithea/templates/admin/settings/settings_global.html:35
 msgid "Public key for reCaptcha system."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:47
+#: kallithea/templates/admin/settings/settings_global.html:40
 msgid "ReCaptcha private key"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:51
+#: kallithea/templates/admin/settings/settings_global.html:43
 msgid ""
 "Private key for reCaptcha system. Setting this value will enable captcha "
 "on registration."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_global.html:56
-#: kallithea/templates/admin/settings/settings_vcs.html:80
-#: kallithea/templates/admin/settings/settings_visual.html:116
+#: kallithea/templates/admin/settings/settings_global.html:49
+#: kallithea/templates/admin/settings/settings_vcs.html:77
+#: kallithea/templates/admin/settings/settings_visual.html:115
 #, fuzzy
 msgid "Save Settings"
 msgstr "儲存設定"
 
-#: kallithea/templates/admin/settings/settings_hooks.html:1
+#: kallithea/templates/admin/settings/settings_hooks.html:3
 msgid "Built-in Mercurial Hooks (Read-Only)"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:15
+#: kallithea/templates/admin/settings/settings_hooks.html:17
+#, fuzzy
+msgid "Custom Hooks"
+msgstr "自訂hook"
+
+#: kallithea/templates/admin/settings/settings_hooks.html:18
 msgid ""
 "Hooks can be used to trigger actions on certain events such as push / "
 "pull. They can trigger Python functions or external applications."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_hooks.html:19
-#, fuzzy
-msgid "Custom Hooks"
-msgstr "自訂hook"
-
-#: kallithea/templates/admin/settings/settings_hooks.html:67
+#: kallithea/templates/admin/settings/settings_hooks.html:60
 msgid "Failed to remove hook"
 msgstr "移除hook失敗"
 
-#: kallithea/templates/admin/settings/settings_mapping.html:6
-msgid "Rescan option"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_mapping.html:11
+#: kallithea/templates/admin/settings/settings_mapping.html:4
+#, fuzzy
+#| msgid "Description"
+msgid "Rescan options"
+msgstr "描述"
+
+#: kallithea/templates/admin/settings/settings_mapping.html:9
 msgid "Delete records of missing repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:13
+#: kallithea/templates/admin/settings/settings_mapping.html:12
 msgid ""
-"Check this option to remove all comments, pull requests and other records"
-" related to repositories that no longer exist in the filesystem."
+"Check this option to remove all comments, pull requests and other records "
+"related to repositories that no longer exist in the filesystem."
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:17
 msgid "Invalidate cache for all repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:19
+#: kallithea/templates/admin/settings/settings_mapping.html:20
 msgid "Check this to reload data and clear cache keys for all repositories."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:23
-msgid "Install Git hooks"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:25
-msgid ""
-"Verify if Kallithea's Git hooks are installed for each repository. "
-"Current hooks will be updated to the latest version."
+msgid "Install Git hooks"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_mapping.html:28
+msgid ""
+"Verify if Kallithea's Git hooks are installed for each repository. "
+"Current hooks will be updated to the latest version."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:32
 msgid "Overwrite existing Git hooks"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_mapping.html:30
-msgid ""
-"If installing Git hooks, overwrite any existing hooks, even if they do "
-"not seem to come from Kallithea. WARNING: This operation will destroy any"
-" custom git hooks you may have deployed by hand!"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_mapping.html:35
+msgid ""
+"If installing Git hooks, overwrite any existing hooks, even if they do "
+"not seem to come from Kallithea. WARNING: This operation will destroy any "
+"custom git hooks you may have deployed by hand!"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_mapping.html:41
 msgid "Rescan Repositories"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:7
+#: kallithea/templates/admin/settings/settings_search.html:4
 msgid "Index build option"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_search.html:9
+msgid "Build from scratch"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_search.html:12
-msgid "Build from scratch"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_search.html:15
 msgid ""
 "This option completely reindexeses all of the repositories for proper "
 "fulltext search capabilities."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_search.html:21
+#: kallithea/templates/admin/settings/settings_search.html:18
 msgid "Reindex"
 msgstr "重新索引"
 
-#: kallithea/templates/admin/settings/settings_system.html:4
+#: kallithea/templates/admin/settings/settings_system.html:2
+msgid "Checking for updates..."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:7
 msgid "Kallithea version"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:4
-msgid "Check for updates"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:5
-msgid "Kallithea configuration file"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_system.html:6
-msgid "Python version"
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:7
-msgid "Platform"
+msgid "Check for updates"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:8
-msgid "Git version"
+msgid "Kallithea configuration file"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:9
-msgid "Git path"
+msgid "Python version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_system.html:10
+msgid "Platform"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:11
+msgid "Git version"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:12
+msgid "Git path"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Upgrade info endpoint"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:10
+#: kallithea/templates/admin/settings/settings_system.html:13
 msgid "Note: please make sure this server can access this URL"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_system.html:15
-msgid "Checking for updates..."
-msgstr ""
-
 #: kallithea/templates/admin/settings/settings_system.html:23
 msgid "Python Packages"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:6
-msgid "Web"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:11
-msgid "Require SSL for vcs operations"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:13
-msgid ""
-"Activate to require SSL both pushing and pulling. If SSL certificate is "
-"missing, it will return an HTTP Error 406: Not Acceptable."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:24
+#: kallithea/templates/admin/settings/settings_vcs.html:9
 msgid "Show repository size after push"
 msgstr "push 後顯示版本庫大小"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:28
+#: kallithea/templates/admin/settings/settings_vcs.html:15
 msgid "Log user push commands"
 msgstr "紀錄使用者推送命令"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:32
+#: kallithea/templates/admin/settings/settings_vcs.html:21
 msgid "Log user pull commands"
 msgstr "紀錄使用者抓取命令"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:36
+#: kallithea/templates/admin/settings/settings_vcs.html:27
 msgid "Update repository after push (hg update)"
 msgstr "push後更新版本庫 (hg update)"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:42
+#: kallithea/templates/admin/settings/settings_vcs.html:33
 #, fuzzy
 msgid "Mercurial extensions"
 msgstr "Mercurial 版本庫"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:47
+#: kallithea/templates/admin/settings/settings_vcs.html:38
 msgid "Enable largefiles extension"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:51
+#: kallithea/templates/admin/settings/settings_vcs.html:44
 msgid "Enable hgsubversion extension"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:53
+#: kallithea/templates/admin/settings/settings_vcs.html:47
 msgid ""
 "Requires hgsubversion library to be installed. Enables cloning of remote "
 "Subversion repositories while converting them to Mercurial."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_vcs.html:64
+#: kallithea/templates/admin/settings/settings_vcs.html:59
 #, fuzzy
 msgid "Location of repositories"
 msgstr "建立版本庫"
 
-#: kallithea/templates/admin/settings/settings_vcs.html:69
+#: kallithea/templates/admin/settings/settings_vcs.html:64
 msgid ""
-"Click to unlock. You must restart Kallithea in order to make this setting"
-" take effect."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_vcs.html:72
+"Click to unlock. You must restart Kallithea in order to make this setting "
+"take effect."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_vcs.html:68
 msgid ""
 "Filesystem location where repositories are stored. After changing this "
 "value, a restart and rescan of the repository folder are both required."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:8
+#: kallithea/templates/admin/settings/settings_visual.html:4
 msgid "General"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:13
+#: kallithea/templates/admin/settings/settings_visual.html:9
 msgid "Use repository extra fields"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:15
+#: kallithea/templates/admin/settings/settings_visual.html:12
 msgid "Allows storing additional customized fields per repository."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:18
+#: kallithea/templates/admin/settings/settings_visual.html:17
 msgid "Show Kallithea version"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:20
-msgid "Shows or hides a version number of Kallithea displayed in the footer."
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:24
-msgid "Use Gravatars in Kallithea"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:30
+msgid ""
+"Shows or hides a version number of Kallithea displayed in the footer."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:25
+msgid "Show user Gravatars"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:29
 msgid ""
 "Gravatar URL allows you to use another avatar server application.\n"
 "                                                        The following "
@@ -4136,93 +3611,102 @@
 "network location/server host of running Kallithea server"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:42
+#: kallithea/templates/admin/settings/settings_visual.html:40
+#: kallithea/templates/summary/summary.html:63
+#, fuzzy
+msgid "Clone URL"
+msgstr "複製連結"
+
+#: kallithea/templates/admin/settings/settings_visual.html:43
 msgid ""
-"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'."
-"\n"
-"                                                        The following "
+"Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/"
+"{repo}'.\n"
+"                                                    The following "
 "variables are available:\n"
-"                                                        {scheme} 'http' "
-"or 'https' sent from running Kallithea server,\n"
-"                                                        {user}   current "
-"user username,\n"
-"                                                        {netloc} network "
+"                                                    {scheme} 'http' or "
+"'https' sent from running Kallithea server,\n"
+"                                                    {user}   current user "
+"username,\n"
+"                                                    {netloc} network "
 "location/server host of running Kallithea server,\n"
-"                                                        {repo}   full "
+"                                                    {repo}   full "
 "repository name,\n"
-"                                                        {repoid} ID of "
-"repository, can be used to contruct clone-by-id"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:55
-msgid "Dashboard items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:59
+"                                                    {repoid} ID of "
+"repository, can be used to construct clone-by-id"
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:54
+#, fuzzy
+#| msgid "repositories"
+msgid "Repository page size"
+msgstr "個版本庫"
+
+#: kallithea/templates/admin/settings/settings_visual.html:57
 msgid ""
-"Number of items displayed in the main page dashboard before pagination is"
-" shown."
+"Number of items displayed in the repository pages before pagination is "
+"shown."
+msgstr ""
+
+#: kallithea/templates/admin/settings/settings_visual.html:62
+msgid "Admin page size"
 msgstr ""
 
 #: kallithea/templates/admin/settings/settings_visual.html:65
-msgid "Admin pages items"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:69
 msgid ""
 "Number of items displayed in the admin pages grids before pagination is "
 "shown."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:75
+#: kallithea/templates/admin/settings/settings_visual.html:70
 msgid "Icons"
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:80
+#: kallithea/templates/admin/settings/settings_visual.html:75
 msgid "Show public repository icon on repositories"
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:81
+msgid "Show private repository icon on repositories"
+msgstr ""
+
 #: kallithea/templates/admin/settings/settings_visual.html:84
-msgid "Show private repository icon on repositories"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:86
 msgid "Show public/private icons next to repository names."
 msgstr ""
 
-#: kallithea/templates/admin/settings/settings_visual.html:92
+#: kallithea/templates/admin/settings/settings_visual.html:89
 #, fuzzy
 msgid "Meta Tagging"
 msgstr "設定"
 
-#: kallithea/templates/admin/settings/settings_visual.html:97
-msgid "Stylify recognised meta tags:"
-msgstr ""
-
-#: kallithea/templates/admin/settings/settings_visual.html:111
+#: kallithea/templates/admin/settings/settings_visual.html:94
 msgid ""
 "Parses meta tags from the repository description field and turns them "
 "into colored tags."
 msgstr ""
 
+#: kallithea/templates/admin/settings/settings_visual.html:98
+msgid "Stylify recognised meta tags:"
+msgstr ""
+
 #: kallithea/templates/admin/user_groups/user_group_add.html:5
 msgid "Add user group"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:10
 #: kallithea/templates/admin/user_groups/user_group_edit.html:11
-#: kallithea/templates/admin/user_groups/user_groups.html:10
-#: kallithea/templates/base/base.html:63 kallithea/templates/base/base.html:83
+#: kallithea/templates/admin/user_groups/user_groups.html:9
+#: kallithea/templates/base/base.html:59
+#: kallithea/templates/base/base.html:79
 msgid "User Groups"
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_add.html:12
-#: kallithea/templates/admin/user_groups/user_groups.html:25
+#: kallithea/templates/admin/user_groups/user_groups.html:24
 msgid "Add User Group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_add.html:44
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:19
+#: kallithea/templates/admin/user_groups/user_group_add.html:36
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:13
 msgid "Short, optional description for this user group."
 msgstr ""
 
@@ -4242,13 +3726,13 @@
 msgstr ""
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:6
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:32
-#: kallithea/templates/admin/user_groups/user_groups.html:48
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:23
+#: kallithea/templates/admin/user_groups/user_groups.html:40
 msgid "Members"
 msgstr "成員"
 
 #: kallithea/templates/admin/user_groups/user_group_edit_advanced.html:19
-#: kallithea/templates/data_table/_dt_elements.html:174
+#: kallithea/templates/data_table/_dt_elements.html:102
 #, python-format
 msgid "Confirm to delete this user group: %s"
 msgstr ""
@@ -4257,15 +3741,15 @@
 msgid "Delete this user group"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_members.html:17
+#: kallithea/templates/admin/user_groups/user_group_edit_members.html:11
 msgid "No members yet"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:40
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:26
 msgid "Chosen group members"
 msgstr ""
 
-#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:49
+#: kallithea/templates/admin/user_groups/user_group_edit_settings.html:39
 msgid "Available members"
 msgstr "啟用的成員"
 
@@ -4280,17 +3764,17 @@
 
 #: kallithea/templates/admin/users/user_add.html:10
 #: kallithea/templates/admin/users/user_edit.html:11
-#: kallithea/templates/admin/users/users.html:10
-#: kallithea/templates/base/base.html:62
+#: kallithea/templates/admin/users/users.html:9
+#: kallithea/templates/base/base.html:58
 msgid "Users"
 msgstr "使用者"
 
 #: kallithea/templates/admin/users/user_add.html:12
-#: kallithea/templates/admin/users/users.html:24
+#: kallithea/templates/admin/users/users.html:23
 msgid "Add User"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_add.html:50
+#: kallithea/templates/admin/users/user_add.html:41
 msgid "Password confirmation"
 msgstr ""
 
@@ -4309,12 +3793,12 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:7
-#: kallithea/templates/admin/users/user_edit_profile.html:42
+#: kallithea/templates/admin/users/user_edit_profile.html:32
 msgid "Source of Record"
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:9
-#: kallithea/templates/admin/users/users.html:53
+#: kallithea/templates/admin/users/users.html:41
 msgid "Last Login"
 msgstr ""
 
@@ -4323,7 +3807,7 @@
 msgstr ""
 
 #: kallithea/templates/admin/users/user_edit_advanced.html:21
-#: kallithea/templates/data_table/_dt_elements.html:158
+#: kallithea/templates/data_table/_dt_elements.html:90
 #, python-format
 msgid "Confirm to delete this user: %s"
 msgstr ""
@@ -4332,24 +3816,16 @@
 msgid "Delete this user"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_ips.html:8
+#: kallithea/templates/admin/users/user_edit_ips.html:7
 #, python-format
 msgid "Inherited from %s"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:8
-msgid "Change avatar at"
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:12
-msgid "Missing email, please update this user email address."
-msgstr ""
-
-#: kallithea/templates/admin/users/user_edit_profile.html:51
+#: kallithea/templates/admin/users/user_edit_profile.html:39
 msgid "Name in Source of Record"
 msgstr ""
 
-#: kallithea/templates/admin/users/user_edit_profile.html:69
+#: kallithea/templates/admin/users/user_edit_profile.html:53
 msgid "New password confirmation"
 msgstr ""
 
@@ -4358,223 +3834,224 @@
 msgid "Users Administration"
 msgstr "使用者管理員"
 
-#: kallithea/templates/admin/users/users.html:56
+#: kallithea/templates/admin/users/users.html:44
 msgid "Auth Type"
 msgstr ""
 
-#: kallithea/templates/base/base.html:18
+#: kallithea/templates/base/base.html:16
 #, python-format
 msgid "Server instance: %s"
 msgstr ""
 
-#: kallithea/templates/base/base.html:30
+#: kallithea/templates/base/base.html:28
 msgid "Support"
 msgstr ""
 
-#: kallithea/templates/base/base.html:90
+#: kallithea/templates/base/base.html:86
+#: kallithea/templates/base/base.html:424
 msgid "Mercurial repository"
 msgstr "Mercurial 版本庫"
 
-#: kallithea/templates/base/base.html:93
+#: kallithea/templates/base/base.html:89
+#: kallithea/templates/base/base.html:427
 msgid "Git repository"
 msgstr "Git 版本庫"
 
-#: kallithea/templates/base/base.html:119
+#: kallithea/templates/base/base.html:115
 msgid "Create Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:130
-#: kallithea/templates/data_table/_dt_elements.html:13
-#: kallithea/templates/data_table/_dt_elements.html:17
-#: kallithea/templates/summary/summary.html:8
+#: kallithea/templates/base/base.html:127
+#: kallithea/templates/summary/summary.html:9
 msgid "Summary"
 msgstr "概況"
 
-#: kallithea/templates/base/base.html:132
-#: kallithea/templates/base/base.html:134
-#: kallithea/templates/changelog/changelog.html:14
-#: kallithea/templates/data_table/_dt_elements.html:21
-#: kallithea/templates/data_table/_dt_elements.html:25
+#: kallithea/templates/base/base.html:129
+#: kallithea/templates/base/base.html:131
+#: kallithea/templates/changelog/changelog.html:16
 msgid "Changelog"
 msgstr "修改紀錄"
 
-#: kallithea/templates/base/base.html:136
-#: kallithea/templates/data_table/_dt_elements.html:29
-#: kallithea/templates/data_table/_dt_elements.html:33
+#: kallithea/templates/base/base.html:133
 #: kallithea/templates/files/files.html:11
 msgid "Files"
 msgstr "檔案"
 
-#: kallithea/templates/base/base.html:138
-msgid "Switch To"
-msgstr ""
-
-#: kallithea/templates/base/base.html:145
-#: kallithea/templates/base/base.html:147
+#: kallithea/templates/base/base.html:135
+#, python-format
+msgid "Show Pull Requests for %s"
+msgstr ""
+
+#: kallithea/templates/base/base.html:135
+msgid "Pull Requests"
+msgstr ""
+
+#: kallithea/templates/base/base.html:146
+#: kallithea/templates/base/base.html:148
 msgid "Options"
 msgstr "選項"
 
-#: kallithea/templates/base/base.html:155
-#: kallithea/templates/forks/forks_data.html:21
+#: kallithea/templates/base/base.html:156
+#: kallithea/templates/forks/forks_data.html:18
 msgid "Compare Fork"
 msgstr ""
 
-#: kallithea/templates/base/base.html:157
-#: kallithea/templates/bookmarks/bookmarks.html:56
-#: kallithea/templates/bookmarks/bookmarks_data.html:13
-#: kallithea/templates/branches/branches.html:56
-#: kallithea/templates/branches/branches_data.html:13
-#: kallithea/templates/tags/tags.html:56
-#: kallithea/templates/tags/tags_data.html:13
+#: kallithea/templates/base/base.html:158
 msgid "Compare"
 msgstr ""
 
-#: kallithea/templates/base/base.html:159
-#: kallithea/templates/base/base.html:247
+#: kallithea/templates/base/base.html:160
+#: kallithea/templates/base/base.html:322
 #: kallithea/templates/search/search.html:14
-#: kallithea/templates/search/search.html:54
+#: kallithea/templates/search/search.html:67
 msgid "Search"
 msgstr "搜尋"
 
-#: kallithea/templates/base/base.html:163
+#: kallithea/templates/base/base.html:164
 msgid "Unlock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:165
+#: kallithea/templates/base/base.html:166
 msgid "Lock"
 msgstr ""
 
-#: kallithea/templates/base/base.html:173
-msgid "Follow"
-msgstr ""
-
 #: kallithea/templates/base/base.html:174
+msgid "Follow"
+msgstr ""
+
+#: kallithea/templates/base/base.html:175
 msgid "Unfollow"
 msgstr ""
 
-#: kallithea/templates/base/base.html:177
-#: kallithea/templates/data_table/_dt_elements.html:37
-#: kallithea/templates/data_table/_dt_elements.html:41
+#: kallithea/templates/base/base.html:178
 #: kallithea/templates/forks/fork.html:9
 msgid "Fork"
 msgstr "分支"
 
-#: kallithea/templates/base/base.html:178
-#: kallithea/templates/pullrequests/pullrequest.html:88
+#: kallithea/templates/base/base.html:179
+#: kallithea/templates/pullrequests/pullrequest.html:77
 msgid "Create Pull Request"
 msgstr ""
 
-#: kallithea/templates/base/base.html:183
-#, python-format
-msgid "Show Pull Requests for %s"
-msgstr ""
-
-#: kallithea/templates/base/base.html:221
+#: kallithea/templates/base/base.html:191
+msgid "Switch To"
+msgstr ""
+
+#: kallithea/templates/base/base.html:203
+#: kallithea/templates/base/base.html:452
+msgid "No matches found"
+msgstr ""
+
+#: kallithea/templates/base/base.html:296
 msgid "Show recent activity"
 msgstr ""
 
-#: kallithea/templates/base/base.html:227
-#: kallithea/templates/base/base.html:228
+#: kallithea/templates/base/base.html:302
+#: kallithea/templates/base/base.html:303
 msgid "Public journal"
 msgstr "公開日誌"
 
-#: kallithea/templates/base/base.html:233
+#: kallithea/templates/base/base.html:308
 msgid "Show public gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:234
+#: kallithea/templates/base/base.html:309
 msgid "Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:238
+#: kallithea/templates/base/base.html:313
 msgid "All Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:240
+#: kallithea/templates/base/base.html:315
 msgid "My Public Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:241
+#: kallithea/templates/base/base.html:316
 msgid "My Private Gists"
 msgstr ""
 
-#: kallithea/templates/base/base.html:246
+#: kallithea/templates/base/base.html:321
 msgid "Search in repositories"
 msgstr ""
 
-#: kallithea/templates/base/base.html:269
-#: kallithea/templates/base/base.html:270
+#: kallithea/templates/base/base.html:344
+#: kallithea/templates/base/base.html:345
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:6
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:10
 msgid "My Pull Requests"
 msgstr ""
 
-#: kallithea/templates/base/base.html:289
+#: kallithea/templates/base/base.html:360
 msgid "Not Logged In"
 msgstr ""
 
-#: kallithea/templates/base/base.html:296
+#: kallithea/templates/base/base.html:369
 msgid "Login to Your Account"
 msgstr ""
 
-#: kallithea/templates/base/base.html:319
-msgid "Forgot password ?"
+#: kallithea/templates/base/base.html:379
+#, fuzzy
+#| msgid "Forgot password ?"
+msgid "Forgot password?"
 msgstr "忘記密碼?"
 
-#: kallithea/templates/base/base.html:346
+#: kallithea/templates/base/base.html:383
+#, fuzzy
+#| msgid "Don't have an account ?"
+msgid "Don't have an account?"
+msgstr "沒有帳號?"
+
+#: kallithea/templates/base/base.html:400
 msgid "Log Out"
 msgstr "登出"
 
-#: kallithea/templates/base/base.html:395
-msgid "No matches found"
-msgstr ""
-
 #: kallithea/templates/base/base.html:524
-msgid "Keyboard shortcuts"
+msgid "Parent rev."
 msgstr ""
 
 #: kallithea/templates/base/base.html:533
-msgid "Site-wide shortcuts"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:14
+msgid "Child rev."
+msgstr ""
+
+#: kallithea/templates/base/default_perms_box.html:11
 #, fuzzy
 msgid "Inherit defaults"
 msgstr "個版本庫"
 
-#: kallithea/templates/base/default_perms_box.html:19
+#: kallithea/templates/base/default_perms_box.html:15
 #, python-format
 msgid ""
 "Select to inherit global settings, IP whitelist and permissions from the "
 "%s."
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:28
+#: kallithea/templates/base/default_perms_box.html:23
 msgid "Create repositories"
 msgstr "建立版本庫"
 
+#: kallithea/templates/base/default_perms_box.html:27
+msgid "Select this option to allow repository creation for this user"
+msgstr ""
+
 #: kallithea/templates/base/default_perms_box.html:33
-msgid "Select this option to allow repository creation for this user"
-msgstr ""
-
-#: kallithea/templates/base/default_perms_box.html:40
 msgid "Create user groups"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:45
+#: kallithea/templates/base/default_perms_box.html:37
 msgid "Select this option to allow user group creation for this user"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:52
+#: kallithea/templates/base/default_perms_box.html:43
 msgid "Fork repositories"
 msgstr ""
 
-#: kallithea/templates/base/default_perms_box.html:57
+#: kallithea/templates/base/default_perms_box.html:47
 msgid "Select this option to allow repository forking for this user"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:13
-#: kallithea/templates/changelog/changelog.html:42
+#: kallithea/templates/changelog/changelog.html:41
 msgid "Show"
 msgstr "顯示"
 
@@ -4583,230 +4060,176 @@
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:30
-#: kallithea/templates/base/perms_summary.html:54
+#: kallithea/templates/base/perms_summary.html:55
 msgid "Permission"
 msgstr ""
 
 #: kallithea/templates/base/perms_summary.html:32
-#: kallithea/templates/base/perms_summary.html:56
+#: kallithea/templates/base/perms_summary.html:57
 msgid "Edit Permission"
 msgstr ""
 
-#: kallithea/templates/base/perms_summary.html:90
+#: kallithea/templates/base/perms_summary.html:92
 msgid "No permission defined"
 msgstr ""
 
-#: kallithea/templates/base/root.html:22
-#, fuzzy
-msgid "Add Another Comment"
-msgstr ""
-
-#: kallithea/templates/base/root.html:23
-#: kallithea/templates/data_table/_dt_elements.html:214
-msgid "Stop following this repository"
-msgstr "停止追蹤這個版本庫"
-
-#: kallithea/templates/base/root.html:24
-msgid "Start following this repository"
-msgstr "開始追蹤這個版本庫"
-
-#: kallithea/templates/base/root.html:25
-msgid "Group"
-msgstr "群組"
-
-#: kallithea/templates/base/root.html:26
-msgid "members"
-msgstr "成員"
-
-#: kallithea/templates/base/root.html:27
-msgid "Loading ..."
-msgstr ""
-
 #: kallithea/templates/base/root.html:28
-msgid "loading ..."
+msgid "Retry"
 msgstr ""
 
 #: kallithea/templates/base/root.html:29
-msgid "Search truncated"
+#: kallithea/templates/changeset/changeset_file_comment.html:65
+msgid "Submitting ..."
 msgstr ""
 
 #: kallithea/templates/base/root.html:30
-msgid "No matching files"
-msgstr ""
+#, fuzzy
+#| msgid "Enable downloads"
+msgid "Unable to post"
+msgstr "啟用下載"
 
 #: kallithea/templates/base/root.html:31
-msgid "Open New Pull Request from {0}"
+msgid "Add Another Comment"
 msgstr ""
 
 #: kallithea/templates/base/root.html:32
-msgid "Open New Pull Request for {0} &rarr; {1}"
-msgstr ""
+msgid "Stop following this repository"
+msgstr "停止追蹤這個版本庫"
 
 #: kallithea/templates/base/root.html:33
-msgid "Show Selected Changesets {0} &rarr; {1}"
-msgstr ""
+msgid "Start following this repository"
+msgstr "開始追蹤這個版本庫"
 
 #: kallithea/templates/base/root.html:34
-msgid "Selection Link"
-msgstr ""
+msgid "Group"
+msgstr "群組"
 
 #: kallithea/templates/base/root.html:35
-#: kallithea/templates/changeset/diff_block.html:8
+msgid "Loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:36
+msgid "loading ..."
+msgstr ""
+
+#: kallithea/templates/base/root.html:37
+msgid "Search truncated"
+msgstr ""
+
+#: kallithea/templates/base/root.html:38
+msgid "No matching files"
+msgstr ""
+
+#: kallithea/templates/base/root.html:39
+msgid "Open New Pull Request from {0}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:40
+msgid "Open New Pull Request for {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:41
+msgid "Show Selected Changesets {0} &rarr; {1}"
+msgstr ""
+
+#: kallithea/templates/base/root.html:42
+msgid "Selection Link"
+msgstr ""
+
+#: kallithea/templates/base/root.html:43
+#: kallithea/templates/changeset/diff_block.html:7
 #, fuzzy
 msgid "Collapse Diff"
 msgstr "檔案差異"
 
-#: kallithea/templates/base/root.html:36
+#: kallithea/templates/base/root.html:44
 #, fuzzy
 msgid "Expand Diff"
 msgstr "檔案差異"
 
-#: kallithea/templates/base/root.html:37
+#: kallithea/templates/base/root.html:45
+#, fuzzy
+msgid "No revisions"
+msgstr "修訂"
+
+#: kallithea/templates/base/root.html:46
+msgid "Type name of user or member to grant permission"
+msgstr ""
+
+#: kallithea/templates/base/root.html:47
 msgid "Failed to revoke permission"
 msgstr ""
 
-#: kallithea/templates/base/root.html:38
+#: kallithea/templates/base/root.html:48
 msgid "Confirm to revoke permission for {0}: {1} ?"
 msgstr ""
 
-#: kallithea/templates/base/root.html:39
-msgid "enabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:40
-msgid "disabled"
-msgstr ""
-
-#: kallithea/templates/base/root.html:42
+#: kallithea/templates/base/root.html:51
+#: kallithea/templates/compare/compare_diff.html:108
+msgid "Select changeset"
+msgstr ""
+
+#: kallithea/templates/base/root.html:52
 msgid "Specify changeset"
 msgstr ""
 
-#: kallithea/templates/bookmarks/bookmarks.html:5
-#, python-format
-msgid "%s Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:26
-msgid "Compare Bookmarks"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:53
-#: kallithea/templates/bookmarks/bookmarks_data.html:10
-#: kallithea/templates/branches/branches.html:53
-#: kallithea/templates/branches/branches_data.html:10
-#: kallithea/templates/changelog/changelog_summary_data.html:10
-#: kallithea/templates/tags/tags.html:53
-#: kallithea/templates/tags/tags_data.html:10
-msgid "Author"
-msgstr ""
-
-#: kallithea/templates/bookmarks/bookmarks.html:54
-#: kallithea/templates/bookmarks/bookmarks_data.html:12
-#: kallithea/templates/branches/branches.html:54
-#: kallithea/templates/branches/branches_data.html:12
-#: kallithea/templates/changelog/changelog_summary_data.html:7
-#: kallithea/templates/files/files_browser.html:32
-#: kallithea/templates/pullrequests/pullrequest.html:62
-#: kallithea/templates/pullrequests/pullrequest.html:78
-#: kallithea/templates/tags/tags.html:54
-#: kallithea/templates/tags/tags_data.html:12
-msgid "Revision"
-msgstr "修訂"
-
-#: kallithea/templates/branches/branches.html:5
-#, python-format
-msgid "%s Branches"
-msgstr ""
-
-#: kallithea/templates/branches/branches.html:26
-msgid "Compare Branches"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:6
+#: kallithea/templates/base/root.html:53
+msgid "Click to sort ascending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:54
+msgid "Click to sort descending"
+msgstr ""
+
+#: kallithea/templates/base/root.html:55
+msgid "No records found."
+msgstr ""
+
+#: kallithea/templates/base/root.html:56
+msgid "Data error."
+msgstr ""
+
+#: kallithea/templates/base/root.html:57
+msgid "Loading..."
+msgstr ""
+
+#: kallithea/templates/changelog/changelog.html:8
 #, python-format
 msgid "%s Changelog"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:21
+#: kallithea/templates/changelog/changelog.html:23
 #, python-format
 msgid "showing %d out of %d revision"
 msgid_plural "showing %d out of %d revisions"
 msgstr[0] ""
 
-#: kallithea/templates/changelog/changelog.html:49
+#: kallithea/templates/changelog/changelog.html:47
 msgid "Clear selection"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:55
+#: kallithea/templates/changelog/changelog.html:54
 #, fuzzy
 msgid "Go to tip of repository"
 msgstr "Git 版本庫"
 
-#: kallithea/templates/changelog/changelog.html:60
-#: kallithea/templates/forks/forks_data.html:19
+#: kallithea/templates/changelog/changelog.html:59
+#: kallithea/templates/forks/forks_data.html:16
 #, python-format
 msgid "Compare fork with %s"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:62
+#: kallithea/templates/changelog/changelog.html:61
 #, python-format
 msgid "Compare fork with parent repository (%s)"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:66
+#: kallithea/templates/changelog/changelog.html:65
 #: kallithea/templates/files/files.html:29
 msgid "Branch filter:"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog.html:92
-#: kallithea/templates/changelog/changelog_summary_data.html:20
-#, python-format
-msgid ""
-"Changeset status: %s\n"
-"Click to open associated pull request %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:96
-#: kallithea/templates/compare/compare_cs.html:24
-#, python-format
-msgid "Changeset status: %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:115
-#: kallithea/templates/compare/compare_cs.html:63
-msgid "Expand commit message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:124
-#: kallithea/templates/compare/compare_cs.html:30
-msgid "Changeset has comments"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:134
-#: kallithea/templates/changelog/changelog_summary_data.html:54
-#: kallithea/templates/changeset/changeset.html:94
-#: kallithea/templates/changeset/changeset_range.html:92
-#, python-format
-msgid "Bookmark %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:140
-#: kallithea/templates/changelog/changelog_summary_data.html:60
-#: kallithea/templates/changeset/changeset.html:101
-#: kallithea/templates/changeset/changeset_range.html:98
-#, python-format
-msgid "Tag %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:145
-#: kallithea/templates/changelog/changelog_summary_data.html:65
-#: kallithea/templates/changeset/changeset.html:106
-#: kallithea/templates/changeset/changeset_range.html:102
-#, python-format
-msgid "Branch %s"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog.html:309
+#: kallithea/templates/changelog/changelog.html:221
 msgid "There are no changes yet"
 msgstr "尚未有任何變更"
 
@@ -4822,7 +4245,7 @@
 
 #: kallithea/templates/changelog/changelog_details.html:6
 #: kallithea/templates/changeset/changeset.html:79
-#: kallithea/templates/changeset/diff_block.html:79
+#: kallithea/templates/changeset/diff_block.html:38
 msgid "Added"
 msgstr ""
 
@@ -4836,39 +4259,60 @@
 msgid "Affected %s files"
 msgstr ""
 
-#: kallithea/templates/changelog/changelog_summary_data.html:8
-#: kallithea/templates/files/files_add.html:60
-#: kallithea/templates/files/files_delete.html:39
-#: kallithea/templates/files/files_edit.html:63
-msgid "Commit Message"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:9
-#: kallithea/templates/pullrequests/pullrequest_data.html:17
-msgid "Age"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:11
-msgid "Refs"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:81
-msgid "Add or upload files directly via Kallithea"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:84
-#: kallithea/templates/files/files_add.html:21
-#: kallithea/templates/files/files_ypjax.html:9
-msgid "Add New File"
-msgstr ""
-
-#: kallithea/templates/changelog/changelog_summary_data.html:90
-#, fuzzy
-msgid "Push new repository"
-msgstr "私有版本庫"
-
-#: kallithea/templates/changelog/changelog_summary_data.html:98
-msgid "Existing repository?"
+#: kallithea/templates/changelog/changelog_table.html:20
+msgid "First (oldest) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:22
+msgid "Last (most recent) changeset in this list"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:24
+msgid "Position in this list of changesets"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:35
+#, python-format
+msgid ""
+"Changeset status: %s by %s\n"
+"Click to open associated pull request %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:41
+#, fuzzy, python-format
+msgid "Changeset status: %s by %s"
+msgstr "尚未有任何變更"
+
+#: kallithea/templates/changelog/changelog_table.html:60
+msgid "Expand commit message"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:76
+#, fuzzy, python-format
+#| msgid "%s committed on %s"
+msgid "%s comments"
+msgstr "%s 評論於 %s"
+
+#: kallithea/templates/changelog/changelog_table.html:80
+#: kallithea/templates/changeset/changeset.html:63
+#: kallithea/templates/changeset/changeset_range.html:84
+#, python-format
+msgid "Bookmark %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:83
+#: kallithea/templates/changeset/changeset.html:67
+#: kallithea/templates/changeset/changeset_range.html:90
+#: kallithea/templates/pullrequests/pullrequest_show.html:165
+#, python-format
+msgid "Tag %s"
+msgstr ""
+
+#: kallithea/templates/changelog/changelog_table.html:102
+#: kallithea/templates/changeset/changeset.html:71
+#: kallithea/templates/changeset/changeset_range.html:94
+#, python-format
+msgid "Branch %s"
 msgstr ""
 
 #: kallithea/templates/changeset/changeset.html:8
@@ -4876,86 +4320,77 @@
 msgid "%s Changeset"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:36
-msgid "Parent rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:42
-msgid "Child rev."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset.html:50
-#: kallithea/templates/changeset/changeset_file_comment.html:37
-#: kallithea/templates/changeset/changeset_range.html:48
+#: kallithea/templates/changeset/changeset.html:34
 msgid "Changeset status"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:54
-#: kallithea/templates/changeset/diff_block.html:27
-#: kallithea/templates/files/diff_2way.html:49
+#: kallithea/templates/changeset/changeset.html:43
+#: kallithea/templates/changeset/diff_block.html:64
+#: kallithea/templates/files/diff_2way.html:51
 msgid "Raw diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:57
+#: kallithea/templates/changeset/changeset.html:46
 msgid "Patch diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:60
-#: kallithea/templates/changeset/diff_block.html:30
-#: kallithea/templates/files/diff_2way.html:52
+#: kallithea/templates/changeset/changeset.html:49
+#: kallithea/templates/changeset/diff_block.html:66
+#: kallithea/templates/files/diff_2way.html:54
 msgid "Download diff"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:89
-#: kallithea/templates/changeset/changeset_range.html:88
+#: kallithea/templates/changeset/changeset.html:59
+#: kallithea/templates/changeset/changeset_range.html:80
 #, fuzzy
 msgid "Merge"
 msgstr "合併"
 
-#: kallithea/templates/changeset/changeset.html:123
+#: kallithea/templates/changeset/changeset.html:96
 msgid "Grafted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:129
+#: kallithea/templates/changeset/changeset.html:102
 msgid "Transplanted from:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:135
+#: kallithea/templates/changeset/changeset.html:108
 msgid "Replaced by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:149
+#: kallithea/templates/changeset/changeset.html:122
 msgid "Preceded by:"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:166
-#: kallithea/templates/compare/compare_diff.html:54
-#: kallithea/templates/pullrequests/pullrequest_show.html:318
+#: kallithea/templates/changeset/changeset.html:139
+#: kallithea/templates/compare/compare_diff.html:59
+#: kallithea/templates/pullrequests/pullrequest_show.html:290
 #, python-format
 msgid "%s file changed"
 msgid_plural "%s files changed"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset.html:168
-#: kallithea/templates/compare/compare_diff.html:56
-#: kallithea/templates/pullrequests/pullrequest_show.html:320
+#: kallithea/templates/changeset/changeset.html:141
+#: kallithea/templates/compare/compare_diff.html:61
+#: kallithea/templates/pullrequests/pullrequest_show.html:292
 #, python-format
 msgid "%s file changed with %s insertions and %s deletions"
 msgid_plural "%s files changed with %s insertions and %s deletions"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset.html:182
-#: kallithea/templates/changeset/changeset.html:195
-#: kallithea/templates/pullrequests/pullrequest_show.html:339
-#: kallithea/templates/pullrequests/pullrequest_show.html:363
+#: kallithea/templates/changeset/changeset.html:154
+#: kallithea/templates/changeset/changeset.html:173
+#: kallithea/templates/compare/compare_diff.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:309
+#: kallithea/templates/pullrequests/pullrequest_show.html:333
 msgid "Show full diff anyway"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset.html:247
-#: kallithea/templates/changeset/changeset.html:284
+#: kallithea/templates/changeset/changeset_file_comment.html:20
 #, fuzzy
-msgid "No revisions"
-msgstr "修訂"
+#| msgid "%s committed on %s"
+msgid "comment"
+msgstr "%s 評論於 %s"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:21
 msgid "on pull request"
@@ -4972,177 +4407,181 @@
 msgstr "沒有修改"
 
 #: kallithea/templates/changeset/changeset_file_comment.html:30
-#, fuzzy
 msgid "Delete comment?"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:37
+#: kallithea/templates/changeset/changeset_file_comment.html:38
+#: kallithea/templates/changeset/changeset_file_comment.html:71
 #, fuzzy
 msgid "Status change"
 msgstr "多個檔案修改"
 
-#: kallithea/templates/changeset/changeset_file_comment.html:59
-msgid "Commenting on line {1}."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:60
-#: kallithea/templates/changeset/changeset_file_comment.html:148
-#, python-format
-msgid "Comments parsed using %s syntax with %s support."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:62
-msgid "Use @username inside this text to notify another user"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:72
-#: kallithea/templates/changeset/changeset_file_comment.html:184
-msgid "Comment preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:77
-msgid "Submitting ..."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:80
-#: kallithea/templates/changeset/changeset_file_comment.html:190
+#: kallithea/templates/changeset/changeset_file_comment.html:87
+msgid "Comments are in plain text. Use @username to notify another user."
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:93
+#, fuzzy
+msgid "Set changeset status"
+msgstr "尚未有任何變更"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:95
+msgid "Vote for pull request status"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:101
+#: kallithea/templates/changeset/diff_block.html:46
+#, fuzzy
+msgid "No change"
+msgstr "沒有修改"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:114
+#, fuzzy
+msgid "Finish pull request"
+msgstr "文件內容"
+
+#: kallithea/templates/changeset/changeset_file_comment.html:117
+msgid "Close"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_file_comment.html:129
 msgid "Comment"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:82
-#: kallithea/templates/changeset/changeset_file_comment.html:191
-msgid "Preview"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "You need to be logged in to comment."
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:90
+#: kallithea/templates/changeset/changeset_file_comment.html:137
 msgid "Login now"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:94
+#: kallithea/templates/changeset/changeset_file_comment.html:141
 msgid "Hide"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:106
+#: kallithea/templates/changeset/changeset_file_comment.html:153
 #, python-format
 msgid "%d comment"
 msgid_plural "%d comments"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:107
-#, fuzzy, python-format
+#: kallithea/templates/changeset/changeset_file_comment.html:154
+#, python-format
 msgid "%d inline"
 msgid_plural "%d inline"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:108
+#: kallithea/templates/changeset/changeset_file_comment.html:155
 #, python-format
 msgid "%d general"
 msgid_plural "%d general"
 msgstr[0] ""
 
-#: kallithea/templates/changeset/changeset_file_comment.html:150
-msgid "Use @username inside this text to notify another user."
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:157
-msgid "Vote for pull request status"
-msgstr ""
-
-#: kallithea/templates/changeset/changeset_file_comment.html:159
-#, fuzzy
-msgid "Set changeset status"
-msgstr "尚未有任何變更"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:163
-#, fuzzy
-msgid "No change"
-msgstr "沒有修改"
-
-#: kallithea/templates/changeset/changeset_file_comment.html:176
-msgid "Close"
-msgstr ""
-
 #: kallithea/templates/changeset/changeset_range.html:5
 #, python-format
 msgid "%s Changesets"
 msgstr ""
 
-#: kallithea/templates/changeset/changeset_range.html:56
+#: kallithea/templates/changeset/changeset_range.html:43
+#, python-format
+msgid "Changeset status: %s"
+msgstr ""
+
+#: kallithea/templates/changeset/changeset_range.html:50
 msgid "Files affected"
 msgstr ""
 
-#: kallithea/templates/changeset/diff_block.html:21
-#: kallithea/templates/files/diff_2way.html:43
-msgid "Show full diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:24
-#: kallithea/templates/changeset/diff_block.html:98
-#: kallithea/templates/files/diff_2way.html:46
-msgid "Show full side-by-side diff for this file"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:38
-msgid "Show inline comments"
-msgstr ""
-
-#: kallithea/templates/changeset/diff_block.html:86
+#: kallithea/templates/changeset/diff_block.html:30
+msgid "No file before"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:33
+#, fuzzy
+#| msgid "file removed"
+msgid "File before"
+msgstr "移除檔案"
+
+#: kallithea/templates/changeset/diff_block.html:40
+#, fuzzy
+#| msgid "Unmodified"
+msgid "Modified"
+msgstr "未修改"
+
+#: kallithea/templates/changeset/diff_block.html:42
 #, fuzzy
 msgid "Deleted"
 msgstr "刪除"
 
-#: kallithea/templates/changeset/diff_block.html:89
+#: kallithea/templates/changeset/diff_block.html:44
 #, fuzzy
 msgid "Renamed"
 msgstr "讀"
 
-#: kallithea/templates/compare/compare_cs.html:4
+#: kallithea/templates/changeset/diff_block.html:48
+#, fuzzy, python-format
+#| msgid "Unknown revision %s"
+msgid "Unknown operation: %r"
+msgstr "未知修訂 %s"
+
+#: kallithea/templates/changeset/diff_block.html:52
+#, fuzzy
+#| msgid "New file type"
+msgid "No file after"
+msgstr "未知的存檔類型"
+
+#: kallithea/templates/changeset/diff_block.html:55
+#, fuzzy
+#| msgid "file added"
+msgid "File after"
+msgstr "檔案新增"
+
+#: kallithea/templates/changeset/diff_block.html:60
+#: kallithea/templates/files/diff_2way.html:43
+msgid "Show full diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:62
+#: kallithea/templates/files/diff_2way.html:47
+msgid "Show full side-by-side diff for this file"
+msgstr ""
+
+#: kallithea/templates/changeset/diff_block.html:72
+msgid "Show inline comments"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:5
 msgid "No changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:8
-msgid "Ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:44
-msgid "First (oldest) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:46
-msgid "Last (most recent) changeset in this list"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:48
-msgid "Position in this list of changesets"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:76
+#: kallithea/templates/compare/compare_cs.html:12
+msgid "Criss cross merge situation with multiple merge ancestors detected!"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:15
+msgid ""
+"Please merge the target branch to your branch before creating a pull "
+"request."
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:19
+msgid "Merge Ancestor"
+msgstr ""
+
+#: kallithea/templates/compare/compare_cs.html:40
 msgid "Show merge diff"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:86
-#: kallithea/templates/pullrequests/pullrequest_show.html:310
-msgid "Common ancestor"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:90
-msgid "No common ancestor found - repositories are unrelated"
-msgstr ""
-
-#: kallithea/templates/compare/compare_cs.html:98
+#: kallithea/templates/compare/compare_cs.html:54
 msgid "is"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:99
-#, fuzzy, python-format
+#: kallithea/templates/compare/compare_cs.html:55
+#, python-format
 msgid "%s changesets"
 msgstr ""
 
-#: kallithea/templates/compare/compare_cs.html:100
+#: kallithea/templates/compare/compare_cs.html:56
 #, fuzzy
 msgid "behind"
 msgstr "重新索引"
@@ -5154,127 +4593,182 @@
 msgstr ""
 
 #: kallithea/templates/compare/compare_diff.html:13
-#: kallithea/templates/compare/compare_diff.html:35
+#: kallithea/templates/compare/compare_diff.html:41
 msgid "Compare Revisions"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:33
+#: kallithea/templates/compare/compare_diff.html:39
 msgid "Swap"
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:42
+#: kallithea/templates/compare/compare_diff.html:48
 msgid "Compare revisions, branches, bookmarks, or tags."
 msgstr ""
 
-#: kallithea/templates/compare/compare_diff.html:47
-#: kallithea/templates/pullrequests/pullrequest_show.html:305
+#: kallithea/templates/compare/compare_diff.html:53
+#: kallithea/templates/pullrequests/pullrequest_show.html:278
 #, python-format
 msgid "Showing %s commit"
 msgid_plural "Showing %s commits"
 msgstr[0] ""
 
-#: kallithea/templates/compare/compare_diff.html:78
-#: kallithea/templates/compare/compare_diff.html:89
+#: kallithea/templates/compare/compare_diff.html:95
 msgid "Show full diff"
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:74
+#: kallithea/templates/data_table/_dt_elements.html:23
 msgid "Public repository"
 msgstr "公開的版本庫"
 
-#: kallithea/templates/data_table/_dt_elements.html:84
+#: kallithea/templates/data_table/_dt_elements.html:29
 msgid "Repository creation in progress..."
 msgstr ""
 
-#: kallithea/templates/data_table/_dt_elements.html:98
+#: kallithea/templates/data_table/_dt_elements.html:42
 msgid "No changesets yet"
 msgstr "尚未有任何變更"
 
-#: kallithea/templates/data_table/_dt_elements.html:105
-#: kallithea/templates/data_table/_dt_elements.html:107
+#: kallithea/templates/data_table/_dt_elements.html:48
+#: kallithea/templates/data_table/_dt_elements.html:50
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "訂閱 %s rss"
 
-#: kallithea/templates/data_table/_dt_elements.html:113
-#: kallithea/templates/data_table/_dt_elements.html:115
+#: kallithea/templates/data_table/_dt_elements.html:56
+#: kallithea/templates/data_table/_dt_elements.html:58
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "訂閱 %s atom"
 
-#: kallithea/templates/data_table/_dt_elements.html:139
+#: kallithea/templates/data_table/_dt_elements.html:76
 msgid "Creating"
 msgstr ""
 
-#: kallithea/templates/email_templates/changeset_comment.html:5
-#, python-format
-msgid "Comment from %s on %s changeset %s mentioned you"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:7
-#, python-format
-msgid "Comment from %s on %s changeset %s"
-msgstr ""
-
-#: kallithea/templates/email_templates/changeset_comment.html:12
-msgid "The changeset status was changed to"
-msgstr ""
-
-#: kallithea/templates/email_templates/main.html:6
-msgid "This is an automatic notification. Don't reply to this mail."
-msgstr ""
-
-#: kallithea/templates/email_templates/password_reset.html:4
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, python-format
+msgid "Mention in Comment on Changeset \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/changeset_comment.html:4
+#, fuzzy, python-format
+#| msgid "Set changeset status"
+msgid "Comment on Changeset \"%s\""
+msgstr "尚未有任何變更"
+
+#: kallithea/templates/email_templates/changeset_comment.html:20
+#, fuzzy
+#| msgid "Changesets"
+msgid "Changeset on"
+msgstr "變更"
+
+#: kallithea/templates/email_templates/changeset_comment.html:23
+#: kallithea/templates/email_templates/pull_request.html:22
+#: kallithea/templates/email_templates/pull_request.html:28
+#: kallithea/templates/email_templates/pull_request_comment.html:30
+#: kallithea/templates/email_templates/pull_request_comment.html:36
+#, fuzzy
+#| msgid "Branches"
+msgid "branch"
+msgstr "分支"
+
+#: kallithea/templates/email_templates/changeset_comment.html:29
+#: kallithea/templates/email_templates/pull_request.html:15
+#: kallithea/templates/email_templates/pull_request_comment.html:23
+msgid "by"
+msgstr ""
+
+#: kallithea/templates/email_templates/comment.html:27
+#, fuzzy
+#| msgid "Status change"
+msgid "Status change:"
+msgstr "多個檔案修改"
+
+#: kallithea/templates/email_templates/comment.html:33
+#, fuzzy
+#| msgid "Repository has been locked"
+msgid "The pull request has been closed."
+msgstr "儲存所已被鎖定"
+
+#: kallithea/templates/email_templates/password_reset.html:9
 #, python-format
 msgid "Hello %s"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:6
+#: kallithea/templates/email_templates/password_reset.html:16
 msgid "We have received a request to reset the password for your account."
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:7
+#: kallithea/templates/email_templates/password_reset.html:25
+msgid ""
+"This account is however managed outside this system and the password "
+"cannot be changed here."
+msgstr ""
+
+#: kallithea/templates/email_templates/password_reset.html:28
 msgid "To set a new password, click the following link"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:10
+#: kallithea/templates/email_templates/password_reset.html:33
 msgid ""
 "Should you not be able to use the link above, please type the following "
 "code into the password reset form"
 msgstr ""
 
-#: kallithea/templates/email_templates/password_reset.html:12
+#: kallithea/templates/email_templates/password_reset.html:44
 msgid ""
 "If it weren't you who requested the password reset, just disregard this "
 "message."
 msgstr ""
 
-#: kallithea/templates/email_templates/pull_request.html:5
-#, python-format
-msgid "%s mentioned you on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request.html:7
-#, python-format
-msgid "%s requested your review of %s pull request \"%s\""
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Mention on Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:4
+#, python-format
+msgid "Added as Reviewer of Pull Request %s \"%s\" by %s"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:12
+#: kallithea/templates/email_templates/pull_request_comment.html:20
+#, fuzzy
+#| msgid "Pull request %s"
+msgid "Pull request"
+msgstr "提取要求 %s"
+
+#: kallithea/templates/email_templates/pull_request.html:19
+#: kallithea/templates/email_templates/pull_request_comment.html:27
+msgid "from"
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request.html:25
+#: kallithea/templates/email_templates/pull_request_comment.html:33
+msgid "to"
 msgstr ""
 
 #: kallithea/templates/email_templates/pull_request_comment.html:4
 #, python-format
-msgid "Comment from %s on %s pull request \"%s\""
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:9
-msgid "The comment closed the pull request with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/pull_request_comment.html:11
-msgid "The comment was made with status"
-msgstr ""
-
-#: kallithea/templates/email_templates/registration.html:6
-msgid "View this user here"
-msgstr ""
+msgid "Mention in Comment on Pull Request %s \"%s\""
+msgstr ""
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s"
+msgid "Pull Request %s \"%s\" Closed"
+msgstr "提取要求 %s"
+
+#: kallithea/templates/email_templates/pull_request_comment.html:4
+#, fuzzy, python-format
+#| msgid "Pull request %s"
+msgid "Comment on Pull Request %s \"%s\""
+msgstr "提取要求 %s"
+
+#: kallithea/templates/email_templates/registration.html:22
+#, fuzzy
+#| msgid "Group name"
+msgid "Full Name"
+msgstr "群組名稱"
 
 #: kallithea/templates/files/diff_2way.html:15
 #, python-format
@@ -5292,7 +4786,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files.html:4
-#: kallithea/templates/files/files.html:80
+#: kallithea/templates/files/files.html:77
 #, python-format
 msgid "%s Files"
 msgstr ""
@@ -5302,74 +4796,76 @@
 msgid "%s Files Add"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:40
-#: kallithea/templates/files/files_edit.html:38
+#: kallithea/templates/files/files_add.html:21
+#: kallithea/templates/files/files_ypjax.html:9
+#: kallithea/templates/summary/summary.html:191
+msgid "Add New File"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:39
+#: kallithea/templates/files/files_edit.html:39
 #: kallithea/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "位置"
 
-#: kallithea/templates/files/files_add.html:42
+#: kallithea/templates/files/files_add.html:41
 msgid "Enter filename..."
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:43
+#: kallithea/templates/files/files_add.html:47
 msgid "or"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:44
+#: kallithea/templates/files/files_add.html:43
 msgid "Upload File"
 msgstr ""
 
-#: kallithea/templates/files/files_add.html:48
+#: kallithea/templates/files/files_add.html:47
 msgid "Create New File"
 msgstr ""
 
 #: kallithea/templates/files/files_add.html:53
-msgid "New file mode"
-msgstr ""
+#, fuzzy
+msgid "New file type"
+msgstr "未知的存檔類型"
 
 #: kallithea/templates/files/files_add.html:64
-#: kallithea/templates/files/files_delete.html:43
+#: kallithea/templates/files/files_delete.html:34
 #: kallithea/templates/files/files_edit.html:67
+msgid "Commit Message"
+msgstr ""
+
+#: kallithea/templates/files/files_add.html:68
+#: kallithea/templates/files/files_delete.html:40
+#: kallithea/templates/files/files_edit.html:71
 #, fuzzy
 msgid "Commit Changes"
 msgstr "遞交修改"
 
-#: kallithea/templates/files/files_browser.html:33
-msgid "Previous revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:35
-msgid "Next revision"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:41
-msgid "Follow current branch"
-msgstr ""
-
-#: kallithea/templates/files/files_browser.html:44
+#: kallithea/templates/files/files_browser.html:37
 msgid "Search File List"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:48
+#: kallithea/templates/files/files_browser.html:42
 msgid "Loading file list..."
 msgstr "載入檔案列表..."
 
-#: kallithea/templates/files/files_browser.html:61
+#: kallithea/templates/files/files_browser.html:52
+#: kallithea/templates/summary/summary.html:145
 msgid "Size"
 msgstr "大小"
 
-#: kallithea/templates/files/files_browser.html:62
+#: kallithea/templates/files/files_browser.html:53
 msgid "Last Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_browser.html:63
+#: kallithea/templates/files/files_browser.html:54
 #, fuzzy
 msgid "Last Modified"
 msgstr "最後修改"
 
-#: kallithea/templates/files/files_browser.html:64
+#: kallithea/templates/files/files_browser.html:55
 #, fuzzy
 msgid "Last Committer"
 msgstr "最後的遞交者"
@@ -5380,7 +4876,7 @@
 msgstr ""
 
 #: kallithea/templates/files/files_delete.html:12
-#: kallithea/templates/files/files_delete.html:31
+#: kallithea/templates/files/files_delete.html:30
 msgid "Delete file"
 msgstr ""
 
@@ -5393,75 +4889,80 @@
 msgid "Edit file"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:48
-#: kallithea/templates/files/files_source.html:32
+#: kallithea/templates/files/files_edit.html:51
+#: kallithea/templates/files/files_source.html:28
 msgid "Show Annotation"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:50
-#: kallithea/templates/files/files_source.html:35
-msgid "Download as Raw"
-msgstr ""
-
 #: kallithea/templates/files/files_edit.html:53
+#: kallithea/templates/files/files_source.html:31
+msgid "Download as Raw"
+msgstr ""
+
+#: kallithea/templates/files/files_edit.html:56
 msgid "Source"
 msgstr ""
 
-#: kallithea/templates/files/files_edit.html:58
-msgid "Editing file"
-msgstr ""
-
 #: kallithea/templates/files/files_history_box.html:2
 #, python-format
 msgid "%s author"
 msgid_plural "%s authors"
 msgstr[0] ""
 
-#: kallithea/templates/files/files_source.html:7
+#: kallithea/templates/files/files_source.html:6
 msgid "Diff to Revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:8
+#: kallithea/templates/files/files_source.html:7
 msgid "Show at Revision"
 msgstr ""
 
+#: kallithea/templates/files/files_source.html:9
+msgid "Show Full History"
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:10
-msgid "Show Full History"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:11
 msgid "Show Authors"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:30
+#: kallithea/templates/files/files_source.html:26
 msgid "Show Source"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:38
-#, python-format
-msgid "Edit on Branch:%s"
+#: kallithea/templates/files/files_source.html:34
+#, python-format
+msgid "Edit on Branch: %s"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:37
+msgid "Editing binary files not allowed"
+msgstr ""
+
+#: kallithea/templates/files/files_source.html:40
+msgid "Editing files allowed only when on branch head revision"
 msgstr ""
 
 #: kallithea/templates/files/files_source.html:41
-msgid "Editing binary files not allowed"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:44
-msgid "Editing files allowed only when on branch head revision"
-msgstr ""
-
-#: kallithea/templates/files/files_source.html:45
 msgid "Deleting files allowed only when on branch head revision"
 msgstr ""
 
-#: kallithea/templates/files/files_source.html:63
+#: kallithea/templates/files/files_source.html:58
 #, python-format
 msgid "Binary file (%s)"
 msgstr "二進位檔 (%s)"
 
+#: kallithea/templates/files/files_source.html:69
+#, fuzzy
+msgid "File is too big to display."
+msgstr "顯示的檔案太大"
+
+#: kallithea/templates/files/files_source.html:71
+msgid "Show full annotation anyway."
+msgstr ""
+
 #: kallithea/templates/files/files_source.html:73
-msgid "File is too big to display"
-msgstr "顯示的檔案太大"
+msgid "Show as raw."
+msgstr ""
 
 #: kallithea/templates/files/files_ypjax.html:5
 msgid "annotation"
@@ -5481,12 +4982,12 @@
 msgstr ""
 
 #: kallithea/templates/followers/followers.html:9
-#: kallithea/templates/summary/summary.html:142
-#: kallithea/templates/summary/summary.html:143
+#: kallithea/templates/summary/summary.html:130
+#: kallithea/templates/summary/summary.html:131
 msgid "Followers"
 msgstr "追蹤者"
 
-#: kallithea/templates/followers/followers_data.html:12
+#: kallithea/templates/followers/followers_data.html:9
 msgid "Started following -"
 msgstr ""
 
@@ -5495,35 +4996,35 @@
 msgid "Fork repository %s"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:27
+#: kallithea/templates/forks/fork.html:25
 msgid "Fork name"
 msgstr "分支名稱"
 
-#: kallithea/templates/forks/fork.html:62
+#: kallithea/templates/forks/fork.html:53
 msgid "Default revision for files page, downloads, whoosh, and readme."
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:68
+#: kallithea/templates/forks/fork.html:58
 msgid "Private"
 msgstr "私有"
 
-#: kallithea/templates/forks/fork.html:77
+#: kallithea/templates/forks/fork.html:66
 msgid "Copy permissions"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:81
+#: kallithea/templates/forks/fork.html:69
 msgid "Copy permissions from forked repository"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:87
+#: kallithea/templates/forks/fork.html:75
 msgid "Update after clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:91
+#: kallithea/templates/forks/fork.html:78
 msgid "Checkout source after making a clone"
 msgstr ""
 
-#: kallithea/templates/forks/fork.html:96
+#: kallithea/templates/forks/fork.html:85
 msgid "Fork this Repository"
 msgstr ""
 
@@ -5533,40 +5034,40 @@
 msgstr ""
 
 #: kallithea/templates/forks/forks.html:9
-#: kallithea/templates/summary/summary.html:148
-#: kallithea/templates/summary/summary.html:149
+#: kallithea/templates/summary/summary.html:136
+#: kallithea/templates/summary/summary.html:137
 msgid "Forks"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:17
+#: kallithea/templates/forks/forks_data.html:14
 msgid "Forked"
 msgstr ""
 
-#: kallithea/templates/forks/forks_data.html:30
+#: kallithea/templates/forks/forks_data.html:24
 msgid "There are no forks yet"
 msgstr "尚未有任何 fork"
 
-#: kallithea/templates/journal/journal.html:21
+#: kallithea/templates/journal/journal.html:22
 msgid "ATOM journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:22
+#: kallithea/templates/journal/journal.html:23
 msgid "RSS journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/journal.html:56
+#: kallithea/templates/journal/journal.html:34
 msgid "My Repositories"
 msgstr ""
 
-#: kallithea/templates/journal/journal_data.html:43
+#: kallithea/templates/journal/journal_data.html:42
 msgid "No entries yet"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:13
+#: kallithea/templates/journal/public_journal.html:10
 msgid "ATOM public journal feed"
 msgstr ""
 
-#: kallithea/templates/journal/public_journal.html:14
+#: kallithea/templates/journal/public_journal.html:11
 msgid "RSS public journal feed"
 msgstr ""
 
@@ -5575,31 +5076,36 @@
 msgid "New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:31
+#: kallithea/templates/pullrequests/pullrequest.html:26
 #: kallithea/templates/pullrequests/pullrequest_data.html:15
 #: kallithea/templates/pullrequests/pullrequest_show.html:29
-#: kallithea/templates/pullrequests/pullrequest_show.html:54
+#: kallithea/templates/pullrequests/pullrequest_show.html:52
 msgid "Title"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:34
+#: kallithea/templates/pullrequests/pullrequest.html:28
 msgid "Summarize the changes - or leave empty"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:43
-#: kallithea/templates/pullrequests/pullrequest_show.html:66
+#: kallithea/templates/pullrequests/pullrequest.html:35
+#: kallithea/templates/pullrequests/pullrequest_show.html:61
 msgid "Write a short description on this pull request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:49
+#: kallithea/templates/pullrequests/pullrequest.html:40
 msgid "Changeset flow"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:56
+#: kallithea/templates/pullrequests/pullrequest.html:46
 msgid "Origin repository"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest.html:72
+#: kallithea/templates/pullrequests/pullrequest.html:52
+#: kallithea/templates/pullrequests/pullrequest.html:68
+msgid "Revision"
+msgstr "修訂"
+
+#: kallithea/templates/pullrequests/pullrequest.html:62
 msgid "Destination repository"
 msgstr ""
 
@@ -5612,6 +5118,10 @@
 msgid "Vote"
 msgstr "遠端"
 
+#: kallithea/templates/pullrequests/pullrequest_data.html:17
+msgid "Age"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_data.html:18
 msgid "From"
 msgstr ""
@@ -5635,7 +5145,7 @@
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:37
 #: kallithea/templates/pullrequests/pullrequest_show.html:31
-#: kallithea/templates/pullrequests/pullrequest_show.html:83
+#: kallithea/templates/pullrequests/pullrequest_show.html:73
 msgid "Closed"
 msgstr ""
 
@@ -5648,7 +5158,7 @@
 msgstr ""
 
 #: kallithea/templates/pullrequests/pullrequest_data.html:70
-#, fuzzy, python-format
+#, python-format
 msgid "Confirm again to delete this pull request with %s comments"
 msgstr ""
 
@@ -5662,111 +5172,101 @@
 msgid "Pull request %s from %s#%s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:57
+#: kallithea/templates/pullrequests/pullrequest_show.html:54
 #, fuzzy
 msgid "Summarize the changes"
 msgstr "遞交修改"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:74
-msgid "Reviewer voting result"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:80
-#: kallithea/templates/pullrequests/pullrequest_show.html:81
+#: kallithea/templates/pullrequests/pullrequest_show.html:67
+msgid "Voting Result"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:70
+#: kallithea/templates/pullrequests/pullrequest_show.html:71
 msgid "Pull request status calculated from votes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:93
-msgid "Still not reviewed by"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:97
-#, python-format
-msgid "%d reviewer"
-msgid_plural "%d reviewers"
-msgstr[0] ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:99
-msgid "Pull request was reviewed by all reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:101
-#, fuzzy
-msgid "There are no reviewers"
-msgstr "沒有任何分支"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:107
+#: kallithea/templates/pullrequests/pullrequest_show.html:81
 msgid "Origin"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:113
+#: kallithea/templates/pullrequests/pullrequest_show.html:86
 #, fuzzy
 msgid "on"
 msgstr "無"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:120
+#: kallithea/templates/pullrequests/pullrequest_show.html:92
 msgid "Target"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:124
+#: kallithea/templates/pullrequests/pullrequest_show.html:95
 msgid ""
 "This is just a range of changesets and doesn't have a target or a real "
 "merge ancestor."
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:133
+#: kallithea/templates/pullrequests/pullrequest_show.html:103
 msgid "Pull changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:173
-#, fuzzy
-msgid "Update"
-msgstr "時間"
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:191
-msgid "Current revision - no change"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:213
-msgid "Pull Request Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:238
+#: kallithea/templates/pullrequests/pullrequest_show.html:136
 #, fuzzy
-msgid "Remove reviewer"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:250
-msgid "Type name of reviewer to add"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:258
-#, fuzzy
-msgid "Potential Reviewers"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:261
-msgid "Click to add the repository owner as reviewer:"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:284
+#| msgid "Registration"
+msgid "Next iteration"
+msgstr "註冊"
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:153
+msgid "Current revision - no change"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:177
+msgid ""
+"Pull request iterations do not change content once created. Select a "
+"revision to create a new iteration."
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:187
 msgid "Save Changes"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:285
-msgid "Save as New Pull Request"
-msgstr ""
-
-#: kallithea/templates/pullrequests/pullrequest_show.html:286
+#: kallithea/templates/pullrequests/pullrequest_show.html:188
+msgid "Create New Iteration with Changes"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:189
 #, fuzzy
 msgid "Cancel Changes"
 msgstr "沒有修改"
 
-#: kallithea/templates/pullrequests/pullrequest_show.html:296
+#: kallithea/templates/pullrequests/pullrequest_show.html:197
+msgid "Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:223
+msgid "Remove reviewer"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:234
+msgid "Type name of reviewer to add"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:240
+msgid "Potential Reviewers"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:243
+msgid "Click to add the repository owner as reviewer:"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show.html:268
 #, fuzzy
 msgid "Pull Request Content"
 msgstr "文件內容"
 
+#: kallithea/templates/pullrequests/pullrequest_show.html:283
+msgid "Common ancestor"
+msgstr ""
+
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:6
 #, python-format
 msgid "%s Pull Requests"
@@ -5774,44 +5274,47 @@
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:11
 #, fuzzy, python-format
-#| msgid "Pull request %s"
 msgid "Pull Requests from '%s'"
-msgstr "提取要求 %s"
+msgstr "文件內容"
 
 #: kallithea/templates/pullrequests/pullrequest_show_all.html:13
 #, python-format
 msgid "Pull Requests to '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:32
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:31
 msgid "Open New Pull Request"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:37
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:34
 #, python-format
 msgid "Show Pull Requests to %s"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:39
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:36
 #, python-format
 msgid "Show Pull Requests from '%s'"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:49
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:44
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:28
 msgid "Hide closed pull requests (only show open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_all.html:51
+#: kallithea/templates/pullrequests/pullrequest_show_all.html:46
 #: kallithea/templates/pullrequests/pullrequest_show_my.html:30
 msgid "Show closed pull requests (in addition to open pull requests)"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:35
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:34
 msgid "Pull Requests Created by Me"
 msgstr ""
 
-#: kallithea/templates/pullrequests/pullrequest_show_my.html:38
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:37
+msgid "Pull Requests Needing My Review"
+msgstr ""
+
+#: kallithea/templates/pullrequests/pullrequest_show_my.html:40
 msgid "Pull Requests I Participate In"
 msgstr ""
 
@@ -5826,29 +5329,29 @@
 msgid "Search in All Repositories"
 msgstr "Mercurial 版本庫"
 
-#: kallithea/templates/search/search.html:50
+#: kallithea/templates/search/search.html:47
 msgid "Search term"
 msgstr "搜尋關鍵字"
 
-#: kallithea/templates/search/search.html:62
+#: kallithea/templates/search/search.html:54
 msgid "Search in"
 msgstr "搜尋範圍"
 
-#: kallithea/templates/search/search.html:65
+#: kallithea/templates/search/search.html:56
 msgid "File contents"
 msgstr "文件內容"
 
-#: kallithea/templates/search/search.html:66
+#: kallithea/templates/search/search.html:57
 msgid "Commit messages"
 msgstr ""
 
-#: kallithea/templates/search/search.html:67
+#: kallithea/templates/search/search.html:58
 msgid "File names"
 msgstr "檔案名稱"
 
-#: kallithea/templates/search/search_commit.html:35
-#: kallithea/templates/search/search_content.html:21
-#: kallithea/templates/search/search_path.html:15
+#: kallithea/templates/search/search_commit.html:29
+#: kallithea/templates/search/search_content.html:17
+#: kallithea/templates/search/search_path.html:14
 msgid "Permission denied"
 msgstr "權限不足"
 
@@ -5858,80 +5361,80 @@
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:16
-#: kallithea/templates/summary/summary.html:39
+#: kallithea/templates/summary/summary.html:36
 #, python-format
 msgid "%s ATOM feed"
 msgstr ""
 
 #: kallithea/templates/summary/statistics.html:17
-#: kallithea/templates/summary/summary.html:40
+#: kallithea/templates/summary/summary.html:37
 #, python-format
 msgid "%s RSS feed"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:36
-#: kallithea/templates/summary/summary.html:100
-#: kallithea/templates/summary/summary.html:116
+#: kallithea/templates/summary/statistics.html:35
+#: kallithea/templates/summary/summary.html:91
+#: kallithea/templates/summary/summary.html:105
 msgid "Enable"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:39
+#: kallithea/templates/summary/statistics.html:38
 msgid "Stats gathered: "
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:89
-#: kallithea/templates/summary/summary.html:349
+#: kallithea/templates/summary/statistics.html:87
+#: kallithea/templates/summary/summary.html:354
 msgid "files"
 msgstr "檔案"
 
-#: kallithea/templates/summary/statistics.html:113
-#: kallithea/templates/summary/summary.html:373
+#: kallithea/templates/summary/statistics.html:111
+#: kallithea/templates/summary/summary.html:384
 msgid "Show more"
 msgstr ""
 
-#: kallithea/templates/summary/statistics.html:390
+#: kallithea/templates/summary/statistics.html:405
 msgid "commits"
 msgstr "遞交"
 
-#: kallithea/templates/summary/statistics.html:391
+#: kallithea/templates/summary/statistics.html:406
 msgid "files added"
 msgstr "多個檔案新增"
 
-#: kallithea/templates/summary/statistics.html:392
+#: kallithea/templates/summary/statistics.html:407
 msgid "files changed"
 msgstr "多個檔案修改"
 
-#: kallithea/templates/summary/statistics.html:393
+#: kallithea/templates/summary/statistics.html:408
 msgid "files removed"
 msgstr "移除多個檔案"
 
-#: kallithea/templates/summary/statistics.html:395
+#: kallithea/templates/summary/statistics.html:410
 msgid "commit"
 msgstr "遞交"
 
-#: kallithea/templates/summary/statistics.html:396
+#: kallithea/templates/summary/statistics.html:411
 msgid "file added"
 msgstr "檔案新增"
 
-#: kallithea/templates/summary/statistics.html:397
+#: kallithea/templates/summary/statistics.html:412
 msgid "file changed"
 msgstr "檔案修改"
 
-#: kallithea/templates/summary/statistics.html:398
+#: kallithea/templates/summary/statistics.html:413
 msgid "file removed"
 msgstr "移除檔案"
 
-#: kallithea/templates/summary/summary.html:4
+#: kallithea/templates/summary/summary.html:5
 #, python-format
 msgid "%s Summary"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:13
+#: kallithea/templates/summary/summary.html:14
 #, python-format
 msgid "Repository locked by %s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:15
+#: kallithea/templates/summary/summary.html:16
 msgid "Repository unlocked"
 msgstr ""
 
@@ -5939,227 +5442,151 @@
 msgid "Fork of"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:29
+#: kallithea/templates/summary/summary.html:27
 msgid "Clone from"
 msgstr "複製由"
 
-#: kallithea/templates/summary/summary.html:72
-#, fuzzy
-msgid "Clone URL"
-msgstr "複製連結"
-
-#: kallithea/templates/summary/summary.html:78
+#: kallithea/templates/summary/summary.html:68
+msgid "Show by ID"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:73
 msgid "Show by Name"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:79
-msgid "Show by ID"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:92
+#: kallithea/templates/summary/summary.html:84
 msgid "Trending files"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:108
+#: kallithea/templates/summary/summary.html:98
 msgid "Download"
 msgstr "下載"
 
-#: kallithea/templates/summary/summary.html:112
+#: kallithea/templates/summary/summary.html:101
 msgid "There are no downloads yet"
 msgstr "沒有任何下載"
 
-#: kallithea/templates/summary/summary.html:114
+#: kallithea/templates/summary/summary.html:103
 msgid "Downloads are disabled for this repository"
 msgstr "這個版本庫的下載已停用"
 
-#: kallithea/templates/summary/summary.html:120
+#: kallithea/templates/summary/summary.html:109
 msgid "Download as zip"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:113
 msgid "Check this to download archive with subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:125
+#: kallithea/templates/summary/summary.html:115
 msgid "With subrepos"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:156
-msgid "Repository Size"
-msgstr ""
-
-#: kallithea/templates/summary/summary.html:163
-#: kallithea/templates/summary/summary.html:165
+#: kallithea/templates/summary/summary.html:153
+#: kallithea/templates/summary/summary.html:155
 msgid "Feed"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:186
+#: kallithea/templates/summary/summary.html:175
 #, fuzzy
 msgid "Latest Changes"
 msgstr "多個檔案修改"
 
-#: kallithea/templates/summary/summary.html:188
+#: kallithea/templates/summary/summary.html:177
 #, fuzzy
 msgid "Quick Start"
 msgstr "快速過濾..."
 
-#: kallithea/templates/summary/summary.html:202
+#: kallithea/templates/summary/summary.html:188
+msgid "Add or upload files directly via Kallithea"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:196
+#, fuzzy
+msgid "Push new repository"
+msgstr "私有版本庫"
+
+#: kallithea/templates/summary/summary.html:204
+msgid "Existing repository?"
+msgstr ""
+
+#: kallithea/templates/summary/summary.html:222
 #, python-format
 msgid "Readme file from revision %s:%s"
 msgstr ""
 
-#: kallithea/templates/summary/summary.html:293
+#: kallithea/templates/summary/summary.html:298
 #, python-format
 msgid "Download %s as %s"
 msgstr "下載 %s 為 %s"
 
-#: kallithea/templates/tags/tags.html:5
-#, python-format
-msgid "%s Tags"
-msgstr ""
-
-#: kallithea/templates/tags/tags.html:26
-msgid "Compare Tags"
-msgstr ""
-
-#~ msgid "No comments."
-#~ msgstr ""
-
-#~ msgid "public journal"
-#~ msgstr ""
+#~ msgid "There is no index to search in. Please run whoosh indexer"
+#~ msgstr "沒有任何索引可以搜尋。請執行 whoosh 建立索引"
+
+#, fuzzy
+#~| msgid "Repository group"
+#~ msgid "Repository Group"
+#~ msgstr "版本庫群組"
+
+#~ msgid "Home"
+#~ msgstr "首頁"
+
+#~ msgid "members"
+#~ msgstr "成員"
+
+#, fuzzy
+#~| msgid "Pull request %s"
+#~ msgid "Pull request from"
+#~ msgstr "提取要求 %s"
+
+#, fuzzy
+#~| msgid "Date"
+#~ msgid "at"
+#~ msgstr "時間"
+
+#, fuzzy
+#~ msgid "There are no reviewers"
+#~ msgstr "沒有任何分支"
+
+#~ msgid "Dashboard"
+#~ msgstr "儀表板"
+
+#~ msgid "Change your avatar at"
+#~ msgstr "修改您的頭像於"
+
+#~ msgid "Using"
+#~ msgstr "使用中"
+
+#~ msgid "quick filter..."
+#~ msgstr "快速過濾..."
+
+#~ msgid "Forgot password ?"
+#~ msgstr "忘記密碼?"
 
 #~ msgid "journal"
 #~ msgstr "日誌"
 
-#~ msgid "bad captcha"
-#~ msgstr ""
-
-#~ msgid "forever"
-#~ msgstr ""
-
-#~ msgid "unmodified"
-#~ msgstr ""
-
-#~ msgid "Cannot delete %s it still contains attached forks"
-#~ msgstr ""
-
-#~ msgid "Locked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked repository"
-#~ msgstr ""
-
-#~ msgid "Unlocked"
-#~ msgstr ""
-
-#~ msgid "Locked"
-#~ msgstr ""
-
-#~ msgid "Repository has been %s"
-#~ msgstr ""
-
 #~ msgid "You can't edit this user"
 #~ msgstr "您無法編輯這位使用者"
 
-#~ msgid "compare view"
-#~ msgstr ""
-
-#~ msgid "fork name %s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s"
-#~ msgstr ""
-
 #~ msgid "No Files"
 #~ msgstr "沒有檔案"
 
-#~ msgid ""
-#~ msgstr ""
-
-#~ msgid "%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s"
-#~ msgstr ""
-
-#~ msgid "Username \"%(username)s\" is forbidden"
-#~ msgstr ""
-
 #~ msgid "invalid user name"
 #~ msgstr "無效的使用者名稱"
 
 #~ msgid "Your account is disabled"
 #~ msgstr "您的帳號已被停用"
 
-#~ msgid "Repository name %(repo)s is disallowed"
-#~ msgstr ""
-
 #~ msgid "invalid clone URL"
 #~ msgstr "無效的複製URL"
 
-#~ msgid "Invalid clone URL, provide a valid clone http(s)/svn+http(s)/ssh URL"
-#~ msgstr ""
-
-#~ msgid "email \"%(email)s\" does not exist."
-#~ msgstr ""
-
-#~ msgid "Revisions %(revs)s are already part of pull request or have set status"
-#~ msgstr ""
-
-#~ msgid "Defaults"
-#~ msgstr ""
-
-#~ msgid "never"
-#~ msgstr ""
-
-#~ msgid "My Emails"
-#~ msgstr ""
-
-#~ msgid "Watched"
-#~ msgstr ""
-
 #~ msgid "My Permissions"
 #~ msgstr "權限"
 
-#~ msgid "expires"
-#~ msgstr ""
-
-#~ msgid "Confirm to reset this api key: %s"
-#~ msgstr ""
-
-#~ msgid "reset"
-#~ msgstr ""
-
-#~ msgid "expired"
-#~ msgstr ""
-
-#~ msgid "Confirm to remove this api key: %s"
-#~ msgstr ""
-
-#~ msgid "remove"
-#~ msgstr ""
-
-#~ msgid "No additional api keys specified"
-#~ msgstr ""
-
-#~ msgid "New api key"
-#~ msgstr ""
-
-#~ msgid "current IP"
-#~ msgstr ""
-
 #~ msgid "Permissions Administration"
 #~ msgstr "權限管理員"
 
-#~ msgid "Overview"
-#~ msgstr ""
-
-#~ msgid "Overwrite existing settings"
-#~ msgstr ""
-
-#~ msgid "Default IP Whitelist for All Users"
-#~ msgstr ""
-
-#~ msgid "Confirm to delete this ip: %s"
-#~ msgstr ""
-
 #~ msgid "Default User Permissions Overview"
 #~ msgstr "預設權限"
 
@@ -6175,195 +5602,29 @@
 #~ msgid "admin"
 #~ msgstr "管理員"
 
-#~ msgid "user/user group"
-#~ msgstr ""
-
-#~ msgid "default"
-#~ msgstr ""
-
-#~ msgid "revoke"
-#~ msgstr ""
-
-#~ msgid "delegated admin"
-#~ msgstr ""
-
-#~ msgid "apply to children"
-#~ msgstr ""
-
-#~ msgid "Import existing repository ?"
-#~ msgstr ""
-
-#~ msgid "Optional URL from which repository should be cloned."
-#~ msgstr ""
-
 #~ msgid "Remote URL"
 #~ msgstr "複製連結"
 
-#~ msgid "Pull Changes from Remote Location"
-#~ msgstr ""
-
-#~ msgid "Confirm to pull changes from remote side."
-#~ msgstr ""
-
-#~ msgid "This repository does not have a remote URL set."
-#~ msgstr ""
-
-#~ msgid "Non-changeable id"
-#~ msgstr ""
-
 #~ msgid "edit"
 #~ msgstr "編輯"
 
-#~ msgid "new value"
-#~ msgstr ""
-
-#~ msgid "URL used for doing remote pulls."
-#~ msgstr ""
-
-#~ msgid "Email prefix"
-#~ msgstr ""
-
-#~ msgid "Kallithea email from"
-#~ msgstr ""
-
-#~ msgid "Error email from"
-#~ msgstr ""
-
-#~ msgid "Error email recipients"
-#~ msgstr ""
-
-#~ msgid "SMTP server"
-#~ msgstr ""
-
-#~ msgid "SMTP username"
-#~ msgstr ""
-
-#~ msgid "SMTP password"
-#~ msgstr ""
-
-#~ msgid "SMTP port"
-#~ msgstr ""
-
-#~ msgid "SMTP use TLS"
-#~ msgstr ""
-
-#~ msgid "SMTP use SSL"
-#~ msgstr ""
-
-#~ msgid "SMTP auth"
-#~ msgstr ""
-
-#~ msgid "Destroy old data"
-#~ msgstr ""
-
-#~ msgid "check for updates"
-#~ msgstr ""
-
-#~ msgid "Meta-Tagging"
-#~ msgstr ""
-
 #~ msgid "Default permissions"
 #~ msgstr "預設權限"
 
-#~ msgid "user groups"
-#~ msgstr ""
-
-#~ msgid "Inherit from defaults"
-#~ msgstr ""
-
-#~ msgid "show"
-#~ msgstr ""
-
-#~ msgid "Push new repo"
-#~ msgstr ""
-
-#~ msgid "parent rev."
-#~ msgstr ""
-
-#~ msgid "child rev."
-#~ msgstr ""
-
-#~ msgid "no revisions"
-#~ msgstr ""
-
-#~ msgid "Status change from pull request"
-#~ msgstr ""
-
-#~ msgid "Comment from pull request"
-#~ msgstr ""
-
-#~ msgid "Status change on changeset"
-#~ msgstr ""
-
-#~ msgid "Comment on changeset"
-#~ msgstr ""
-
-#~ msgid "revision"
-#~ msgstr ""
-
-#~ msgid "Mimetype"
-#~ msgstr ""
-
 #~ msgid "My Repos"
 #~ msgstr "空的版本庫"
 
-#~ msgid "Latest vote: %s"
-#~ msgstr ""
-
-#~ msgid "Nobody voted"
-#~ msgstr ""
-
-#~ msgid "%s Pull Request #%s"
-#~ msgstr ""
-
-#~ msgid "Pull request #%s from %s#%s"
-#~ msgstr ""
-
-#~ msgid "owner"
-#~ msgstr ""
-
-#~ msgid "reviewer"
-#~ msgstr ""
-
-#~ msgid "with subrepos"
-#~ msgstr ""
-
-#~ msgid "Your new password"
-#~ msgstr ""
-
-#~ msgid "Your new Kallithea password:%s"
-#~ msgstr ""
-
-#~ msgid "Open New Pull Request for Selected Changesets"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changesets __S &rarr; __E"
-#~ msgstr ""
-
-#~ msgid "Show Selected Changeset __S"
-#~ msgstr ""
-
-#~ msgid "We received a request to create a new password for your account."
-#~ msgstr ""
-
-#~ msgid "You can generate it by clicking following URL"
-#~ msgstr ""
-
-#~ msgid "Please ignore this email if you did not request a new password ."
-#~ msgstr ""
-
-#~ msgid "Created by"
-#~ msgstr ""
-
-#~ msgid "You can only delete files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "You can only edit files with revision being a valid branch "
-#~ msgstr ""
-
-#~ msgid "Changeset not found"
-#~ msgstr ""
-
-#~ msgid "Pull Requests from %s'"
-#~ msgstr ""
-
+#~ msgid "Confirm to invalidate repository cache."
+#~ msgstr "確認廢止版本庫快取"
+
+#~ msgid "Closing."
+#~ msgstr "關閉中。"
+
+#~ msgid "There are no branches yet"
+#~ msgstr "沒有任何分支"
+
+#~ msgid "There are no tags yet"
+#~ msgstr "沒有任何標籤"
+
+#~ msgid "Update"
+#~ msgstr "時間"
--- a/kallithea/lib/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -24,30 +24,3 @@
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
 """
-
-import os
-
-def get_current_revision(quiet=False):
-    """
-    Returns tuple of (number, id) from repository containing this package
-    or None if repository could not be found.
-
-    :param quiet: prints error for fetching revision if True
-    """
-
-    try:
-        from kallithea.lib.vcs import get_repo
-        from kallithea.lib.vcs.utils.helpers import get_scm
-        repopath = os.path.abspath(os.path.join(os.path.dirname(__file__),
-                                                '..', '..'))
-        scm = get_scm(repopath)[0]
-        repo = get_repo(path=repopath, alias=scm)
-        wk_dir = repo.workdir
-        cur_rev = wk_dir.get_changeset()
-        return (cur_rev.revision, cur_rev.short_id)
-    except Exception as err:
-        if not quiet:
-            print ("WARNING: Cannot retrieve kallithea's revision. "
-                   "disregard this if you don't know what that means. "
-                   "Original error was: %s" % err)
-        return None
--- a/kallithea/lib/annotate.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/annotate.py	Sun Mar 31 21:28:56 2019 +0200
@@ -48,7 +48,7 @@
     :param headers: dictionary with headers (keys are whats in ``order``
       parameter)
     """
-    from kallithea.lib.utils import get_custom_lexer
+    from kallithea.lib.pygmentsutils import get_custom_lexer
     options['linenos'] = True
     formatter = AnnotateHtmlFormatter(filenode=filenode, order=order,
         headers=headers,
@@ -68,7 +68,7 @@
         following function as ``annotate_from_changeset_func``::
 
             def changeset_to_anchor(changeset):
-                return '<a href="/changesets/%s/">%s</a>\n' %\
+                return '<a href="/changesets/%s/">%s</a>\n' % \
                        (changeset.id, changeset.id)
 
         :param annotate_from_changeset_func: see above
@@ -149,7 +149,7 @@
             for i in range(fl, fl + lncount):
                 if i % st == 0:
                     if aln:
-                        lines.append('<a href="#%s-%d">%*d</a>' \
+                        lines.append('<a href="#%s-%d">%*d</a>'
                                      % (la, i, mw, i))
                     else:
                         lines.append('%*d' % (mw, i))
@@ -158,7 +158,7 @@
             ls = '\n'.join(lines)
 
 #        annotate_changesets = [tup[1] for tup in self.filenode.annotate]
-##        TODO: not sure what that fixes
+#        # TODO: not sure what that fixes
 #        # If pygments cropped last lines break we need do that too
 #        ln_cs = len(annotate_changesets)
 #        ln_ = len(ls.splitlines())
--- a/kallithea/lib/app_globals.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/app_globals.py	Sun Mar 31 21:28:56 2019 +0200
@@ -25,9 +25,8 @@
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
 """
-
-from beaker.cache import CacheManager
-from beaker.util import parse_cache_config_options
+import tg
+from tg import config
 
 
 class Globals(object):
@@ -36,11 +35,18 @@
     life of the application
     """
 
-    def __init__(self, config):
+    def __init__(self):
         """One instance of Globals is created during application
         initialization and is available during requests via the
         'app_globals' variable
 
         """
-        self.cache = CacheManager(**parse_cache_config_options(config))
         self.available_permissions = None   # propagated after init_model
+
+    @property
+    def cache(self):
+        return tg.cache
+
+    @property
+    def mako_lookup(self):
+        return config['render_functions']['mako'].normal_loader
--- a/kallithea/lib/auth.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth.py	Sun Mar 31 21:28:56 2019 +0200
@@ -24,7 +24,6 @@
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
 """
-import time
 import os
 import logging
 import traceback
@@ -34,17 +33,16 @@
 
 from decorator import decorator
 
-from pylons import url, request, session
-from pylons.controllers.util import abort, redirect
-from pylons.i18n.translation import _
+from tg import request, session
+from tg.i18n import ugettext as _
 from webhelpers.pylonslib import secure_form
-from sqlalchemy import or_
 from sqlalchemy.orm.exc import ObjectDeletedError
 from sqlalchemy.orm import joinedload
+from webob.exc import HTTPFound, HTTPBadRequest, HTTPForbidden, HTTPMethodNotAllowed
 
 from kallithea import __platform__, is_windows, is_unix
+from kallithea.config.routing import url
 from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.model import meta
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
 from kallithea.model.db import User, Repository, Permission, \
@@ -52,7 +50,7 @@
     RepoGroup, UserGroupRepoGroupToPerm, UserIpMap, UserGroupUserGroupToPerm, \
     UserGroup, UserApiKeys
 
-from kallithea.lib.utils2 import safe_unicode, aslist
+from kallithea.lib.utils2 import safe_str, safe_unicode, aslist
 from kallithea.lib.utils import get_repo_slug, get_repo_group_slug, \
     get_user_group_slug, conditional_cache
 from kallithea.lib.caching_query import FromCache
@@ -93,61 +91,49 @@
         return ''.join(l)
 
 
-class KallitheaCrypto(object):
-
-    @classmethod
-    def hash_string(cls, str_):
-        """
-        Cryptographic function used for password hashing based on pybcrypt
-        or Python's own OpenSSL wrapper on windows
-
-        :param password: password to hash
-        """
-        if is_windows:
-            return hashlib.sha256(str_).hexdigest()
-        elif is_unix:
-            import bcrypt
-            return bcrypt.hashpw(str_, bcrypt.gensalt(10))
-        else:
-            raise Exception('Unknown or unsupported platform %s' \
-                            % __platform__)
+def get_crypt_password(password):
+    """
+    Cryptographic function used for password hashing based on pybcrypt
+    or Python's own OpenSSL wrapper on windows
 
-    @classmethod
-    def hash_check(cls, password, hashed):
-        """
-        Checks matching password with it's hashed value, runs different
-        implementation based on platform it runs on
-
-        :param password: password
-        :param hashed: password in hashed form
-        """
-
-        try:
-            password = str(password)
-        except UnicodeEncodeError:
-            log.warning('rejecting non-ascii password')
-            return False
-        if is_windows:
-            return hashlib.sha256(password).hexdigest() == hashed
-        elif is_unix:
-            import bcrypt
-            return bcrypt.hashpw(password, hashed) == hashed
-        else:
-            raise Exception('Unknown or unsupported platform %s' \
-                            % __platform__)
-
-
-def get_crypt_password(password):
-    return KallitheaCrypto.hash_string(password)
+    :param password: password to hash
+    """
+    if is_windows:
+        return hashlib.sha256(password).hexdigest()
+    elif is_unix:
+        import bcrypt
+        return bcrypt.hashpw(safe_str(password), bcrypt.gensalt(10))
+    else:
+        raise Exception('Unknown or unsupported platform %s'
+                        % __platform__)
 
 
 def check_password(password, hashed):
-    return KallitheaCrypto.hash_check(password, hashed)
+    """
+    Checks matching password with it's hashed value, runs different
+    implementation based on platform it runs on
+
+    :param password: password
+    :param hashed: password in hashed form
+    """
 
+    if is_windows:
+        return hashlib.sha256(password).hexdigest() == hashed
+    elif is_unix:
+        import bcrypt
+        try:
+            return bcrypt.checkpw(safe_str(password), safe_str(hashed))
+        except ValueError as e:
+            # bcrypt will throw ValueError 'Invalid hashed_password salt' on all password errors
+            log.error('error from bcrypt checking password: %s', e)
+            return False
+    else:
+        raise Exception('Unknown or unsupported platform %s'
+                        % __platform__)
 
 
 def _cached_perms_data(user_id, user_is_admin, user_inherit_default_permissions,
-                       explicit, algo):
+                       explicit):
     RK = 'repositories'
     GK = 'repositories_groups'
     UK = 'user_groups'
@@ -158,14 +144,9 @@
     def _choose_perm(new_perm, cur_perm):
         new_perm_val = PERM_WEIGHTS[new_perm]
         cur_perm_val = PERM_WEIGHTS[cur_perm]
-        if algo == 'higherwin':
-            if new_perm_val > cur_perm_val:
-                return new_perm
-            return cur_perm
-        elif algo == 'lowerwin':
-            if new_perm_val < cur_perm_val:
-                return new_perm
-            return cur_perm
+        if new_perm_val > cur_perm_val:
+            return new_perm
+        return cur_perm
 
     #======================================================================
     # fetch default permissions
@@ -209,8 +190,8 @@
     #==================================================================
 
     # default global permissions taken from the default user
-    default_global_perms = UserToPerm.query()\
-        .filter(UserToPerm.user_id == default_user_id)\
+    default_global_perms = UserToPerm.query() \
+        .filter(UserToPerm.user_id == default_user_id) \
         .options(joinedload(UserToPerm.permission))
 
     for perm in default_global_perms:
@@ -219,10 +200,10 @@
     # defaults for repositories, taken from default user
     for perm in default_repo_perms:
         r_k = perm.UserRepoToPerm.repository.repo_name
-        if perm.Repository.private and not (perm.Repository.user_id == user_id):
+        if perm.Repository.private and not (perm.Repository.owner_id == user_id):
             # disable defaults for private repos,
             p = 'repository.none'
-        elif perm.Repository.user_id == user_id:
+        elif perm.Repository.owner_id == user_id:
             # set admin if owner
             p = 'repository.admin'
         else:
@@ -256,15 +237,15 @@
 
     # USER GROUPS comes first
     # user group global permissions
-    user_perms_from_users_groups = Session().query(UserGroupToPerm)\
-        .options(joinedload(UserGroupToPerm.permission))\
+    user_perms_from_users_groups = Session().query(UserGroupToPerm) \
+        .options(joinedload(UserGroupToPerm.permission)) \
         .join((UserGroupMember, UserGroupToPerm.users_group_id ==
-               UserGroupMember.users_group_id))\
-        .filter(UserGroupMember.user_id == user_id)\
+               UserGroupMember.users_group_id)) \
+        .filter(UserGroupMember.user_id == user_id) \
         .join((UserGroup, UserGroupMember.users_group_id ==
-               UserGroup.users_group_id))\
-        .filter(UserGroup.users_group_active == True)\
-        .order_by(UserGroupToPerm.users_group_id)\
+               UserGroup.users_group_id)) \
+        .filter(UserGroup.users_group_active == True) \
+        .order_by(UserGroupToPerm.users_group_id) \
         .all()
     # need to group here by groups since user can be in more than
     # one group
@@ -274,24 +255,24 @@
     for gr, perms in _grouped:
         # since user can be in multiple groups iterate over them and
         # select the lowest permissions first (more explicit)
-        ##TODO: do this^^
+        # TODO: do this^^
         if not gr.inherit_default_permissions:
             # NEED TO IGNORE all configurable permissions and
             # replace them with explicitly set
-            permissions[GLOBAL] = permissions[GLOBAL]\
+            permissions[GLOBAL] = permissions[GLOBAL] \
                                             .difference(_configurable)
         for perm in perms:
             permissions[GLOBAL].add(perm.permission.permission_name)
 
     # user specific global permissions
-    user_perms = Session().query(UserToPerm)\
-            .options(joinedload(UserToPerm.permission))\
+    user_perms = Session().query(UserToPerm) \
+            .options(joinedload(UserToPerm.permission)) \
             .filter(UserToPerm.user_id == user_id).all()
 
     if not user_inherit_default_permissions:
         # NEED TO IGNORE all configurable permissions and
         # replace them with explicitly set
-        permissions[GLOBAL] = permissions[GLOBAL]\
+        permissions[GLOBAL] = permissions[GLOBAL] \
                                         .difference(_configurable)
 
         for perm in user_perms:
@@ -303,23 +284,22 @@
     #======================================================================
     #======================================================================
     # check if user is part of user groups for this repository and
-    # fill in his permission from it. _choose_perm decides of which
-    # permission should be selected based on selected method
+    # fill in his permission from it.
     #======================================================================
 
     # user group for repositories permissions
     user_repo_perms_from_users_groups = \
-     Session().query(UserGroupRepoToPerm, Permission, Repository,)\
+     Session().query(UserGroupRepoToPerm, Permission, Repository,) \
         .join((Repository, UserGroupRepoToPerm.repository_id ==
-               Repository.repo_id))\
+               Repository.repo_id)) \
         .join((Permission, UserGroupRepoToPerm.permission_id ==
-               Permission.permission_id))\
+               Permission.permission_id)) \
         .join((UserGroup, UserGroupRepoToPerm.users_group_id ==
-               UserGroup.users_group_id))\
-        .filter(UserGroup.users_group_active == True)\
+               UserGroup.users_group_id)) \
+        .filter(UserGroup.users_group_active == True) \
         .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
-               UserGroupMember.users_group_id))\
-        .filter(UserGroupMember.user_id == user_id)\
+               UserGroupMember.users_group_id)) \
+        .filter(UserGroupMember.user_id == user_id) \
         .all()
 
     multiple_counter = collections.defaultdict(int)
@@ -329,7 +309,7 @@
         p = perm.Permission.permission_name
         cur_perm = permissions[RK][r_k]
 
-        if perm.Repository.user_id == user_id:
+        if perm.Repository.owner_id == user_id:
             # set admin if owner
             p = 'repository.admin'
         else:
@@ -344,7 +324,7 @@
         r_k = perm.UserRepoToPerm.repository.repo_name
         cur_perm = permissions[RK][r_k]
         # set admin if owner
-        if perm.Repository.user_id == user_id:
+        if perm.Repository.owner_id == user_id:
             p = 'repository.admin'
         else:
             p = perm.Permission.permission_name
@@ -357,21 +337,20 @@
     #======================================================================
     #======================================================================
     # check if user is part of user groups for this repository groups and
-    # fill in his permission from it. _choose_perm decides of which
-    # permission should be selected based on selected method
+    # fill in his permission from it.
     #======================================================================
     # user group for repo groups permissions
     user_repo_group_perms_from_users_groups = \
-     Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
-     .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
+     Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup) \
+     .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)) \
      .join((Permission, UserGroupRepoGroupToPerm.permission_id
-            == Permission.permission_id))\
+            == Permission.permission_id)) \
      .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id ==
-            UserGroup.users_group_id))\
-     .filter(UserGroup.users_group_active == True)\
+            UserGroup.users_group_id)) \
+     .filter(UserGroup.users_group_active == True) \
      .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
-            == UserGroupMember.users_group_id))\
-     .filter(UserGroupMember.user_id == user_id)\
+            == UserGroupMember.users_group_id)) \
+     .filter(UserGroupMember.user_id == user_id) \
      .all()
 
     multiple_counter = collections.defaultdict(int)
@@ -399,17 +378,17 @@
     #======================================================================
     # user group for user group permissions
     user_group_user_groups_perms = \
-     Session().query(UserGroupUserGroupToPerm, Permission, UserGroup)\
+     Session().query(UserGroupUserGroupToPerm, Permission, UserGroup) \
      .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
-            == UserGroup.users_group_id))\
+            == UserGroup.users_group_id)) \
      .join((Permission, UserGroupUserGroupToPerm.permission_id
-            == Permission.permission_id))\
+            == Permission.permission_id)) \
      .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
-            == UserGroupMember.users_group_id))\
-     .filter(UserGroupMember.user_id == user_id)\
+            == UserGroupMember.users_group_id)) \
+     .filter(UserGroupMember.user_id == user_id) \
      .join((UserGroup, UserGroupMember.users_group_id ==
-            UserGroup.users_group_id), aliased=True, from_joinpoint=True)\
-     .filter(UserGroup.users_group_active == True)\
+            UserGroup.users_group_id), aliased=True, from_joinpoint=True) \
+     .filter(UserGroup.users_group_active == True) \
      .all()
 
     multiple_counter = collections.defaultdict(int)
@@ -422,7 +401,7 @@
             p = _choose_perm(p, cur_perm)
         permissions[UK][g_k] = p
 
-    #user explicit permission for user groups
+    # user explicit permission for user groups
     user_user_groups_perms = Permission.get_default_user_group_perms(user_id)
     for perm in user_user_groups_perms:
         u_k = perm.UserUserGroupToPerm.user_group.users_group_name
@@ -450,7 +429,7 @@
     else:
         msg = 'controller: %s is *NOT* in API whitelist' % (controller_name)
         if api_key:
-            #if we use API key and don't have access it's a warning
+            # if we use API key and don't have access it's a warning
             log.warning(msg)
         else:
             log.debug(msg)
@@ -470,24 +449,33 @@
     access to Kallithea is enabled, the default user is loaded instead.
 
     `AuthUser` does not by itself authenticate users and the constructor
-    sets the `is_authenticated` field to False, except when falling back
-    to the default anonymous user (if enabled). It's up to other parts
+    sets the `is_authenticated` field to False. It's up to other parts
     of the code to check e.g. if a supplied password is correct, and if
     so, set `is_authenticated` to True.
 
     However, `AuthUser` does refuse to load a user that is not `active`.
+
+    Note that Kallithea distinguishes between the default user (an actual
+    user in the database with username "default") and "no user" (no actual
+    User object, AuthUser filled with blank values and username "None").
+
+    If the default user is active, that will always be used instead of
+    "no user". On the other hand, if the default user is disabled (and
+    there is no login information), we instead get "no user"; this should
+    only happen on the login page (as all other requests are redirected).
+
+    `is_default_user` specifically checks if the AuthUser is the user named
+    "default". Use `is_anonymous` to check for both "default" and "no user".
     """
 
-    def __init__(self, user_id=None, dbuser=None,
+    def __init__(self, user_id=None, dbuser=None, authenticating_api_key=None,
             is_external_auth=False):
 
         self.is_authenticated = False
         self.is_external_auth = is_external_auth
+        self.authenticating_api_key = authenticating_api_key
 
-        user_model = UserModel()
-        self.anonymous_user = User.get_default_user(cache=True)
-
-        # These attributes will be overriden by fill_data, below, unless the
+        # These attributes will be overridden by fill_data, below, unless the
         # requested user cannot be found and the default anonymous user is
         # not enabled.
         self.user_id = None
@@ -502,7 +490,7 @@
         # Look up database user, if necessary.
         if user_id is not None:
             log.debug('Auth User lookup by USER ID %s', user_id)
-            dbuser = user_model.get(user_id)
+            dbuser = UserModel().get(user_id)
         else:
             # Note: dbuser is allowed to be None.
             log.debug('Auth User lookup by database user %s', dbuser)
@@ -510,12 +498,15 @@
         is_user_loaded = self._fill_data(dbuser)
 
         # If user cannot be found, try falling back to anonymous.
-        if not is_user_loaded:
-            is_user_loaded =  self._fill_data(self.anonymous_user)
+        if is_user_loaded:
+            assert dbuser is not None
+            self.is_default_user = dbuser.is_default_user
+        else:
+            default_user = User.get_default_user(cache=True)
+            is_user_loaded = self._fill_data(default_user)
+            self.is_default_user = is_user_loaded
 
-        # The anonymous user is always "logged in".
-        if self.user_id == self.anonymous_user.user_id:
-            self.is_authenticated = True
+        self.is_anonymous = not is_user_loaded or self.is_default_user
 
         if not self.username:
             self.username = 'None'
@@ -542,11 +533,47 @@
     def permissions(self):
         return self.__get_perms(user=self, cache=False)
 
+    def has_repository_permission_level(self, repo_name, level, purpose=None):
+        required_perms = {
+            'read': ['repository.read', 'repository.write', 'repository.admin'],
+            'write': ['repository.write', 'repository.admin'],
+            'admin': ['repository.admin'],
+        }[level]
+        actual_perm = self.permissions['repositories'].get(repo_name)
+        ok = actual_perm in required_perms
+        log.debug('Checking if user %r can %r repo %r (%s): %s (has %r)',
+            self.username, level, repo_name, purpose, ok, actual_perm)
+        return ok
+
+    def has_repository_group_permission_level(self, repo_group_name, level, purpose=None):
+        required_perms = {
+            'read': ['group.read', 'group.write', 'group.admin'],
+            'write': ['group.write', 'group.admin'],
+            'admin': ['group.admin'],
+        }[level]
+        actual_perm = self.permissions['repositories_groups'].get(repo_group_name)
+        ok = actual_perm in required_perms
+        log.debug('Checking if user %r can %r repo group %r (%s): %s (has %r)',
+            self.username, level, repo_group_name, purpose, ok, actual_perm)
+        return ok
+
+    def has_user_group_permission_level(self, user_group_name, level, purpose=None):
+        required_perms = {
+            'read': ['usergroup.read', 'usergroup.write', 'usergroup.admin'],
+            'write': ['usergroup.write', 'usergroup.admin'],
+            'admin': ['usergroup.admin'],
+        }[level]
+        actual_perm = self.permissions['user_groups'].get(user_group_name)
+        ok = actual_perm in required_perms
+        log.debug('Checking if user %r can %r user group %r (%s): %s (has %r)',
+            self.username, level, user_group_name, purpose, ok, actual_perm)
+        return ok
+
     @property
     def api_keys(self):
         return self._get_api_keys()
 
-    def __get_perms(self, user, explicit=True, algo='higherwin', cache=False):
+    def __get_perms(self, user, explicit=True, cache=False):
         """
         Fills user permission attribute with permissions taken from database
         works for permissions given for repositories, and for permissions that
@@ -556,11 +583,7 @@
         :param explicit: In case there are permissions both for user and a group
             that user is part of, explicit flag will define if user will
             explicitly override permissions from group, if it's False it will
-            make decision based on the algo
-        :param algo: algorithm to decide what permission should be choose if
-            it's multiple defined, eg user in two different groups. It also
-            decides if explicit flag is turned off how to specify the permission
-            for case when user is in a group + have defined separate permission
+            compute the decision
         """
         user_id = user.user_id
         user_is_admin = user.is_admin
@@ -570,14 +593,12 @@
         compute = conditional_cache('short_term', 'cache_desc',
                                     condition=cache, func=_cached_perms_data)
         return compute(user_id, user_is_admin,
-                       user_inherit_default_permissions, explicit, algo)
+                       user_inherit_default_permissions, explicit)
 
     def _get_api_keys(self):
         api_keys = [self.api_key]
-        for api_key in UserApiKeys.query()\
-                .filter(UserApiKeys.user_id == self.user_id)\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time())).all():
+        for api_key in UserApiKeys.query() \
+                .filter_by(user_id=self.user_id, is_expired=False):
             api_keys.append(api_key.api_key)
 
         return api_keys
@@ -627,18 +648,13 @@
             return False
 
     def __repr__(self):
-        return "<AuthUser('id:%s[%s] auth:%s')>"\
-            % (self.user_id, self.username, self.is_authenticated)
-
-    def set_authenticated(self, authenticated=True):
-        if self.user_id != self.anonymous_user.user_id:
-            self.is_authenticated = authenticated
+        return "<AuthUser('id:%s[%s] auth:%s')>" \
+            % (self.user_id, self.username, (self.is_authenticated or self.is_default_user))
 
     def to_cookie(self):
         """ Serializes this login session to a cookie `dict`. """
         return {
             'user_id': self.user_id,
-            'is_authenticated': self.is_authenticated,
             'is_external_auth': self.is_external_auth,
         }
 
@@ -652,9 +668,7 @@
             user_id=cookie.get('user_id'),
             is_external_auth=cookie.get('is_external_auth', False),
         )
-        if not au.is_authenticated and au.user_id is not None:
-            # user is not authenticated and not empty
-            au.set_authenticated(cookie.get('is_authenticated'))
+        au.is_authenticated = True
         return au
 
     @classmethod
@@ -662,8 +676,8 @@
         _set = set()
 
         if inherit_from_default:
-            default_ips = UserIpMap.query().filter(UserIpMap.user ==
-                                            User.get_default_user(cache=True))
+            default_ips = UserIpMap.query().filter(UserIpMap.user_id ==
+                                            User.get_default_user(cache=True).user_id)
             if cache:
                 default_ips = default_ips.options(FromCache("sql_cache_short",
                                                   "get_user_ips_default"))
@@ -694,101 +708,76 @@
 
 def set_available_permissions(config):
     """
-    This function will propagate pylons globals with all available defined
+    This function will propagate globals with all available defined
     permission given in db. We don't want to check each time from db for new
     permissions since adding a new permission also requires application restart
     ie. to decorate new views with the newly created permission
 
-    :param config: current pylons config instance
+    :param config: current config instance
 
     """
     log.info('getting information about all available permissions')
     try:
-        sa = meta.Session
-        all_perms = sa.query(Permission).all()
+        all_perms = Session().query(Permission).all()
         config['available_permissions'] = [x.permission_name for x in all_perms]
     finally:
-        meta.Session.remove()
+        Session.remove()
 
 
 #==============================================================================
 # CHECK DECORATORS
 #==============================================================================
 
-def redirect_to_login(message=None):
+def _redirect_to_login(message=None):
+    """Return an exception that must be raised. It will redirect to the login
+    page which will redirect back to the current URL after authentication.
+    The optional message will be shown in a flash message."""
     from kallithea.lib import helpers as h
-    p = request.path_qs
     if message:
         h.flash(message, category='warning')
+    p = request.path_qs
     log.debug('Redirecting to login page, origin: %s', p)
-    return redirect(url('login_home', came_from=p))
+    return HTTPFound(location=url('login_home', came_from=p))
 
 
+# Use as decorator
 class LoginRequired(object):
-    """
-    Must be logged in to execute this function else
-    redirect to login page
+    """Client must be logged in as a valid User, or we'll redirect to the login
+    page.
 
-    :param api_access: if enabled this checks only for valid auth token
-        and grants access based on valid token
+    If the "default" user is enabled and allow_default_user is true, that is
+    considered valid too.
+
+    Also checks that IP address is allowed, and if using API key instead
+    of regular cookie authentication, checks that API key access is allowed
+    (based on `api_access` parameter and the API view whitelist).
     """
 
-    def __init__(self, api_access=False):
+    def __init__(self, api_access=False, allow_default_user=False):
         self.api_access = api_access
+        self.allow_default_user = allow_default_user
 
     def __call__(self, func):
         return decorator(self.__wrapper, func)
 
     def __wrapper(self, func, *fargs, **fkwargs):
         controller = fargs[0]
-        user = controller.authuser
+        user = request.authuser
         loc = "%s:%s" % (controller.__class__.__name__, func.__name__)
         log.debug('Checking access for user %s @ %s', user, loc)
 
-        if not AuthUser.check_ip_allowed(user, controller.ip_addr):
-            return redirect_to_login(_('IP %s not allowed') % controller.ip_addr)
-
-        # check if we used an API key and it's a valid one
-        api_key = request.GET.get('api_key')
+        # Check if we used an API key to authenticate.
+        api_key = user.authenticating_api_key
         if api_key is not None:
-            # explicit controller is enabled or API is in our whitelist
-            if self.api_access or allowed_api_access(loc, api_key=api_key):
-                if api_key in user.api_keys:
-                    log.info('user %s authenticated with API key ****%s @ %s',
-                             user, api_key[-4:], loc)
-                    return func(*fargs, **fkwargs)
-                else:
-                    log.warning('API key ****%s is NOT valid', api_key[-4:])
-                    return redirect_to_login(_('Invalid API key'))
-            else:
+            # Check that controller is enabled for API key usage.
+            if not self.api_access and not allowed_api_access(loc, api_key=api_key):
                 # controller does not allow API access
                 log.warning('API access to %s is not allowed', loc)
-                return abort(403)
-
-        # Only allow the following HTTP request methods. (We sometimes use POST
-        # requests with a '_method' set to 'PUT' or 'DELETE'; but that is only
-        # used for the route lookup, and does not affect request.method.)
-        if request.method not in ['GET', 'HEAD', 'POST', 'PUT']:
-            return abort(405)
+                raise HTTPForbidden()
 
-        # Also verify the _method override. This is only permitted in POST
-        # requests, and can specify PUT or DELETE.
-        _method = request.params.get('_method')
-        if _method is None:
-            pass # no override, no problem
-        elif request.method == 'POST' and _method.upper() in ['PUT', 'DELETE']:
-            pass # permitted override
-        else:
-            raise HTTPMethodNotAllowed()
-
-        # Make sure CSRF token never appears in the URL. If so, invalidate it.
-        if secure_form.token_key in request.GET:
-            log.error('CSRF key leak detected')
-            session.pop(secure_form.token_key, None)
-            session.save()
-            from kallithea.lib import helpers as h
-            h.flash(_("CSRF token leak has been detected - all form tokens have been expired"),
-                    category='error')
+            log.info('user %s authenticated with API key ****%s @ %s',
+                     user, api_key[-4:], loc)
+            return func(*fargs, **fkwargs)
 
         # CSRF protection: Whenever a request has ambient authority (whether
         # through a session cookie or its origin IP address), it must include
@@ -800,527 +789,203 @@
             token = request.POST.get(secure_form.token_key)
             if not token or token != secure_form.authentication_token():
                 log.error('CSRF check failed')
-                return abort(403)
-
-        # WebOb already ignores request payload parameters for anything other
-        # than POST/PUT, but double-check since other Kallithea code relies on
-        # this assumption.
-        if request.method not in ['POST', 'PUT'] and request.POST:
-            log.error('%r request with payload parameters; WebOb should have stopped this', request.method)
-            return abort(400)
+                raise HTTPForbidden()
 
         # regular user authentication
         if user.is_authenticated:
             log.info('user %s authenticated with regular auth @ %s', user, loc)
             return func(*fargs, **fkwargs)
+        elif user.is_default_user:
+            if self.allow_default_user:
+                log.info('default user @ %s', loc)
+                return func(*fargs, **fkwargs)
+            log.info('default user is not accepted here @ %s', loc)
         else:
             log.warning('user %s NOT authenticated with regular auth @ %s', user, loc)
-            return redirect_to_login()
+        raise _redirect_to_login()
 
+
+# Use as decorator
 class NotAnonymous(object):
-    """
-    Must be logged in to execute this function else
-    redirect to login page"""
+    """Ensures that client is not logged in as the "default" user, and
+    redirects to the login page otherwise. Must be used together with
+    LoginRequired."""
 
     def __call__(self, func):
         return decorator(self.__wrapper, func)
 
     def __wrapper(self, func, *fargs, **fkwargs):
         cls = fargs[0]
-        self.user = cls.authuser
+        user = request.authuser
 
-        log.debug('Checking if user is not anonymous @%s', cls)
+        log.debug('Checking that user %s is not anonymous @%s', user.username, cls)
 
-        anonymous = self.user.username == User.DEFAULT_USER
-
-        if anonymous:
-            return redirect_to_login(_('You need to be a registered user to '
-                    'perform this action'))
+        if user.is_default_user:
+            raise _redirect_to_login(_('You need to be a registered user to '
+                                       'perform this action'))
         else:
             return func(*fargs, **fkwargs)
 
 
-class PermsDecorator(object):
-    """Base class for controller decorators"""
+class _PermsDecorator(object):
+    """Base class for controller decorators with multiple permissions"""
 
     def __init__(self, *required_perms):
-        self.required_perms = set(required_perms)
-        self.user_perms = None
+        self.required_perms = required_perms # usually very short - a list is thus fine
 
     def __call__(self, func):
         return decorator(self.__wrapper, func)
 
     def __wrapper(self, func, *fargs, **fkwargs):
         cls = fargs[0]
-        self.user = cls.authuser
-        self.user_perms = self.user.permissions
+        user = request.authuser
         log.debug('checking %s permissions %s for %s %s',
-          self.__class__.__name__, self.required_perms, cls, self.user)
+          self.__class__.__name__, self.required_perms, cls, user)
 
-        if self.check_permissions():
-            log.debug('Permission granted for %s %s', cls, self.user)
+        if self.check_permissions(user):
+            log.debug('Permission granted for %s %s', cls, user)
             return func(*fargs, **fkwargs)
 
         else:
-            log.debug('Permission denied for %s %s', cls, self.user)
-            anonymous = self.user.username == User.DEFAULT_USER
-
-            if anonymous:
-                return redirect_to_login(_('You need to be signed in to view this page'))
+            log.info('Permission denied for %s %s', cls, user)
+            if user.is_default_user:
+                raise _redirect_to_login(_('You need to be signed in to view this page'))
             else:
-                # redirect with forbidden ret code
-                return abort(403)
+                raise HTTPForbidden()
 
-    def check_permissions(self):
-        """Dummy function for overriding"""
-        raise Exception('You have to write this function in child class')
-
-
-class HasPermissionAllDecorator(PermsDecorator):
-    """
-    Checks for access permission for all given predicates. All of them
-    have to be meet in order to fulfill the request
-    """
-
-    def check_permissions(self):
-        if self.required_perms.issubset(self.user_perms.get('global')):
-            return True
-        return False
+    def check_permissions(self, user):
+        raise NotImplementedError()
 
 
-class HasPermissionAnyDecorator(PermsDecorator):
-    """
-    Checks for access permission for any of given predicates. In order to
-    fulfill the request any of predicates must be meet
-    """
-
-    def check_permissions(self):
-        if self.required_perms.intersection(self.user_perms.get('global')):
-            return True
-        return False
-
-
-class HasRepoPermissionAllDecorator(PermsDecorator):
-    """
-    Checks for access permission for all given predicates for specific
-    repository. All of them have to be meet in order to fulfill the request
+class HasPermissionAnyDecorator(_PermsDecorator):
     """
-
-    def check_permissions(self):
-        repo_name = get_repo_slug(request)
-        try:
-            user_perms = set([self.user_perms['repositories'][repo_name]])
-        except KeyError:
-            return False
-        if self.required_perms.issubset(user_perms):
-            return True
-        return False
-
-
-class HasRepoPermissionAnyDecorator(PermsDecorator):
-    """
-    Checks for access permission for any of given predicates for specific
-    repository. In order to fulfill the request any of predicates must be meet
+    Checks the user has any of the given global permissions.
     """
 
-    def check_permissions(self):
-        repo_name = get_repo_slug(request)
-        try:
-            user_perms = set([self.user_perms['repositories'][repo_name]])
-        except KeyError:
-            return False
-
-        if self.required_perms.intersection(user_perms):
-            return True
-        return False
+    def check_permissions(self, user):
+        global_permissions = user.permissions['global'] # usually very short
+        return any(p in global_permissions for p in self.required_perms)
 
 
-class HasRepoGroupPermissionAllDecorator(PermsDecorator):
-    """
-    Checks for access permission for all given predicates for specific
-    repository group. All of them have to be meet in order to fulfill the request
-    """
+class _PermDecorator(_PermsDecorator):
+    """Base class for controller decorators with a single permission"""
 
-    def check_permissions(self):
-        group_name = get_repo_group_slug(request)
-        try:
-            user_perms = set([self.user_perms['repositories_groups'][group_name]])
-        except KeyError:
-            return False
-
-        if self.required_perms.issubset(user_perms):
-            return True
-        return False
+    def __init__(self, required_perm):
+        _PermsDecorator.__init__(self, [required_perm])
+        self.required_perm = required_perm
 
 
-class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
+class HasRepoPermissionLevelDecorator(_PermDecorator):
     """
-    Checks for access permission for any of given predicates for specific
-    repository group. In order to fulfill the request any of predicates must be meet
+    Checks the user has at least the specified permission level for the requested repository.
     """
 
-    def check_permissions(self):
-        group_name = get_repo_group_slug(request)
-        try:
-            user_perms = set([self.user_perms['repositories_groups'][group_name]])
-        except KeyError:
-            return False
-
-        if self.required_perms.intersection(user_perms):
-            return True
-        return False
+    def check_permissions(self, user):
+        repo_name = get_repo_slug(request)
+        return user.has_repository_permission_level(repo_name, self.required_perm)
 
 
-class HasUserGroupPermissionAllDecorator(PermsDecorator):
+class HasRepoGroupPermissionLevelDecorator(_PermDecorator):
     """
-    Checks for access permission for all given predicates for specific
-    user group. All of them have to be meet in order to fulfill the request
+    Checks the user has any of given permissions for the requested repository group.
     """
 
-    def check_permissions(self):
-        group_name = get_user_group_slug(request)
-        try:
-            user_perms = set([self.user_perms['user_groups'][group_name]])
-        except KeyError:
-            return False
-
-        if self.required_perms.issubset(user_perms):
-            return True
-        return False
+    def check_permissions(self, user):
+        repo_group_name = get_repo_group_slug(request)
+        return user.has_repository_group_permission_level(repo_group_name, self.required_perm)
 
 
-class HasUserGroupPermissionAnyDecorator(PermsDecorator):
+class HasUserGroupPermissionLevelDecorator(_PermDecorator):
     """
     Checks for access permission for any of given predicates for specific
     user group. In order to fulfill the request any of predicates must be meet
     """
 
-    def check_permissions(self):
-        group_name = get_user_group_slug(request)
-        try:
-            user_perms = set([self.user_perms['user_groups'][group_name]])
-        except KeyError:
-            return False
-
-        if self.required_perms.intersection(user_perms):
-            return True
-        return False
+    def check_permissions(self, user):
+        user_group_name = get_user_group_slug(request)
+        return user.has_user_group_permission_level(user_group_name, self.required_perm)
 
 
 #==============================================================================
 # CHECK FUNCTIONS
 #==============================================================================
-class PermsFunction(object):
-    """Base function for other check functions"""
-
-    def __init__(self, *perms):
-        self.required_perms = set(perms)
-        self.user_perms = None
-        self.repo_name = None
-        self.group_name = None
-
-    def __call__(self, check_location='', user=None):
-        if not user:
-            #TODO: remove this someday,put as user as attribute here
-            user = request.user
-
-        # init auth user if not already given
-        if not isinstance(user, AuthUser):
-            user = AuthUser(user.user_id)
 
-        cls_name = self.__class__.__name__
-        check_scope = {
-            'HasPermissionAll': '',
-            'HasPermissionAny': '',
-            'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
-            'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
-            'HasRepoGroupPermissionAll': 'group:%s' % self.group_name,
-            'HasRepoGroupPermissionAny': 'group:%s' % self.group_name,
-        }.get(cls_name, '?')
-        log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
-                  self.required_perms, user, check_scope,
-                  check_location or 'unspecified location')
-        if not user:
-            log.debug('Empty request user')
-            return False
-        self.user_perms = user.permissions
-        if self.check_permissions():
-            log.debug('Permission to %s granted for user: %s @ %s',
-                      check_scope, user,
-                         check_location or 'unspecified location')
-            return True
+class _PermsFunction(object):
+    """Base function for other check functions with multiple permissions"""
+
+    def __init__(self, *required_perms):
+        self.required_perms = required_perms # usually very short - a list is thus fine
 
-        else:
-            log.debug('Permission to %s denied for user: %s @ %s',
-                      check_scope, user,
-                         check_location or 'unspecified location')
-            return False
-
-    def check_permissions(self):
-        """Dummy function for overriding"""
-        raise Exception('You have to write this function in child class')
-
-
-class HasPermissionAll(PermsFunction):
-    def check_permissions(self):
-        if self.required_perms.issubset(self.user_perms.get('global')):
-            return True
-        return False
-
+    def __nonzero__(self):
+        """ Defend against accidentally forgetting to call the object
+            and instead evaluating it directly in a boolean context,
+            which could have security implications.
+        """
+        raise AssertionError(self.__class__.__name__ + ' is not a bool and must be called!')
 
-class HasPermissionAny(PermsFunction):
-    def check_permissions(self):
-        if self.required_perms.intersection(self.user_perms.get('global')):
-            return True
-        return False
-
-
-class HasRepoPermissionAll(PermsFunction):
-    def __call__(self, repo_name=None, check_location='', user=None):
-        self.repo_name = repo_name
-        return super(HasRepoPermissionAll, self).__call__(check_location, user)
-
-    def check_permissions(self):
-        if not self.repo_name:
-            self.repo_name = get_repo_slug(request)
-
-        try:
-            self._user_perms = set(
-                [self.user_perms['repositories'][self.repo_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.issubset(self._user_perms):
-            return True
-        return False
+    def __call__(self, *a, **b):
+        raise NotImplementedError()
 
 
-class HasRepoPermissionAny(PermsFunction):
-    def __call__(self, repo_name=None, check_location='', user=None):
-        self.repo_name = repo_name
-        return super(HasRepoPermissionAny, self).__call__(check_location, user)
-
-    def check_permissions(self):
-        if not self.repo_name:
-            self.repo_name = get_repo_slug(request)
+class HasPermissionAny(_PermsFunction):
 
-        try:
-            self._user_perms = set(
-                [self.user_perms['repositories'][self.repo_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.intersection(self._user_perms):
-            return True
-        return False
-
+    def __call__(self, purpose=None):
+        global_permissions = request.authuser.permissions['global'] # usually very short
+        ok = any(p in global_permissions for p in self.required_perms)
 
-class HasRepoGroupPermissionAny(PermsFunction):
-    def __call__(self, group_name=None, check_location='', user=None):
-        self.group_name = group_name
-        return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
-
-    def check_permissions(self):
-        try:
-            self._user_perms = set(
-                [self.user_perms['repositories_groups'][self.group_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.intersection(self._user_perms):
-            return True
-        return False
+        log.debug('Check %s for global %s (%s): %s' %
+            (request.authuser.username, self.required_perms, purpose, ok))
+        return ok
 
 
-class HasRepoGroupPermissionAll(PermsFunction):
-    def __call__(self, group_name=None, check_location='', user=None):
-        self.group_name = group_name
-        return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
+class _PermFunction(_PermsFunction):
+    """Base function for other check functions with a single permission"""
 
-    def check_permissions(self):
-        try:
-            self._user_perms = set(
-                [self.user_perms['repositories_groups'][self.group_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.issubset(self._user_perms):
-            return True
-        return False
+    def __init__(self, required_perm):
+        _PermsFunction.__init__(self, [required_perm])
+        self.required_perm = required_perm
 
 
-class HasUserGroupPermissionAny(PermsFunction):
-    def __call__(self, user_group_name=None, check_location='', user=None):
-        self.user_group_name = user_group_name
-        return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
+class HasRepoPermissionLevel(_PermFunction):
 
-    def check_permissions(self):
-        try:
-            self._user_perms = set(
-                [self.user_perms['user_groups'][self.user_group_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.intersection(self._user_perms):
-            return True
-        return False
+    def __call__(self, repo_name, purpose=None):
+        return request.authuser.has_repository_permission_level(repo_name, self.required_perm, purpose)
 
 
-class HasUserGroupPermissionAll(PermsFunction):
-    def __call__(self, user_group_name=None, check_location='', user=None):
-        self.user_group_name = user_group_name
-        return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
+class HasRepoGroupPermissionLevel(_PermFunction):
+
+    def __call__(self, group_name, purpose=None):
+        return request.authuser.has_repository_group_permission_level(group_name, self.required_perm, purpose)
 
-    def check_permissions(self):
-        try:
-            self._user_perms = set(
-                [self.user_perms['user_groups'][self.user_group_name]]
-            )
-        except KeyError:
-            return False
-        if self.required_perms.issubset(self._user_perms):
-            return True
-        return False
+
+class HasUserGroupPermissionLevel(_PermFunction):
+
+    def __call__(self, user_group_name, purpose=None):
+        return request.authuser.has_user_group_permission_level(user_group_name, self.required_perm, purpose)
 
 
 #==============================================================================
 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
 #==============================================================================
+
 class HasPermissionAnyMiddleware(object):
     def __init__(self, *perms):
         self.required_perms = set(perms)
 
-    def __call__(self, user, repo_name):
-        # repo_name MUST be unicode, since we handle keys in permission
+    def __call__(self, user, repo_name, purpose=None):
+        # repo_name MUST be unicode, since we handle keys in ok
         # dict by unicode
         repo_name = safe_unicode(repo_name)
-        usr = AuthUser(user.user_id)
-        self.user_perms = set([usr.permissions['repositories'][repo_name]])
-        self.username = user.username
-        self.repo_name = repo_name
-        return self.check_permissions()
-
-    def check_permissions(self):
-        log.debug('checking VCS protocol '
-                  'permissions %s for user:%s repository:%s', self.user_perms,
-                                                self.username, self.repo_name)
-        if self.required_perms.intersection(self.user_perms):
-            log.debug('Permission to repo: %s granted for user: %s @ %s',
-                      self.repo_name, self.username, 'PermissionMiddleware')
-            return True
-        log.debug('Permission to repo: %s denied for user: %s @ %s',
-                  self.repo_name, self.username, 'PermissionMiddleware')
-        return False
-
-
-#==============================================================================
-# SPECIAL VERSION TO HANDLE API AUTH
-#==============================================================================
-class _BaseApiPerm(object):
-    def __init__(self, *perms):
-        self.required_perms = set(perms)
-
-    def __call__(self, check_location=None, user=None, repo_name=None,
-                 group_name=None):
-        cls_name = self.__class__.__name__
-        check_scope = 'user:%s' % (user)
-        if repo_name:
-            check_scope += ', repo:%s' % (repo_name)
-
-        if group_name:
-            check_scope += ', repo group:%s' % (group_name)
-
-        log.debug('checking cls:%s %s %s @ %s',
-                  cls_name, self.required_perms, check_scope, check_location)
-        if not user:
-            log.debug('Empty User passed into arguments')
-            return False
-
-        ## process user
-        if not isinstance(user, AuthUser):
-            user = AuthUser(user.user_id)
-        if not check_location:
-            check_location = 'unspecified'
-        if self.check_permissions(user.permissions, repo_name, group_name):
-            log.debug('Permission to %s granted for user: %s @ %s',
-                      check_scope, user, check_location)
-            return True
-
-        else:
-            log.debug('Permission to %s denied for user: %s @ %s',
-                      check_scope, user, check_location)
-            return False
-
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        """
-        implement in child class should return True if permissions are ok,
-        False otherwise
+        user = AuthUser(user.user_id)
 
-        :param perm_defs: dict with permission definitions
-        :param repo_name: repo name
-        """
-        raise NotImplementedError()
-
-
-class HasPermissionAllApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        if self.required_perms.issubset(perm_defs.get('global')):
-            return True
-        return False
-
-
-class HasPermissionAnyApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        if self.required_perms.intersection(perm_defs.get('global')):
-            return True
-        return False
-
-
-class HasRepoPermissionAllApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
         try:
-            _user_perms = set([perm_defs['repositories'][repo_name]])
+            ok = user.permissions['repositories'][repo_name] in self.required_perms
         except KeyError:
-            log.warning(traceback.format_exc())
-            return False
-        if self.required_perms.issubset(_user_perms):
-            return True
-        return False
-
+            ok = False
 
-class HasRepoPermissionAnyApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        try:
-            _user_perms = set([perm_defs['repositories'][repo_name]])
-        except KeyError:
-            log.warning(traceback.format_exc())
-            return False
-        if self.required_perms.intersection(_user_perms):
-            return True
-        return False
-
+        log.debug('Middleware check %s for %s for repo %s (%s): %s' % (user.username, self.required_perms, repo_name, purpose, ok))
+        return ok
 
-class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        try:
-            _user_perms = set([perm_defs['repositories_groups'][group_name]])
-        except KeyError:
-            log.warning(traceback.format_exc())
-            return False
-        if self.required_perms.intersection(_user_perms):
-            return True
-        return False
-
-class HasRepoGroupPermissionAllApi(_BaseApiPerm):
-    def check_permissions(self, perm_defs, repo_name=None, group_name=None):
-        try:
-            _user_perms = set([perm_defs['repositories_groups'][group_name]])
-        except KeyError:
-            log.warning(traceback.format_exc())
-            return False
-        if self.required_perms.issubset(_user_perms):
-            return True
-        return False
 
 def check_ip_access(source_ip, allowed_ips=None):
     """
@@ -1330,6 +995,7 @@
     :param allowed_ips: list of allowed ips together with mask
     """
     from kallithea.lib import ipaddr
+    source_ip = source_ip.split('%', 1)[0]
     log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
     if isinstance(allowed_ips, (tuple, list, set)):
         for ip in allowed_ips:
--- a/kallithea/lib/auth_modules/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -17,12 +17,11 @@
 
 import logging
 import traceback
+import importlib
 
-from kallithea import EXTERN_TYPE_INTERNAL
-from kallithea.lib.compat import importlib
 from kallithea.lib.utils2 import str2bool
 from kallithea.lib.compat import formatted_json, hybrid_property
-from kallithea.lib.auth import PasswordGenerator
+from kallithea.lib.auth import PasswordGenerator, AuthUser
 from kallithea.model.user import UserModel
 from kallithea.model.db import Setting, User
 from kallithea.model.meta import Session
@@ -41,7 +40,7 @@
         from inspect import isfunction
         formencode_obj = self.formencode_obj
         if isfunction(formencode_obj):
-            #case we wrap validators into functions
+            # case we wrap validators into functions
             formencode_obj = self.formencode_obj(*args, **kwargs)
         return formencode_obj(*self.args, **self.kwargs)
 
@@ -55,9 +54,6 @@
         "groups": '["list", "of", "groups"]',
         "extern_name": "name in external source of record",
         "admin": 'True|False defines if user should be Kallithea admin',
-        "active": 'True|False defines active state of user in Kallithea',
-        "active_from_extern": "True|False|None, active state from the external auth, "
-                              "None means use value from the auth plugin"
     }
 
     @property
@@ -77,7 +73,6 @@
                 #log.debug('Initializing lazy formencode object: %s', obj)
                 return LazyFormencode(obj, *args, **kwargs)
 
-
         class ProxyGet(object):
             def __getattribute__(self, name):
                 return LazyCaller(name)
@@ -139,8 +134,8 @@
         log.debug('Trying to fetch user `%s` from Kallithea database',
                   username)
         if username:
-            user = User.get_by_username(username)
-            if not user:
+            user = User.get_by_username_or_email(username)
+            if user is None:
                 log.debug('Fallback to fetch user in case insensitive mode')
                 user = User.get_by_username(username, case_insensitive=True)
         else:
@@ -200,14 +195,6 @@
         )
         return rcsettings
 
-    def user_activation_state(self):
-        """
-        Defines user activation state when creating new users
-
-        :returns: boolean
-        """
-        raise NotImplementedError("Not implemented in base class")
-
     def auth(self, userobj, username, passwd, settings, **kwargs):
         """
         Given a user object (which may be None), username, a plaintext password,
@@ -224,11 +211,6 @@
     def _authenticate(self, userobj, username, passwd, settings, **kwargs):
         """
         Wrapper to call self.auth() that validates call on it
-
-        :param userobj: userobj
-        :param username: username
-        :param passwd: plaintext password
-        :param settings: plugin settings
         """
         user_data = self.auth(userobj, username, passwd, settings, **kwargs)
         if user_data is not None:
@@ -259,17 +241,11 @@
         user_data = super(KallitheaExternalAuthPlugin, self)._authenticate(
             userobj, username, passwd, settings, **kwargs)
         if user_data is not None:
-            # maybe plugin will clean the username ?
-            # we should use the return value
-            username = user_data['username']
-            # if user is not active from our extern type we should fail to auth
-            # this can prevent from creating users in Kallithea when using
-            # external authentication, but if it's inactive user we shouldn't
-            # create that user anyway
-            if user_data['active_from_extern'] is False:
-                log.warning("User %s authenticated against %s, but is inactive",
-                            username, self.__module__)
-                return None
+            if userobj is None: # external authentication of unknown user that will be created soon
+                def_user_perms = AuthUser(dbuser=User.get_default_user()).permissions['global']
+                active = 'hg.extern_activate.auto' in def_user_perms
+            else:
+                active = userobj.active
 
             if self.use_fake_password():
                 # Randomize the PW because we don't need it, but don't want
@@ -279,17 +255,16 @@
             log.debug('Updating or creating user info from %s plugin',
                       self.name)
             user = UserModel().create_or_update(
-                username=username,
+                username=user_data['username'],
                 password=passwd,
                 email=user_data["email"],
                 firstname=user_data["firstname"],
                 lastname=user_data["lastname"],
-                active=user_data["active"],
+                active=active,
                 admin=user_data["admin"],
                 extern_name=user_data["extern_name"],
-                extern_type=self.name
+                extern_type=self.name,
             )
-            Session().flush()
             # enforce user is just in given groups, all of them has to be ones
             # created from plugins. We store this info in _group_data JSON field
             groups = user_data['groups'] or []
@@ -298,10 +273,10 @@
         return user_data
 
 
-def importplugin(plugin):
+def loadplugin(plugin):
     """
-    Imports and returns the authentication plugin in the module named by plugin
-    (e.g., plugin='kallithea.lib.auth_modules.auth_internal'). Returns the
+    Imports, instantiates, and returns the authentication plugin in the module named by plugin
+    (e.g., plugin='kallithea.lib.auth_modules.auth_internal'). Returns an instance of the
     KallitheaAuthPluginBase subclass on success, raises exceptions on failure.
 
     raises:
@@ -314,8 +289,6 @@
         parts = plugin.split(u'.lib.auth_modules.auth_', 1)
         if len(parts) == 2:
             _module, pn = parts
-            if pn == EXTERN_TYPE_INTERNAL:
-                pn = "internal"
             plugin = u'kallithea.lib.auth_modules.auth_' + pn
     PLUGIN_CLASS_NAME = "KallitheaAuthPlugin"
     try:
@@ -333,16 +306,8 @@
     if not issubclass(pluginclass, KallitheaAuthPluginBase):
         raise TypeError("Authentication class %s.KallitheaAuthPlugin is not "
                         "a subclass of %s" % (plugin, KallitheaAuthPluginBase))
-    return pluginclass
 
-
-def loadplugin(plugin):
-    """
-    Loads and returns an instantiated authentication plugin.
-
-        see: importplugin
-    """
-    plugin = importplugin(plugin)()
+    plugin = pluginclass()
     if plugin.plugin_settings.im_func != KallitheaAuthPluginBase.plugin_settings.im_func:
         raise TypeError("Authentication class %s.KallitheaAuthPluginBase "
                         "has overridden the plugin_settings method, which is "
@@ -350,6 +315,19 @@
     return plugin
 
 
+def get_auth_plugins():
+    """Return a list of instances of plugins that are available and enabled"""
+    auth_plugins = []
+    for plugin_name in Setting.get_by_name("auth_plugins").app_settings_value:
+        try:
+            plugin = loadplugin(plugin_name)
+        except Exception:
+            log.exception('Failed to load authentication module %s' % (plugin_name))
+        else:
+            auth_plugins.append(plugin)
+    return auth_plugins
+
+
 def authenticate(username, password, environ=None):
     """
     Authentication function used for access control,
@@ -361,15 +339,10 @@
     :returns: None if auth failed, user_data dict if auth is correct
     """
 
-    auth_plugins = Setting.get_auth_plugins()
-    log.debug('Authentication against %s plugins', auth_plugins)
-    for module in auth_plugins:
-        try:
-            plugin = loadplugin(module)
-        except (ImportError, AttributeError, TypeError) as e:
-            raise ImportError('Failed to load authentication module %s : %s'
-                              % (module, str(e)))
-        log.debug('Trying authentication using ** %s **', module)
+    auth_plugins = get_auth_plugins()
+    for plugin in auth_plugins:
+        module = plugin.__class__.__module__
+        log.debug('Trying authentication using %s', module)
         # load plugin settings from Kallithea database
         plugin_name = plugin.name
         plugin_settings = {}
@@ -377,7 +350,7 @@
             conf_key = "auth_%s_%s" % (plugin_name, v["name"])
             setting = Setting.get_by_name(conf_key)
             plugin_settings[v["name"]] = setting.app_settings_value if setting else None
-        log.debug('Plugin settings \n%s', formatted_json(plugin_settings))
+        log.debug('Settings for auth plugin %s:\n%s', plugin_name, formatted_json(plugin_settings))
 
         if not str2bool(plugin_settings["enabled"]):
             log.info("Authentication plugin %s is disabled, skipping for %s",
@@ -387,7 +360,12 @@
         # use plugin's method of user extraction.
         user = plugin.get_user(username, environ=environ,
                                settings=plugin_settings)
-        log.debug('Plugin %s extracted user is `%s`', module, user)
+        log.debug('Plugin %s extracted user `%s`', module, user)
+
+        if user is not None and not user.active:
+            log.error("Rejecting authentication of in-active user %s", user)
+            continue
+
         if not plugin.accepts(user):
             log.debug('Plugin %s does not accept user `%s` for authentication',
                       module, user)
@@ -395,8 +373,15 @@
         else:
             log.debug('Plugin %s accepted user `%s` for authentication',
                       module, user)
+            # The user might have tried to authenticate using their email address,
+            # then the username variable wouldn't contain a valid username.
+            # But as the plugin has accepted the user, .username field should
+            # have a valid username, so use it for authentication purposes.
+            if user is not None:
+                username = user.username
 
-        log.info('Authenticating user using %s plugin', plugin.__module__)
+        log.info('Authenticating user using %s plugin', module)
+
         # _authenticate is a wrapper for .auth() method of plugin.
         # it checks if .auth() sends proper data. For KallitheaExternalAuthPlugin
         # it also maps users to Database and maps the attributes returned
@@ -405,7 +390,7 @@
         user_data = plugin._authenticate(user, username, password,
                                            plugin_settings,
                                            environ=environ or {})
-        log.debug('PLUGIN USER DATA: %s', user_data)
+        log.debug('Plugin user data: %s', user_data)
 
         if user_data is not None:
             log.debug('Plugin returned proper authentication data')
@@ -414,17 +399,18 @@
         # we failed to Auth because .auth() method didn't return the user
         if username:
             log.warning("User `%s` failed to authenticate against %s",
-                        username, plugin.__module__)
+                        username, module)
     return None
 
+
 def get_managed_fields(user):
     """return list of fields that are managed by the user's auth source, usually some of
-    'username', 'firstname', 'lastname', 'email', 'active', 'password'
+    'username', 'firstname', 'lastname', 'email', 'password'
     """
-    auth_plugins = Setting.get_auth_plugins()
-    for module in auth_plugins:
+    auth_plugins = get_auth_plugins()
+    for plugin in auth_plugins:
+        module = plugin.__class__.__module__
         log.debug('testing %s (%s) with auth plugin %s', user, user.extern_type, module)
-        plugin = loadplugin(module)
         if plugin.name == user.extern_type:
             return plugin.get_managed_fields()
     log.error('no auth plugin %s found for %s', user.extern_type, user)
--- a/kallithea/lib/auth_modules/auth_container.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/auth_container.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,9 +27,9 @@
 
 import logging
 from kallithea.lib import auth_modules
-from kallithea.lib.utils2 import str2bool, safe_unicode
+from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str
 from kallithea.lib.compat import hybrid_property
-from kallithea.model.db import User
+from kallithea.model.db import User, Setting
 
 log = logging.getLogger(__name__)
 
@@ -53,15 +53,39 @@
                 "name": "header",
                 "validator": self.validators.UnicodeString(strip=True, not_empty=True),
                 "type": "string",
-                "description": "Header to extract the user from",
+                "description": "Request header to extract the username from",
                 "default": "REMOTE_USER",
-                "formname": "Header"
+                "formname": "Username header"
+            },
+            {
+                "name": "email_header",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Optional request header to extract the email from",
+                "default": "",
+                "formname": "Email header"
+            },
+            {
+                "name": "firstname_header",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Optional request header to extract the first name from",
+                "default": "",
+                "formname": "Firstname header"
+            },
+            {
+                "name": "lastname_header",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Optional request header to extract the last name from",
+                "default": "",
+                "formname": "Lastname header"
             },
             {
                 "name": "fallback_header",
                 "validator": self.validators.UnicodeString(strip=True),
                 "type": "string",
-                "description": "Header to extract the user from when main one fails",
+                "description": "Request header to extract the user from when main one fails",
                 "default": "HTTP_X_FORWARDED_USER",
                 "formname": "Fallback header"
             },
@@ -81,10 +105,6 @@
     def use_fake_password(self):
         return True
 
-    def user_activation_state(self):
-        def_user_perms = User.get_default_user().AuthUser.permissions['global']
-        return 'hg.extern_activate.auto' in def_user_perms
-
     def _clean_username(self, username):
         # Removing realm and domain from username
         username = username.partition('@')[0]
@@ -158,7 +178,7 @@
         # only way to log in is using environ
         username = None
         if userobj:
-            username = getattr(userobj, 'username')
+            username = safe_str(getattr(userobj, 'username'))
 
         if not username:
             # we don't have any objects in DB, user doesn't exist, extract
@@ -171,10 +191,9 @@
 
         # old attrs fetched from Kallithea database
         admin = getattr(userobj, 'admin', False)
-        active = getattr(userobj, 'active', True)
-        email = getattr(userobj, 'email', '')
-        firstname = getattr(userobj, 'firstname', '')
-        lastname = getattr(userobj, 'lastname', '')
+        email = environ.get(settings.get('email_header'), getattr(userobj, 'email', ''))
+        firstname = environ.get(settings.get('firstname_header'), getattr(userobj, 'firstname', ''))
+        lastname = environ.get(settings.get('lastname_header'), getattr(userobj, 'lastname', ''))
 
         user_data = {
             'username': username,
@@ -183,8 +202,6 @@
             'groups': [],
             'email': email or '',
             'admin': admin or False,
-            'active': active,
-            'active_from_extern': True,
             'extern_name': username,
         }
 
@@ -192,4 +209,11 @@
         return user_data
 
     def get_managed_fields(self):
-        return ['username', 'password']
+        fields = ['username', 'password']
+        if(Setting.get_by_name('auth_container_email_header').app_settings_value):
+            fields.append('email')
+        if(Setting.get_by_name('auth_container_firstname_header').app_settings_value):
+            fields.append('firstname')
+        if(Setting.get_by_name('auth_container_lastname_header').app_settings_value):
+            fields.append('lastname')
+        return fields
--- a/kallithea/lib/auth_modules/auth_crowd.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/auth_crowd.py	Sun Mar 31 21:28:56 2019 +0200
@@ -52,7 +52,7 @@
                                   passwd="some_passwd",
                                   version="1")
         """
-        if not "port" in kwargs:
+        if "port" not in kwargs:
             kwargs["port"] = "8095"
         self._logger = kwargs.get("logger", logging.getLogger(__name__))
         self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
@@ -88,14 +88,14 @@
         log.debug("Sent crowd: \n%s",
                   formatted_json({"url": url, "body": body,
                                            "headers": _headers}))
-        request = urllib2.Request(url, body, _headers)
+        req = urllib2.Request(url, body, _headers)
         if method:
-            request.get_method = lambda: method
+            req.get_method = lambda: method
 
         global msg
         msg = ""
         try:
-            rdoc = self.opener.open(request)
+            rdoc = self.opener.open(req)
             msg = "".join(rdoc.readlines())
             if not msg and empty_response_ok:
                 rval = {}
@@ -131,6 +131,8 @@
 
 
 class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin):
+    def __init__(self):
+        self._protocol_values = ["http", "https"]
 
     @hybrid_property
     def name(self):
@@ -139,6 +141,14 @@
     def settings(self):
         settings = [
             {
+                "name": "method",
+                "validator": self.validators.OneOf(self._protocol_values),
+                "type": "select",
+                "values": self._protocol_values,
+                "description": "The protocol used to connect to the Atlassian CROWD server.",
+                "formname": "Protocol"
+            },
+            {
                 "name": "host",
                 "validator": self.validators.UnicodeString(strip=True),
                 "type": "string",
@@ -183,10 +193,6 @@
     def use_fake_password(self):
         return True
 
-    def user_activation_state(self):
-        def_user_perms = User.get_default_user().AuthUser.permissions['global']
-        return 'hg.extern_activate.auto' in def_user_perms
-
     def auth(self, userobj, username, password, settings, **kwargs):
         """
         Given a user object (which may be null), username, a plaintext password,
@@ -208,6 +214,11 @@
         crowd_user = server.user_auth(username, password)
         log.debug("Crowd returned: \n%s", formatted_json(crowd_user))
         if not crowd_user["status"]:
+            log.error('Crowd authentication as %s returned no status', username)
+            return None
+
+        if not crowd_user.get('active'):
+            log.error('Crowd authentication as %s returned in-active user', username)
             return None
 
         res = server.user_groups(crowd_user["name"])
@@ -216,7 +227,6 @@
 
         # old attrs fetched from Kallithea database
         admin = getattr(userobj, 'admin', False)
-        active = getattr(userobj, 'active', True)
         email = getattr(userobj, 'email', '')
         firstname = getattr(userobj, 'firstname', '')
         lastname = getattr(userobj, 'lastname', '')
@@ -228,8 +238,6 @@
             'groups': crowd_user["groups"],
             'email': crowd_user["email"] or email,
             'admin': admin,
-            'active': active,
-            'active_from_extern': crowd_user.get('active'), # ???
             'extern_name': crowd_user["name"],
         }
 
--- a/kallithea/lib/auth_modules/auth_internal.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/auth_internal.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,7 +28,6 @@
 
 import logging
 
-from kallithea import EXTERN_TYPE_INTERNAL
 from kallithea.lib import auth_modules
 from kallithea.lib.compat import formatted_json, hybrid_property
 from kallithea.model.db import User
@@ -42,15 +41,12 @@
 
     @hybrid_property
     def name(self):
-        return EXTERN_TYPE_INTERNAL
+        # Also found as kallithea.lib.model.db.User.DEFAULT_AUTH_TYPE
+        return 'internal'
 
     def settings(self):
         return []
 
-    def user_activation_state(self):
-        def_user_perms = User.get_default_user().AuthUser.permissions['global']
-        return 'hg.register.auto_activate' in def_user_perms
-
     def accepts(self, user, accepts_empty=True):
         """
         Custom accepts for this auth that doesn't accept empty users. We
@@ -78,28 +74,23 @@
             "groups": [],
             "email": userobj.email,
             "admin": userobj.admin,
-            "active": userobj.active,
-            "active_from_extern": userobj.active,
             "extern_name": userobj.user_id,
         }
+        log.debug(formatted_json(user_data))
 
-        log.debug(formatted_json(user_data))
-        if userobj.active:
-            from kallithea.lib import auth
-            password_match = auth.KallitheaCrypto.hash_check(password, userobj.password)
-            if userobj.username == User.DEFAULT_USER and userobj.active:
-                log.info('user %s authenticated correctly as anonymous user',
-                         username)
-                return user_data
+        from kallithea.lib import auth
+        password_match = auth.check_password(password, userobj.password)
+        if userobj.is_default_user:
+            log.info('user %s authenticated correctly as anonymous user',
+                     username)
+            return user_data
 
-            elif userobj.username == username and password_match:
-                log.info('user %s authenticated correctly', user_data['username'])
-                return user_data
-            log.error("user %s had a bad password", username)
-            return None
-        else:
-            log.warning('user %s tried auth but is disabled', username)
-            return None
+        elif userobj.username == username and password_match:
+            log.info('user %s authenticated correctly', user_data['username'])
+            return user_data
+
+        log.error("user %s had a bad password", username)
+        return None
 
     def get_managed_fields(self):
         # Note: 'username' should only be editable (at least for user) if self registration is enabled
--- a/kallithea/lib/auth_modules/auth_ldap.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/auth_ldap.py	Sun Mar 31 21:28:56 2019 +0200
@@ -41,6 +41,7 @@
 
 try:
     import ldap
+    import ldap.filter
 except ImportError:
     # means that python-ldap is not installed
     ldap = None
@@ -48,38 +49,33 @@
 
 class AuthLdap(object):
 
-    def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
-                 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
+    def __init__(self, server, base_dn, port=None, bind_dn='', bind_pass='',
+                 tls_kind='LDAPS', tls_reqcert='DEMAND', cacertdir=None, ldap_version=3,
                  ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
                  search_scope='SUBTREE', attr_login='uid'):
         if ldap is None:
             raise LdapImportError
 
         self.ldap_version = ldap_version
-        ldap_server_type = 'ldap'
 
         self.TLS_KIND = tls_kind
-
-        if self.TLS_KIND == 'LDAPS':
-            port = port or 689
-            ldap_server_type = ldap_server_type + 's'
-
         OPT_X_TLS_DEMAND = 2
         self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
                                    OPT_X_TLS_DEMAND)
-        # split server into list
-        self.LDAP_SERVER_ADDRESS = server.split(',')
-        self.LDAP_SERVER_PORT = port
+        self.cacertdir = cacertdir
 
-        # USE FOR READ ONLY BIND TO LDAP SERVER
+        protocol = 'ldaps' if self.TLS_KIND == 'LDAPS' else 'ldap'
+        if not port:
+            port = 636 if self.TLS_KIND == 'LDAPS' else 389
+        self.LDAP_SERVER = str(', '.join(
+            "%s://%s:%s" % (protocol,
+                            host.strip(),
+                            port)
+            for host in server.split(',')))
+
         self.LDAP_BIND_DN = safe_str(bind_dn)
         self.LDAP_BIND_PASS = safe_str(bind_pass)
-        _LDAP_SERVERS = []
-        for host in self.LDAP_SERVER_ADDRESS:
-            _LDAP_SERVERS.append("%s://%s:%s" % (ldap_server_type,
-                                                     host.replace(' ', ''),
-                                                     self.LDAP_SERVER_PORT))
-        self.LDAP_SERVER = str(', '.join(s for s in _LDAP_SERVERS))
+
         self.BASE_DN = safe_str(base_dn)
         self.LDAP_FILTER = safe_str(ldap_filter)
         self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
@@ -96,10 +92,6 @@
         :param password: password
         """
 
-        from kallithea.lib.helpers import chop_at
-
-        uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
-
         if not password:
             log.debug("Attempt to authenticate LDAP user "
                       "with blank password rejected.")
@@ -107,9 +99,11 @@
         if "," in username:
             raise LdapUsernameError("invalid character in username: ,")
         try:
-            if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
-                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
-                                '/etc/openldap/cacerts')
+            if self.cacertdir:
+                if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
+                    ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.cacertdir)
+                else:
+                    log.debug("OPT_X_TLS_CACERTDIR is not available - can't set %s", self.cacertdir)
             ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
             ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
             ldap.set_option(ldap.OPT_TIMEOUT, 20)
@@ -131,8 +125,9 @@
                           self.LDAP_BIND_DN)
                 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
 
-            filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login,
-                                        username)
+            filter_ = '(&%s(%s=%s))' % (self.LDAP_FILTER,
+                                        ldap.filter.escape_filter_chars(self.attr_login),
+                                        ldap.filter.escape_filter_chars(username))
             log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
                       filter_, self.LDAP_SERVER)
             lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
@@ -148,26 +143,28 @@
                 try:
                     log.debug('Trying simple bind with %s', dn)
                     server.simple_bind_s(dn, safe_str(password))
-                    attrs = server.search_ext_s(dn, ldap.SCOPE_BASE,
-                                                '(objectClass=*)')[0][1]
-                    break
+                    results = server.search_ext_s(dn, ldap.SCOPE_BASE,
+                                                  '(objectClass=*)')
+                    if len(results) == 1:
+                        dn_, attrs = results[0]
+                        assert dn_ == dn
+                        return dn, attrs
 
                 except ldap.INVALID_CREDENTIALS:
-                    log.debug("LDAP rejected password for user '%s' (%s): %s",
-                              uid, username, dn)
+                    log.debug("LDAP rejected password for user '%s': %s",
+                              username, dn)
+                    continue # accept authentication as another ldap user with same username
 
-            else:
-                log.debug("No matching LDAP objects for authentication "
-                          "of '%s' (%s)", uid, username)
-                raise LdapPasswordError()
+            log.debug("No matching LDAP objects for authentication "
+                      "of '%s'", username)
+            raise LdapPasswordError()
 
         except ldap.NO_SUCH_OBJECT:
-            log.debug("LDAP says no such user '%s' (%s)", uid, username)
+            log.debug("LDAP says no such user '%s'", username)
             raise LdapUsernameError()
         except ldap.SERVER_DOWN:
-            raise LdapConnectionError("LDAP can't access authentication server")
-
-        return dn, attrs
+            # [0] might be {'info': "TLS error -8179:Peer's Certificate issuer is not recognized.", 'desc': "Can't contact LDAP server"}
+            raise LdapConnectionError("LDAP can't connect to authentication server")
 
 
 class KallitheaAuthPlugin(auth_modules.KallitheaExternalAuthPlugin):
@@ -192,11 +189,11 @@
             },
             {
                 "name": "port",
-                "validator": self.validators.Number(strip=True, not_empty=True),
+                "validator": self.validators.Number(strip=True),
                 "type": "string",
-                "description": "Port that the LDAP server is listening on",
-                "default": 389,
-                "formname": "Port"
+                "description": "Port that the LDAP server is listening on. Defaults to 389 for PLAIN/START_TLS and 636 for LDAPS.",
+                "default": "",
+                "formname": "Custom LDAP Port"
             },
             {
                 "name": "dn_user",
@@ -218,7 +215,7 @@
                 "type": "select",
                 "values": self._tls_kind_values,
                 "description": "TLS Type",
-                "default": 'PLAIN',
+                "default": 'LDAPS',
                 "formname": "Connection Security"
             },
             {
@@ -230,6 +227,13 @@
                 "formname": "Certificate Checks"
             },
             {
+                "name": "cacertdir",
+                "validator": self.validators.UnicodeString(strip=True),
+                "type": "string",
+                "description": "Optional: Custom CA certificate directory for validating LDAPS",
+                "formname": "Custom CA Certificates"
+            },
+            {
                 "name": "base_dn",
                 "validator": self.validators.UnicodeString(strip=True),
                 "type": "string",
@@ -285,10 +289,6 @@
     def use_fake_password(self):
         return True
 
-    def user_activation_state(self):
-        def_user_perms = User.get_default_user().AuthUser.permissions['global']
-        return 'hg.extern_activate.auto' in def_user_perms
-
     def auth(self, userobj, username, password, settings, **kwargs):
         """
         Given a user object (which may be null), username, a plaintext password,
@@ -313,6 +313,7 @@
             'bind_pass': settings.get('dn_pass'),
             'tls_kind': settings.get('tls_kind'),
             'tls_reqcert': settings.get('tls_reqcert'),
+            'cacertdir': settings.get('cacertdir'),
             'ldap_filter': settings.get('filter'),
             'search_scope': settings.get('search_scope'),
             'attr_login': settings.get('attr_login'),
@@ -334,7 +335,6 @@
 
             # old attrs fetched from Kallithea database
             admin = getattr(userobj, 'admin', False)
-            active = getattr(userobj, 'active', self.user_activation_state())
             email = getattr(userobj, 'email', '')
             firstname = getattr(userobj, 'firstname', '')
             lastname = getattr(userobj, 'lastname', '')
@@ -346,19 +346,18 @@
                 'groups': [],
                 'email': get_ldap_attr('attr_email') or email,
                 'admin': admin,
-                'active': active,
-                "active_from_extern": None,
                 'extern_name': user_dn,
             }
             log.info('user %s authenticated correctly', user_data['username'])
             return user_data
 
-        except (LdapUsernameError, LdapPasswordError, LdapImportError):
-            log.error(traceback.format_exc())
-            return None
-        except (Exception,):
-            log.error(traceback.format_exc())
-            return None
+        except LdapUsernameError:
+            log.info('Error authenticating %s with LDAP: User not found', username)
+        except LdapPasswordError:
+            log.info('Error authenticating %s with LDAP: Password error', username)
+        except LdapImportError:
+            log.error('Error authenticating %s with LDAP: LDAP not available', username)
+        return None
 
     def get_managed_fields(self):
         return ['username', 'firstname', 'lastname', 'email', 'password']
--- a/kallithea/lib/auth_modules/auth_pam.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/auth_modules/auth_pam.py	Sun Mar 31 21:28:56 2019 +0200
@@ -115,7 +115,6 @@
 
         # old attrs fetched from Kallithea database
         admin = getattr(userobj, 'admin', False)
-        active = getattr(userobj, 'active', True)
         email = getattr(userobj, 'email', '') or "%s@%s" % (username, socket.gethostname())
         firstname = getattr(userobj, 'firstname', '')
         lastname = getattr(userobj, 'lastname', '')
@@ -127,8 +126,6 @@
             'groups': [g.gr_name for g in grp.getgrall() if username in g.gr_mem],
             'email': email,
             'admin': admin,
-            'active': active,
-            "active_from_extern": None,
             'extern_name': username,
         }
 
--- a/kallithea/lib/base.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -29,40 +29,45 @@
 """
 
 import datetime
+import decorator
 import logging
 import time
 import traceback
+import warnings
 
 import webob.exc
 import paste.httpexceptions
 import paste.auth.basic
 import paste.httpheaders
+from webhelpers.pylonslib import secure_form
 
-from pylons import config, tmpl_context as c, request, session, url
-from pylons.controllers import WSGIController
-from pylons.controllers.util import redirect
-from pylons.templating import render_mako as render  # don't remove this import
-from pylons.i18n.translation import _
+from tg import config, tmpl_context as c, request, response, session, render_template
+from tg import TGController
+from tg.i18n import ugettext as _
 
 from kallithea import __version__, BACKENDS
 
-from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
+from kallithea.config.routing import url
+from kallithea.lib.utils2 import str2bool, safe_unicode, AttributeDict, \
     safe_str, safe_int
 from kallithea.lib import auth_modules
 from kallithea.lib.auth import AuthUser, HasPermissionAnyMiddleware
+from kallithea.lib.compat import json
 from kallithea.lib.utils import get_repo_slug
 from kallithea.lib.exceptions import UserCreationError
 from kallithea.lib.vcs.exceptions import RepositoryError, EmptyRepositoryError, ChangesetDoesNotExistError
 from kallithea.model import meta
 
-from kallithea.model.db import Repository, Ui, User, Setting
-from kallithea.model.notification import NotificationModel
+from kallithea.model.db import PullRequest, Repository, Ui, User, Setting
 from kallithea.model.scm import ScmModel
-from kallithea.model.pull_request import PullRequestModel
 
 log = logging.getLogger(__name__)
 
 
+def render(template_path):
+    return render_template({'url': url}, 'mako', template_path)
+
+
 def _filter_proxy(ip):
     """
     HEADERS can have multiple ips inside the left-most being the original
@@ -98,7 +103,7 @@
 
 def _get_access_path(environ):
     path = environ.get('PATH_INFO')
-    org_req = environ.get('pylons.original_request')
+    org_req = environ.get('tg.original_request')
     if org_req:
         path = org_req.environ.get('PATH_INFO')
     return path
@@ -117,7 +122,9 @@
 
     auth_user = AuthUser(dbuser=user,
                          is_external_auth=is_external_auth)
-    auth_user.set_authenticated()
+    # It should not be possible to explicitly log in as the default user.
+    assert not auth_user.is_default_user
+    auth_user.is_authenticated = True
 
     # Start new session to prevent session fixation attacks.
     session.invalidate()
@@ -140,6 +147,42 @@
     return auth_user
 
 
+def check_locking_state(action, repo_name, user):
+    """
+    Checks locking on this repository, if locking is enabled, and if lock
+    is present. Returns a tuple of make_lock, locked, locked_by. make_lock
+    can have 3 states: None (do nothing), True (make lock), and False
+    (release lock). This value is later propagated to hooks, telling them
+    what to do.
+    """
+    locked = False  # defines that locked error should be thrown to user
+    make_lock = None
+    repo = Repository.get_by_repo_name(repo_name)
+    locked_by = repo.locked
+    if repo and repo.enable_locking:
+        if action == 'push':
+            # Check if repo already is locked !, if it is compare users
+            user_id, _date = locked_by
+            if user.user_id == user_id:
+                log.debug('Got push from user %s, now unlocking', user)
+                # Unlock if we have push from the user who locked
+                make_lock = False
+            else:
+                # Another used tried to push - deny access with something like 423 Locked!
+                locked = True
+        if action == 'pull':
+            if repo.locked[0] and repo.locked[1]:
+                locked = True
+            else:
+                log.debug('Setting lock on repo %s by %s', repo, user)
+                make_lock = True
+    else:
+        log.debug('Repository %s does not have locking enabled', repo)
+    log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
+              make_lock, locked, locked_by)
+    return make_lock, locked, locked_by
+
+
 class BasicAuth(paste.auth.basic.AuthBasicAuthenticator):
 
     def __init__(self, realm, authfunc, auth_http_code=None):
@@ -147,8 +190,14 @@
         self.authfunc = authfunc
         self._rc_auth_http_code = auth_http_code
 
-    def build_authentication(self):
+    def build_authentication(self, environ):
         head = paste.httpheaders.WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
+        # Consume the whole body before sending a response
+        try:
+            request_body_size = int(environ.get('CONTENT_LENGTH', 0))
+        except (ValueError):
+            request_body_size = 0
+        environ['wsgi.input'].read(request_body_size)
         if self._rc_auth_http_code and self._rc_auth_http_code == '403':
             # return 403 if alternative http return code is specified in
             # Kallithea config
@@ -158,22 +207,25 @@
     def authenticate(self, environ):
         authorization = paste.httpheaders.AUTHORIZATION(environ)
         if not authorization:
-            return self.build_authentication()
+            return self.build_authentication(environ)
         (authmeth, auth) = authorization.split(' ', 1)
         if 'basic' != authmeth.lower():
-            return self.build_authentication()
+            return self.build_authentication(environ)
         auth = auth.strip().decode('base64')
         _parts = auth.split(':', 1)
         if len(_parts) == 2:
             username, password = _parts
             if self.authfunc(username, password, environ) is not None:
                 return username
-        return self.build_authentication()
+        return self.build_authentication(environ)
 
     __call__ = authenticate
 
 
 class BaseVCSController(object):
+    """Base controller for handling Mercurial/Git protocol requests
+    (coming from a VCS client, and not a browser).
+    """
 
     def __init__(self, application, config):
         self.application = application
@@ -183,7 +235,72 @@
         # authenticate this VCS request using the authentication modules
         self.authenticate = BasicAuth('', auth_modules.authenticate,
                                       config.get('auth_ret_code'))
-        self.ip_addr = '0.0.0.0'
+
+    def _authorize(self, environ, start_response, action, repo_name, ip_addr):
+        """Authenticate and authorize user.
+
+        Since we're dealing with a VCS client and not a browser, we only
+        support HTTP basic authentication, either directly via raw header
+        inspection, or by using container authentication to delegate the
+        authentication to the web server.
+
+        Returns (user, None) on successful authentication and authorization.
+        Returns (None, wsgi_app) to send the wsgi_app response to the client.
+        """
+        # Check if anonymous access is allowed.
+        default_user = User.get_default_user(cache=True)
+        is_default_user_allowed = (default_user.active and
+            self._check_permission(action, default_user, repo_name, ip_addr))
+        if is_default_user_allowed:
+            return default_user, None
+
+        if not default_user.active:
+            log.debug('Anonymous access is disabled')
+        else:
+            log.debug('Not authorized to access this '
+                      'repository as anonymous user')
+
+        username = None
+        #==============================================================
+        # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
+        # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
+        #==============================================================
+
+        # try to auth based on environ, container auth methods
+        log.debug('Running PRE-AUTH for container based authentication')
+        pre_auth = auth_modules.authenticate('', '', environ)
+        if pre_auth is not None and pre_auth.get('username'):
+            username = pre_auth['username']
+        log.debug('PRE-AUTH got %s as username', username)
+
+        # If not authenticated by the container, running basic auth
+        if not username:
+            self.authenticate.realm = safe_str(self.config['realm'])
+            result = self.authenticate(environ)
+            if isinstance(result, str):
+                paste.httpheaders.AUTH_TYPE.update(environ, 'basic')
+                paste.httpheaders.REMOTE_USER.update(environ, result)
+                username = result
+            else:
+                return None, result.wsgi_application
+
+        #==============================================================
+        # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
+        #==============================================================
+        try:
+            user = User.get_by_username_or_email(username)
+            if user is None or not user.active:
+                return None, webob.exc.HTTPForbidden()
+        except Exception:
+            log.error(traceback.format_exc())
+            return None, webob.exc.HTTPInternalServerError()
+
+        # check permissions for this repository
+        perm = self._check_permission(action, user, repo_name, ip_addr)
+        if not perm:
+            return None, webob.exc.HTTPForbidden()
+
+        return user, None
 
     def _handle_request(self, environ, start_response):
         raise NotImplementedError()
@@ -205,14 +322,6 @@
 
         return '/'.join(data)
 
-    def _invalidate_cache(self, repo_name):
-        """
-        Sets cache for this repository for invalidation on next access
-
-        :param repo_name: full repo name, also a cache key
-        """
-        ScmModel().mark_for_invalidation(repo_name)
-
     def _check_permission(self, action, user, repo_name, ip_addr=None):
         """
         Checks permissions using action (push/pull) user and repository
@@ -248,64 +357,6 @@
     def _get_ip_addr(self, environ):
         return _get_ip_addr(environ)
 
-    def _check_ssl(self, environ):
-        """
-        Checks the SSL check flag and returns False if SSL is not present
-        and required True otherwise
-        """
-        #check if we have SSL required  ! if not it's a bad request !
-        if str2bool(Ui.get_by_key('push_ssl').ui_value):
-            org_proto = environ.get('wsgi._org_proto', environ['wsgi.url_scheme'])
-            if org_proto != 'https':
-                log.debug('proto is %s and SSL is required BAD REQUEST !',
-                          org_proto)
-                return False
-        return True
-
-    def _check_locking_state(self, environ, action, repo, user_id):
-        """
-        Checks locking on this repository, if locking is enabled and lock is
-        present returns a tuple of make_lock, locked, locked_by.
-        make_lock can have 3 states None (do nothing) True, make lock
-        False release lock, This value is later propagated to hooks, which
-        do the locking. Think about this as signals passed to hooks what to do.
-
-        """
-        locked = False  # defines that locked error should be thrown to user
-        make_lock = None
-        repo = Repository.get_by_repo_name(repo)
-        user = User.get(user_id)
-
-        # this is kind of hacky, but due to how mercurial handles client-server
-        # server see all operation on changeset; bookmarks, phases and
-        # obsolescence marker in different transaction, we don't want to check
-        # locking on those
-        obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
-        locked_by = repo.locked
-        if repo and repo.enable_locking and not obsolete_call:
-            if action == 'push':
-                #check if it's already locked !, if it is compare users
-                user_id, _date = repo.locked
-                if user.user_id == user_id:
-                    log.debug('Got push from user %s, now unlocking', user)
-                    # unlock if we have push from user who locked
-                    make_lock = False
-                else:
-                    # we're not the same user who locked, ban with 423 !
-                    locked = True
-            if action == 'pull':
-                if repo.locked[0] and repo.locked[1]:
-                    locked = True
-                else:
-                    log.debug('Setting lock on repo %s by %s', repo, user)
-                    make_lock = True
-
-        else:
-            log.debug('Repository %s do not have locking enabled', repo)
-        log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
-                  make_lock, locked, locked_by)
-        return make_lock, locked, locked_by
-
     def __call__(self, environ, start_response):
         start = time.time()
         try:
@@ -316,11 +367,11 @@
             meta.Session.remove()
 
 
-class BaseController(WSGIController):
+class BaseController(TGController):
 
-    def __before__(self):
+    def _before(self, *args, **kwargs):
         """
-        __before__ is called before controller methods and after __call__
+        _before is called before controller methods and after __call__
         """
         c.kallithea_version = __version__
         rc_config = Setting.get_app_settings()
@@ -331,8 +382,8 @@
         ## DB stored
         c.visual.show_public_icon = str2bool(rc_config.get('show_public_icon'))
         c.visual.show_private_icon = str2bool(rc_config.get('show_private_icon'))
-        c.visual.stylify_metatags = str2bool(rc_config.get('stylify_metatags'))
-        c.visual.dashboard_items = safe_int(rc_config.get('dashboard_items', 100))
+        c.visual.stylify_metalabels = str2bool(rc_config.get('stylify_metalabels'))
+        c.visual.page_size = safe_int(rc_config.get('dashboard_items', 100))
         c.visual.admin_grid_items = safe_int(rc_config.get('admin_grid_items', 100))
         c.visual.repository_fields = str2bool(rc_config.get('repository_fields'))
         c.visual.show_version = str2bool(rc_config.get('show_version'))
@@ -366,47 +417,45 @@
 
         c.repo_name = get_repo_slug(request)  # can be empty
         c.backends = BACKENDS.keys()
-        c.unread_notifications = NotificationModel()\
-                        .get_unread_cnt_for_user(c.authuser.user_id)
 
         self.cut_off_limit = safe_int(config.get('cut_off_limit'))
 
-        c.my_pr_count = PullRequestModel().get_pullrequest_cnt_for_user(c.authuser.user_id)
+        c.my_pr_count = PullRequest.query(reviewer_id=request.authuser.user_id, include_closed=False).count()
 
-        self.sa = meta.Session
-        self.scm_model = ScmModel(self.sa)
+        self.scm_model = ScmModel()
 
     @staticmethod
-    def _determine_auth_user(api_key, session_authuser):
+    def _determine_auth_user(api_key, bearer_token, session_authuser):
         """
-        Create an `AuthUser` object given the API key (if any) and the
-        value of the authuser session cookie.
+        Create an `AuthUser` object given the API key/bearer token
+        (if any) and the value of the authuser session cookie.
         """
 
+        # Authenticate by bearer token
+        if bearer_token is not None:
+            api_key = bearer_token
+
         # Authenticate by API key
-        if api_key:
-            # when using API_KEY we are sure user exists.
-            return AuthUser(dbuser=User.get_by_api_key(api_key),
-                            is_external_auth=True)
+        if api_key is not None:
+            au = AuthUser(dbuser=User.get_by_api_key(api_key),
+                authenticating_api_key=api_key, is_external_auth=True)
+            if au.is_anonymous:
+                log.warning('API key ****%s is NOT valid', api_key[-4:])
+                raise webob.exc.HTTPForbidden(_('Invalid API key'))
+            return au
 
         # Authenticate by session cookie
         # In ancient login sessions, 'authuser' may not be a dict.
         # In that case, the user will have to log in again.
-        if isinstance(session_authuser, dict):
-            try:
-                return AuthUser.from_cookie(session_authuser)
-            except UserCreationError as e:
-                # container auth or other auth functions that create users on
-                # the fly can throw UserCreationError to signal issues with
-                # user creation. Explanation should be provided in the
-                # exception object.
-                from kallithea.lib import helpers as h
-                h.flash(e, 'error', logf=log.error)
+        # v0.3 and earlier included an 'is_authenticated' key; if present,
+        # this must be True.
+        if isinstance(session_authuser, dict) and session_authuser.get('is_authenticated', True):
+            return AuthUser.from_cookie(session_authuser)
 
         # Authenticate by auth_container plugin (if enabled)
         if any(
-            auth_modules.importplugin(name).is_container_auth
-            for name in Setting.get_auth_plugins()
+            plugin.is_container_auth
+            for plugin in auth_modules.get_auth_plugins()
         ):
             try:
                 user_info = auth_modules.authenticate('', '', request.environ)
@@ -423,29 +472,73 @@
         # User is anonymous
         return AuthUser()
 
-    def __call__(self, environ, start_response):
-        """Invoke the Controller"""
+    @staticmethod
+    def _basic_security_checks():
+        """Perform basic security/sanity checks before processing the request."""
+
+        # Only allow the following HTTP request methods.
+        if request.method not in ['GET', 'HEAD', 'POST']:
+            raise webob.exc.HTTPMethodNotAllowed()
+
+        # Also verify the _method override - no longer allowed.
+        if request.params.get('_method') is None:
+            pass # no override, no problem
+        else:
+            raise webob.exc.HTTPMethodNotAllowed()
 
-        # WSGIController.__call__ dispatches to the Controller method
-        # the request is routed to. This routing information is
-        # available in environ['pylons.routes_dict']
+        # Make sure CSRF token never appears in the URL. If so, invalidate it.
+        if secure_form.token_key in request.GET:
+            log.error('CSRF key leak detected')
+            session.pop(secure_form.token_key, None)
+            session.save()
+            from kallithea.lib import helpers as h
+            h.flash(_('CSRF token leak has been detected - all form tokens have been expired'),
+                    category='error')
+
+        # WebOb already ignores request payload parameters for anything other
+        # than POST/PUT, but double-check since other Kallithea code relies on
+        # this assumption.
+        if request.method not in ['POST', 'PUT'] and request.POST:
+            log.error('%r request with payload parameters; WebOb should have stopped this', request.method)
+            raise webob.exc.HTTPBadRequest()
+
+    def __call__(self, environ, context):
         try:
-            self.ip_addr = _get_ip_addr(environ)
+            request.ip_addr = _get_ip_addr(environ)
             # make sure that we update permissions each time we call controller
 
-            #set globals for auth user
-            self.authuser = c.authuser = request.user = self._determine_auth_user(
+            self._basic_security_checks()
+
+            # set globals for auth user
+
+            bearer_token = None
+            try:
+                # Request.authorization may raise ValueError on invalid input
+                type, params = request.authorization
+            except (ValueError, TypeError):
+                pass
+            else:
+                if type.lower() == 'bearer':
+                    bearer_token = params
+
+            authuser = self._determine_auth_user(
                 request.GET.get('api_key'),
+                bearer_token,
                 session.get('authuser'),
             )
 
+            if not AuthUser.check_ip_allowed(authuser, request.ip_addr):
+                raise webob.exc.HTTPForbidden()
+
+            request.authuser = authuser
+
             log.info('IP: %s User: %s accessed %s',
-                self.ip_addr, self.authuser,
+                request.ip_addr, request.authuser,
                 safe_unicode(_get_access_path(environ)),
             )
-            return WSGIController.__call__(self, environ, start_response)
-        finally:
-            meta.Session.remove()
+            return super(BaseController, self).__call__(environ, context)
+        except webob.exc.HTTPException as e:
+            return e
 
 
 class BaseRepoController(BaseController):
@@ -460,8 +553,8 @@
     c.repository_following: weather the current user is following the current repo
     """
 
-    def __before__(self):
-        super(BaseRepoController, self).__before__()
+    def _before(self, *args, **kwargs):
+        super(BaseRepoController, self)._before(*args, **kwargs)
         if c.repo_name:  # extracted from routes
             _dbr = Repository.get_by_repo_name(c.repo_name)
             if not _dbr:
@@ -479,7 +572,7 @@
                 if route in ['repo_creating_home']:
                     return
                 check_url = url('repo_creating_home', repo_name=c.repo_name)
-                return redirect(check_url)
+                raise webob.exc.HTTPFound(location=check_url)
 
             dbr = c.db_repo = _dbr
             c.db_repo_scm_instance = c.db_repo.scm_instance
@@ -489,14 +582,14 @@
                 from kallithea.lib import helpers as h
                 h.flash(_('Repository not found in the filesystem'),
                         category='error')
-                raise paste.httpexceptions.HTTPNotFound()
+                raise webob.exc.HTTPNotFound()
 
             # some globals counter for menu
             c.repository_followers = self.scm_model.get_followers(dbr)
             c.repository_forks = self.scm_model.get_forks(dbr)
             c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
             c.repository_following = self.scm_model.is_following_repo(
-                                    c.repo_name, self.authuser.user_id)
+                                    c.repo_name, request.authuser.user_id)
 
     @staticmethod
     def _get_ref_rev(repo, ref_type, ref_name, returnempty=False):
@@ -522,18 +615,26 @@
             raise webob.exc.HTTPBadRequest()
 
 
-class WSGIResultCloseCallback(object):
-    """Wrap a WSGI result and let close call close after calling the
-    close method on the result.
-    """
-    def __init__(self, result, close):
-        self._result = result
-        self._close = close
+@decorator.decorator
+def jsonify(func, *args, **kwargs):
+    """Action decorator that formats output for JSON
 
-    def __iter__(self):
-        return iter(self._result)
-
-    def close(self):
-        if hasattr(self._result, 'close'):
-            self._result.close()
-        self._close()
+    Given a function that will return content, this decorator will turn
+    the result into JSON, with a content-type of 'application/json' and
+    output it.
+    """
+    response.headers['Content-Type'] = 'application/json; charset=utf-8'
+    data = func(*args, **kwargs)
+    if isinstance(data, (list, tuple)):
+        # A JSON list response is syntactically valid JavaScript and can be
+        # loaded and executed as JavaScript by a malicious third-party site
+        # using <script>, which can lead to cross-site data leaks.
+        # JSON responses should therefore be scalars or objects (i.e. Python
+        # dicts), because a JSON object is a syntax error if intepreted as JS.
+        msg = "JSON responses with Array envelopes are susceptible to " \
+              "cross-site data leak attacks, see " \
+              "https://web.archive.org/web/20120519231904/http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
+        warnings.warn(msg, Warning, 2)
+        log.warning(msg)
+    log.debug("Returning JSON wrapped action output")
+    return json.dumps(data, encoding='utf-8')
--- a/kallithea/lib/caching_query.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/caching_query.py	Sun Mar 31 21:28:56 2019 +0200
@@ -9,8 +9,6 @@
    retrieves results in/from Beaker.
  * FromCache - a query option that establishes caching
    parameters on a Query
- * RelationshipCache - a variant of FromCache which is specific
-   to a query invoked during a lazy load.
  * _params_from_query - extracts value parameters from
    a Query.
 
@@ -54,7 +52,7 @@
     by this query, which are usually literals defined in the
     WHERE clause.
 
-    The FromCache and RelationshipCache mapper options below represent
+    The FromCache mapper option below represent
     the "public" method of configuring this state upon the CachingQuery.
 
     """
@@ -212,65 +210,6 @@
                               self.cache_key)
 
 
-class RelationshipCache(MapperOption):
-    """Specifies that a Query as called within a "lazy load"
-       should load results from a cache."""
-
-    propagate_to_loaders = True
-
-    def __init__(self, region, namespace, attribute):
-        """Construct a new RelationshipCache.
-
-        :param region: the cache region.  Should be a
-        region configured in the Beaker CacheManager.
-
-        :param namespace: the cache namespace.  Should
-        be a name uniquely describing the target Query's
-        lexical structure.
-
-        :param attribute: A Class.attribute which
-        indicates a particular class relationship() whose
-        lazy loader should be pulled from the cache.
-
-        """
-        self.region = region
-        self.namespace = namespace
-        self._relationship_options = {
-            (attribute.property.parent.class_, attribute.property.key): self
-        }
-
-    def process_query_conditionally(self, query):
-        """Process a Query that is used within a lazy loader.
-
-        (the process_query_conditionally() method is a SQLAlchemy
-        hook invoked only within lazyload.)
-
-        """
-        if query._current_path:
-            mapper, key = query._current_path[-2:]
-
-            for cls in mapper.class_.__mro__:
-                if (cls, key) in self._relationship_options:
-                    relationship_option = \
-                        self._relationship_options[(cls, key)]
-                    _set_cache_parameters(
-                            query,
-                            relationship_option.region,
-                            relationship_option.namespace,
-                            None)
-
-    def and_(self, option):
-        """Chain another RelationshipCache option to this one.
-
-        While many RelationshipCache objects can be specified on a single
-        Query separately, chaining them together allows for a more efficient
-        lookup during load.
-
-        """
-        self._relationship_options.update(option._relationship_options)
-        return self
-
-
 def _params_from_query(query):
     """Pull the bind parameter values from a query.
 
@@ -281,8 +220,8 @@
 
     """
     v = []
+
     def visit_bindparam(bind):
-
         if bind.key in query._params:
             value = query._params[bind.key]
         elif bind.callable:
@@ -295,7 +234,7 @@
 
         v.append(value)
     if query._criterion is not None:
-        visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
+        visitors.traverse(query._criterion, {}, {'bindparam': visit_bindparam})
     for f in query._from_obj:
-        visitors.traverse(f, {}, {'bindparam':visit_bindparam})
+        visitors.traverse(f, {}, {'bindparam': visit_bindparam})
     return v
--- a/kallithea/lib/celerylib/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/celerylib/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,58 +26,68 @@
 """
 
 
-import socket
-import traceback
+import os
 import logging
-from os.path import join as jn
-from pylons import config
+
+from tg import config
 
 from hashlib import md5
 from decorator import decorator
 
-from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea import CELERY_ON, CELERY_EAGER
-from kallithea.lib.utils2 import str2bool, safe_str
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.pidlock import DaemonLock, LockHeld
-from kallithea.model import init_model
+from kallithea.model.base import init_model
 from kallithea.model import meta
 
-from sqlalchemy import engine_from_config
-
 
 log = logging.getLogger(__name__)
 
 
-class ResultWrapper(object):
-    def __init__(self, task):
-        self.task = task
+class FakeTask(object):
+    """Fake a sync result to make it look like a finished task"""
+
+    def __init__(self, result):
+        self.result = result
 
-    @LazyProperty
-    def result(self):
-        return self.task
+    def failed(self):
+        return False
+
+    traceback = None # if failed
+
+    task_id = None
 
 
-def run_task(task, *args, **kwargs):
-    global CELERY_ON
+def task(f_org):
+    """Wrapper of celery.task.task, running async if CELERY_ON
+    """
+
     if CELERY_ON:
-        try:
-            t = task.apply_async(args=args, kwargs=kwargs)
-            log.info('running task %s:%s', t.task_id, task)
-            return t
+        def f_async(*args, **kwargs):
+            log.info('executing %s task', f_org.__name__)
+            try:
+                f_org(*args, **kwargs)
+            finally:
+                log.info('executed %s task', f_org.__name__)
+        f_async.__name__ = f_org.__name__
+        from kallithea.lib import celerypylons
+        runner = celerypylons.task(ignore_result=True)(f_async)
 
-        except socket.error as e:
-            if isinstance(e, IOError) and e.errno == 111:
-                log.debug('Unable to connect to celeryd. Sync execution')
-                CELERY_ON = False
-            else:
-                log.error(traceback.format_exc())
-        except KeyError as e:
-                log.debug('Unable to connect to celeryd. Sync execution')
-        except Exception as e:
-            log.error(traceback.format_exc())
+        def f_wrapped(*args, **kwargs):
+            t = runner.apply_async(args=args, kwargs=kwargs)
+            log.info('executing task %s in async mode - id %s', f_org, t.task_id)
+            return t
+    else:
+        def f_wrapped(*args, **kwargs):
+            log.info('executing task %s in sync', f_org.__name__)
+            try:
+                result = f_org(*args, **kwargs)
+            except Exception as e:
+                log.error('exception executing sync task %s in sync: %r', f_org.__name__, e)
+                raise # TODO: return this in FakeTask as with async tasks?
+            return FakeTask(result)
 
-    log.debug('executing task %s in sync mode', task)
-    return ResultWrapper(task(*args, **kwargs))
+    return f_wrapped
 
 
 def __get_lockkey(func, *fargs, **fkwargs):
@@ -98,7 +108,7 @@
 
         log.info('running task with lockkey %s', lockkey)
         try:
-            l = DaemonLock(file_=jn(lockkey_path, lockkey))
+            l = DaemonLock(os.path.join(lockkey_path, lockkey))
             ret = func(*fargs, **fkwargs)
             l.release()
             return ret
@@ -110,9 +120,6 @@
 
 
 def get_session():
-    if CELERY_ON:
-        engine = engine_from_config(config, 'sqlalchemy.db1.')
-        init_model(engine)
     sa = meta.Session()
     return sa
 
--- a/kallithea/lib/celerylib/tasks.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/celerylib/tasks.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,73 +26,61 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
-from celery.decorators import task
-
 import os
 import traceback
 import logging
 import rfc822
-from os.path import join as jn
 
 from time import mktime
 from operator import itemgetter
 from string import lower
 
-from pylons import config
+from tg import config
 
 from kallithea import CELERY_ON
-from kallithea.lib.celerylib import run_task, locked_task, dbsession, \
-    str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
+from kallithea.lib import celerylib
 from kallithea.lib.helpers import person
 from kallithea.lib.rcmail.smtp_mailer import SmtpMailer
-from kallithea.lib.utils import add_cache, action_logger
+from kallithea.lib.utils import action_logger
+from kallithea.lib.utils2 import str2bool
 from kallithea.lib.vcs.utils import author_email
 from kallithea.lib.compat import json, OrderedDict
 from kallithea.lib.hooks import log_create_repository
 
-from kallithea.model.db import Statistics, Repository, User
+from kallithea.model.db import Statistics, RepoGroup, Repository, User
 
 
-add_cache(config)  # pragma: no cover
-
 __all__ = ['whoosh_index', 'get_commits_stats', 'send_email']
 
 
-def get_logger(cls):
-    if CELERY_ON:
-        try:
-            return cls.get_logger()
-        except AttributeError:
-            pass
-    return logging.getLogger(__name__)
+log = logging.getLogger(__name__)
 
 
-@task(ignore_result=True)
-@locked_task
-@dbsession
+@celerylib.task
+@celerylib.locked_task
+@celerylib.dbsession
 def whoosh_index(repo_location, full_index):
     from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
-    DBS = get_session()
+    celerylib.get_session() # initialize database connection
 
     index_location = config['index_dir']
     WhooshIndexingDaemon(index_location=index_location,
-                         repo_location=repo_location, sa=DBS)\
+                         repo_location=repo_location) \
                          .run(full_index=full_index)
 
 
-@task(ignore_result=True)
-@dbsession
+@celerylib.task
+@celerylib.dbsession
 def get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit=100):
-    log = get_logger(get_commits_stats)
-    DBS = get_session()
-    lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
+    DBS = celerylib.get_session()
+    lockkey = celerylib.__get_lockkey('get_commits_stats', repo_name, ts_min_y,
                             ts_max_y)
     lockkey_path = config['app_conf']['cache_dir']
 
     log.info('running task with lockkey %s', lockkey)
 
     try:
-        lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
+        lock = celerylib.DaemonLock(os.path.join(lockkey_path, lockkey))
 
         # for js data compatibility cleans the key for person from '
         akc = lambda k: person(k).replace('"', "")
@@ -116,9 +104,9 @@
         last_cs = None
         timegetter = itemgetter('time')
 
-        dbrepo = DBS.query(Repository)\
+        dbrepo = DBS.query(Repository) \
             .filter(Repository.repo_name == repo_name).scalar()
-        cur_stats = DBS.query(Statistics)\
+        cur_stats = DBS.query(Statistics) \
             .filter(Statistics.repository == dbrepo).scalar()
 
         if cur_stats is not None:
@@ -176,7 +164,7 @@
                                     "changed": len(cs.changed),
                                     "removed": len(cs.removed),
                                    }
-                        co_day_auth_aggr[akc(cs.author)]['data']\
+                        co_day_auth_aggr[akc(cs.author)]['data'] \
                             .append(datadict)
 
             else:
@@ -192,7 +180,7 @@
                                         "schema": ["commits"],
                                         }
 
-            #gather all data by day
+            # gather all data by day
             if k in commits_by_day_aggregate:
                 commits_by_day_aggregate[k] += 1
             else:
@@ -236,19 +224,18 @@
 
         # execute another task if celery is enabled
         if len(repo.revisions) > 1 and CELERY_ON and recurse_limit > 0:
-            recurse_limit -= 1
-            run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y,
-                     recurse_limit)
-        if recurse_limit <= 0:
-            log.debug('Breaking recursive mode due to reach of recurse limit')
-        return True
-    except LockHeld:
-        log.info('LockHeld')
+            get_commits_stats(repo_name, ts_min_y, ts_max_y, recurse_limit - 1)
+        elif recurse_limit <= 0:
+            log.debug('Not recursing - limit has been reached')
+        else:
+            log.debug('Not recursing')
+    except celerylib.LockHeld:
+        log.info('Task with key %s already running', lockkey)
         return 'Task with key %s already running' % lockkey
 
 
-@task(ignore_result=True)
-@dbsession
+@celerylib.task
+@celerylib.dbsession
 def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
     """
     Sends an email with defined parameters from the .ini files.
@@ -261,7 +248,6 @@
     :param headers: dictionary of prepopulated e-mail headers
     :param author: User object of the author of this mail, if known and relevant
     """
-    log = get_logger(send_email)
     assert isinstance(recipients, list), recipients
     if headers is None:
         headers = {}
@@ -279,7 +265,7 @@
         recipients = [u.email for u in User.query()
                       .filter(User.admin == True).all()]
         if email_config.get('email_to') is not None:
-            recipients += [email_config.get('email_to')]
+            recipients += email_config.get('email_to').split(',')
 
         # If there are still no recipients, there are no admins and no address
         # configured in email_to, so return.
@@ -335,17 +321,16 @@
         return False
     return True
 
-@task(ignore_result=False)
-@dbsession
+
+@celerylib.task
+@celerylib.dbsession
 def create_repo(form_data, cur_user):
     from kallithea.model.repo import RepoModel
-    from kallithea.model.user import UserModel
     from kallithea.model.db import Setting
 
-    log = get_logger(create_repo)
-    DBS = get_session()
+    DBS = celerylib.get_session()
 
-    cur_user = UserModel(DBS)._get_user(cur_user)
+    cur_user = User.guess_instance(cur_user)
 
     owner = cur_user
     repo_name = form_data['repo_name']
@@ -368,7 +353,7 @@
     enable_downloads = defs.get('repo_enable_downloads')
 
     try:
-        repo = RepoModel(DBS)._create_repo(
+        repo = RepoModel()._create_repo(
             repo_name=repo_name_full,
             repo_type=repo_type,
             description=description,
@@ -387,14 +372,14 @@
         )
 
         action_logger(cur_user, 'user_created_repo',
-                      form_data['repo_name_full'], '', DBS)
+                      form_data['repo_name_full'], '')
 
         DBS.commit()
         # now create this repo on Filesystem
-        RepoModel(DBS)._create_filesystem_repo(
+        RepoModel()._create_filesystem_repo(
             repo_name=repo_name,
             repo_type=repo_type,
-            repo_group=RepoModel(DBS)._get_repo_group(repo_group),
+            repo_group=RepoGroup.guess_instance(repo_group),
             clone_uri=clone_uri,
         )
         repo = Repository.get_by_repo_name(repo_name_full)
@@ -414,18 +399,14 @@
         if repo:
             Repository.delete(repo.repo_id)
             DBS.commit()
-            RepoModel(DBS)._delete_filesystem_repo(repo)
+            RepoModel()._delete_filesystem_repo(repo)
         raise
 
-    # it's an odd fix to make celery fail task when exception occurs
-    def on_failure(self, *args, **kwargs):
-        pass
-
     return True
 
 
-@task(ignore_result=False)
-@dbsession
+@celerylib.task
+@celerylib.dbsession
 def create_repo_fork(form_data, cur_user):
     """
     Creates a fork of repository using interval VCS methods
@@ -434,13 +415,11 @@
     :param cur_user:
     """
     from kallithea.model.repo import RepoModel
-    from kallithea.model.user import UserModel
 
-    log = get_logger(create_repo_fork)
-    DBS = get_session()
+    DBS = celerylib.get_session()
 
     base_path = Repository.base_path()
-    cur_user = UserModel(DBS)._get_user(cur_user)
+    cur_user = User.guess_instance(cur_user)
 
     repo_name = form_data['repo_name']  # fork in this case
     repo_name_full = form_data['repo_name_full']
@@ -454,9 +433,9 @@
     copy_fork_permissions = form_data.get('copy_permissions')
 
     try:
-        fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
+        fork_of = Repository.guess_instance(form_data.get('fork_parent_id'))
 
-        RepoModel(DBS)._create_repo(
+        RepoModel()._create_repo(
             repo_name=repo_name_full,
             repo_type=repo_type,
             description=form_data['description'],
@@ -469,17 +448,16 @@
             copy_fork_permissions=copy_fork_permissions
         )
         action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
-                      fork_of.repo_name, '', DBS)
+                      fork_of.repo_name, '')
         DBS.commit()
 
-        update_after_clone = form_data['update_after_clone'] # FIXME - unused!
         source_repo_path = os.path.join(base_path, fork_of.repo_name)
 
         # now create this repo on Filesystem
-        RepoModel(DBS)._create_filesystem_repo(
+        RepoModel()._create_filesystem_repo(
             repo_name=repo_name,
             repo_type=repo_type,
-            repo_group=RepoModel(DBS)._get_repo_group(repo_group),
+            repo_group=RepoGroup.guess_instance(repo_group),
             clone_uri=source_repo_path,
         )
         repo = Repository.get_by_repo_name(repo_name_full)
@@ -494,18 +472,14 @@
     except Exception as e:
         log.warning('Exception %s occurred when forking repository, '
                     'doing cleanup...' % e)
-        #rollback things manually !
+        # rollback things manually !
         repo = Repository.get_by_repo_name(repo_name_full)
         if repo:
             Repository.delete(repo.repo_id)
             DBS.commit()
-            RepoModel(DBS)._delete_filesystem_repo(repo)
+            RepoModel()._delete_filesystem_repo(repo)
         raise
 
-    # it's an odd fix to make celery fail task when exception occurs
-    def on_failure(self, *args, **kwargs):
-        pass
-
     return True
 
 
--- a/kallithea/lib/celerypylons/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/celerypylons/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,62 @@
 # -*- coding: utf-8 -*-
 
 """
-Automatically sets the environment variable `CELERY_LOADER` to
-`celerypylons.loader:PylonsLoader`.  This ensures the loader is
-specified when accessing the rest of this package, and allows celery
-to be installed in a webapp just by importing celerypylons::
+Kallithea wrapper of Celery
+
+The Celery configuration is in the Kallithea ini file but must be converted to an
+entirely different format before Celery can use it.
 
-    import celerypylons
+We read the configuration from tg.config at module import time. This module can
+thus not be imported in global scope but must be imported on demand in function
+scope after tg.config has been initialized.
 
+To make sure that the config really has been initialized, we check one of the
+mandatory settings.
 """
 
 import os
 import warnings
 
-CELERYPYLONS_LOADER = 'kallithea.lib.celerypylons.loader.PylonsLoader'
-if os.environ.get('CELERY_LOADER', CELERYPYLONS_LOADER) != CELERYPYLONS_LOADER:
-    warnings.warn("'CELERY_LOADER' environment variable will be overridden by celery-pylons.")
-os.environ['CELERY_LOADER'] = CELERYPYLONS_LOADER
+import tg
+import celery
+
+
+def celery_config(config):
+    """Return Celery config object populated from relevant settings in a config dict, such as tg.config"""
+
+    # Verify .ini file configuration has been loaded
+    assert config['celery.imports'] == 'kallithea.lib.celerylib.tasks', 'Kallithea Celery configuration has not been loaded'
+
+    class CeleryConfig(object):
+        pass
+
+    celery_config = CeleryConfig()
+
+    PREFIXES = """ADMINS BROKER CASSANDRA CELERYBEAT CELERYD CELERYMON CELERY EMAIL SERVER""".split()
+    LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES CELERY_ACCEPT_CONTENT""".split()
+
+    for config_key, config_value in sorted(config.items()):
+        celery_key = config_key.replace('.', '_').upper()
+        if celery_key.split('_', 1)[0] not in PREFIXES:
+            continue
+        if not isinstance(config_value, basestring):
+            continue
+        if celery_key in LIST_PARAMS:
+            celery_value = config_value.split()
+        elif config_value.isdigit():
+            celery_value = int(config_value)
+        elif config_value.lower() in ['true', 'false']:
+            celery_value = config_value.lower() == 'true'
+        else:
+            celery_value = config_value
+        setattr(celery_config, celery_key, celery_value)
+    return celery_config
+
+
+# Create celery app from the TurboGears configuration file
+app = celery.Celery()
+app.config_from_object(celery_config(tg.config))
+
+import celery.result as result
+from celery.task import task
+from celery.bin import worker
--- a/kallithea/lib/celerypylons/commands.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import kallithea
-from kallithea.lib.utils import BasePasterCommand, Command, load_rcextensions
-from celery.app import app_or_default
-from celery.bin import camqadm, celerybeat, celeryd, celeryev
-
-from kallithea.lib.utils2 import str2bool
-
-__all__ = ['CeleryDaemonCommand', 'CeleryBeatCommand',
-           'CAMQPAdminCommand', 'CeleryEventCommand']
-
-
-class CeleryCommand(BasePasterCommand):
-    """Abstract class implements run methods needed for celery
-
-    Starts the celery worker that uses a paste.deploy configuration
-    file.
-    """
-
-    def update_parser(self):
-        """
-        Abstract method.  Allows for the class's parser to be updated
-        before the superclass's `run` method is called.  Necessary to
-        allow options/arguments to be passed through to the underlying
-        celery command.
-        """
-
-        cmd = self.celery_command(app_or_default())
-        for x in cmd.get_options():
-            self.parser.add_option(x)
-
-    def command(self):
-        from pylons import config
-        try:
-            CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
-        except KeyError:
-            CELERY_ON = False
-
-        if not CELERY_ON:
-            raise Exception('Please set use_celery = true in .ini config '
-                            'file before running celeryd')
-        kallithea.CELERY_ON = CELERY_ON
-        load_rcextensions(config['here'])
-        cmd = self.celery_command(app_or_default())
-        return cmd.run(**vars(self.options))
-
-
-class CeleryDaemonCommand(CeleryCommand):
-    """Start the celery worker
-
-    Starts the celery worker that uses a paste.deploy configuration
-    file.
-    """
-    usage = 'CONFIG_FILE [celeryd options...]'
-    summary = __doc__.splitlines()[0]
-    description = "".join(__doc__.splitlines()[2:])
-
-    parser = Command.standard_parser(quiet=True)
-    celery_command = celeryd.WorkerCommand
-
-
-class CeleryBeatCommand(CeleryCommand):
-    """Start the celery beat server
-
-    Starts the celery beat server using a paste.deploy configuration
-    file.
-    """
-    usage = 'CONFIG_FILE [celerybeat options...]'
-    summary = __doc__.splitlines()[0]
-    description = "".join(__doc__.splitlines()[2:])
-
-    parser = Command.standard_parser(quiet=True)
-    celery_command = celerybeat.BeatCommand
-
-
-class CAMQPAdminCommand(CeleryCommand):
-    """CAMQP Admin
-
-    CAMQP celery admin tool.
-    """
-    usage = 'CONFIG_FILE [camqadm options...]'
-    summary = __doc__.splitlines()[0]
-    description = "".join(__doc__.splitlines()[2:])
-
-    parser = Command.standard_parser(quiet=True)
-    celery_command = camqadm.AMQPAdminCommand
-
-
-class CeleryEventCommand(CeleryCommand):
-    """Celery event command.
-
-    Capture celery events.
-    """
-    usage = 'CONFIG_FILE [celeryev options...]'
-    summary = __doc__.splitlines()[0]
-    description = "".join(__doc__.splitlines()[2:])
-
-    parser = Command.standard_parser(quiet=True)
-    celery_command = celeryev.EvCommand
--- a/kallithea/lib/celerypylons/loader.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from celery.loaders.base import BaseLoader
-from pylons import config
-
-to_pylons = lambda x: x.replace('_', '.').lower()
-to_celery = lambda x: x.replace('.', '_').upper()
-
-LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES""".split()
-
-
-class PylonsSettingsProxy(object):
-    """Pylons Settings Proxy
-
-    Proxies settings from pylons.config
-
-    """
-    def __getattr__(self, key):
-        pylons_key = to_pylons(key)
-        try:
-            value = config[pylons_key]
-            if key in LIST_PARAMS:return value.split()
-            return self.type_converter(value)
-        except KeyError:
-            raise AttributeError(pylons_key)
-
-    def get(self, key):
-        try:
-            return self.__getattr__(key)
-        except AttributeError:
-            return None
-
-    def __getitem__(self, key):
-        try:
-            return self.__getattr__(key)
-        except AttributeError:
-            raise KeyError()
-
-    def __setattr__(self, key, value):
-        pylons_key = to_pylons(key)
-        config[pylons_key] = value
-
-    def __setitem__(self, key, value):
-        self.__setattr__(key, value)
-
-    def type_converter(self, value):
-        #cast to int
-        if value.isdigit():
-            return int(value)
-
-        #cast to bool
-        if value.lower() in ['true', 'false']:
-            return value.lower() == 'true'
-        return value
-
-class PylonsLoader(BaseLoader):
-    """Pylons celery loader
-
-    Maps the celery config onto pylons.config
-
-    """
-    def read_configuration(self):
-        self.configured = True
-        return PylonsSettingsProxy()
-
-    def on_worker_init(self):
-        """
-        Import task modules.
-        """
-        self.import_default_modules()
--- a/kallithea/lib/colored_formatter.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/colored_formatter.py	Sun Mar 31 21:28:56 2019 +0200
@@ -42,19 +42,19 @@
 def format_sql(sql):
     sql = sql.replace('\n', '')
     sql = one_space_trim(sql)
-    sql = sql\
-        .replace(',', ',\n\t')\
-        .replace('SELECT', '\n\tSELECT \n\t')\
-        .replace('UPDATE', '\n\tUPDATE \n\t')\
-        .replace('DELETE', '\n\tDELETE \n\t')\
-        .replace('FROM', '\n\tFROM')\
-        .replace('ORDER BY', '\n\tORDER BY')\
-        .replace('LIMIT', '\n\tLIMIT')\
-        .replace('WHERE', '\n\tWHERE')\
-        .replace('AND', '\n\tAND')\
-        .replace('LEFT', '\n\tLEFT')\
-        .replace('INNER', '\n\tINNER')\
-        .replace('INSERT', '\n\tINSERT')\
+    sql = sql \
+        .replace(',', ',\n\t') \
+        .replace('SELECT', '\n\tSELECT \n\t') \
+        .replace('UPDATE', '\n\tUPDATE \n\t') \
+        .replace('DELETE', '\n\tDELETE \n\t') \
+        .replace('FROM', '\n\tFROM') \
+        .replace('ORDER BY', '\n\tORDER BY') \
+        .replace('LIMIT', '\n\tLIMIT') \
+        .replace('WHERE', '\n\tWHERE') \
+        .replace('AND', '\n\tAND') \
+        .replace('LEFT', '\n\tLEFT') \
+        .replace('INNER', '\n\tINNER') \
+        .replace('INSERT', '\n\tINSERT') \
         .replace('DELETE', '\n\tDELETE')
     return sql
 
--- a/kallithea/lib/compat.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/compat.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,10 +27,9 @@
 """
 
 
+import sys
 import os
 import functools
-import importlib
-from kallithea import __py_version__, is_windows
 
 #==============================================================================
 # json
@@ -40,41 +39,23 @@
 # alias for formatted json
 formatted_json = functools.partial(json.dumps, indent=4, sort_keys=True)
 
-if __py_version__ >= (2, 7):
+
+#==============================================================================
+# unittest
+#==============================================================================
+if sys.version_info >= (2, 7):
     import unittest
 else:
     import unittest2 as unittest
 
-#==============================================================================
-# izip_longest
-#==============================================================================
-try:
-    from itertools import izip_longest
-except ImportError:
-    import itertools
-
-    def izip_longest(*args, **kwds):
-        fillvalue = kwds.get("fillvalue")
-
-        def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
-            yield counter()  # yields the fillvalue, or raises IndexError
-
-        fillers = itertools.repeat(fillvalue)
-        iters = [itertools.chain(it, sentinel(), fillers)
-                    for it in args]
-        try:
-            for tup in itertools.izip(*iters):
-                yield tup
-        except IndexError:
-            pass
-
 
 #==============================================================================
-# OrderedDict
+# OrderedDict - Python 2.7 could perhaps use collections.OrderedDict
 #==============================================================================
 
 # Python Software Foundation License
 
+
 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
 # "!=" should be faster.
 class _Nil(object):
@@ -94,19 +75,17 @@
         else:
             return NotImplemented
 
+
 _nil = _Nil()
 
 
-class _odict(object):
+class OrderedDict(dict):
     """Ordered dict data structure, with O(1) complexity for dict operations
     that modify one element.
 
     Overwriting values doesn't change their original sequential order.
     """
 
-    def _dict_impl(self):
-        return None
-
     def __init__(self, data=(), **kwds):
         """This doesn't accept keyword initialization as normal dicts to avoid
         a trap - inside a function or method the keyword args are accessible
@@ -116,7 +95,7 @@
         if kwds:
             raise TypeError("__init__() of ordered dict takes no keyword "
                             "arguments to avoid an ordering trap.")
-        self._dict_impl().__init__(self)
+        dict.__init__(self)
         # If you give a normal dict, then the order of elements is undefined
         if hasattr(data, "iteritems"):
             for key, val in data.iteritems():
@@ -127,57 +106,53 @@
 
     # Double-linked list header
     def _get_lh(self):
-        dict_impl = self._dict_impl()
         if not hasattr(self, '_lh'):
-            dict_impl.__setattr__(self, '_lh', _nil)
-        return dict_impl.__getattribute__(self, '_lh')
+            dict.__setattr__(self, '_lh', _nil)
+        return dict.__getattribute__(self, '_lh')
 
     def _set_lh(self, val):
-        self._dict_impl().__setattr__(self, '_lh', val)
+        dict.__setattr__(self, '_lh', val)
 
     lh = property(_get_lh, _set_lh)
 
     # Double-linked list tail
     def _get_lt(self):
-        dict_impl = self._dict_impl()
         if not hasattr(self, '_lt'):
-            dict_impl.__setattr__(self, '_lt', _nil)
-        return dict_impl.__getattribute__(self, '_lt')
+            dict.__setattr__(self, '_lt', _nil)
+        return dict.__getattribute__(self, '_lt')
 
     def _set_lt(self, val):
-        self._dict_impl().__setattr__(self, '_lt', val)
+        dict.__setattr__(self, '_lt', val)
 
     lt = property(_get_lt, _set_lt)
 
     def __getitem__(self, key):
-        return self._dict_impl().__getitem__(self, key)[1]
+        return dict.__getitem__(self, key)[1]
 
     def __setitem__(self, key, val):
-        dict_impl = self._dict_impl()
         try:
-            dict_impl.__getitem__(self, key)[1] = val
+            dict.__getitem__(self, key)[1] = val
         except KeyError:
-            new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
-            dict_impl.__setitem__(self, key, new)
-            if dict_impl.__getattribute__(self, 'lt') == _nil:
-                dict_impl.__setattr__(self, 'lh', key)
+            new = [dict.__getattribute__(self, 'lt'), val, _nil]
+            dict.__setitem__(self, key, new)
+            if dict.__getattribute__(self, 'lt') == _nil:
+                dict.__setattr__(self, 'lh', key)
             else:
-                dict_impl.__getitem__(
-                    self, dict_impl.__getattribute__(self, 'lt'))[2] = key
-            dict_impl.__setattr__(self, 'lt', key)
+                dict.__getitem__(
+                    self, dict.__getattribute__(self, 'lt'))[2] = key
+            dict.__setattr__(self, 'lt', key)
 
     def __delitem__(self, key):
-        dict_impl = self._dict_impl()
-        pred, _, succ = self._dict_impl().__getitem__(self, key)
+        pred, _, succ = dict.__getitem__(self, key)
         if pred == _nil:
-            dict_impl.__setattr__(self, 'lh', succ)
+            dict.__setattr__(self, 'lh', succ)
         else:
-            dict_impl.__getitem__(self, pred)[2] = succ
+            dict.__getitem__(self, pred)[2] = succ
         if succ == _nil:
-            dict_impl.__setattr__(self, 'lt', pred)
+            dict.__setattr__(self, 'lt', pred)
         else:
-            dict_impl.__getitem__(self, succ)[0] = pred
-        dict_impl.__delitem__(self, key)
+            dict.__getitem__(self, succ)[0] = pred
+        dict.__delitem__(self, key)
 
     def __contains__(self, key):
         return key in self.keys()
@@ -198,16 +173,15 @@
 
     def get(self, k, x=None):
         if k in self:
-            return self._dict_impl().__getitem__(self, k)[1]
+            return dict.__getitem__(self, k)[1]
         else:
             return x
 
     def __iter__(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
+        curr_key = dict.__getattribute__(self, 'lh')
         while curr_key != _nil:
             yield curr_key
-            curr_key = dict_impl.__getitem__(self, curr_key)[2]
+            curr_key = dict.__getitem__(self, curr_key)[2]
 
     iterkeys = __iter__
 
@@ -215,20 +189,18 @@
         return list(self.iterkeys())
 
     def itervalues(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
+        curr_key = dict.__getattribute__(self, 'lh')
         while curr_key != _nil:
-            _, val, curr_key = dict_impl.__getitem__(self, curr_key)
+            _, val, curr_key = dict.__getitem__(self, curr_key)
             yield val
 
     def values(self):
         return list(self.itervalues())
 
     def iteritems(self):
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lh')
+        curr_key = dict.__getattribute__(self, 'lh')
         while curr_key != _nil:
-            _, val, next_key = dict_impl.__getitem__(self, curr_key)
+            _, val, next_key = dict.__getitem__(self, curr_key)
             yield curr_key, val
             curr_key = next_key
 
@@ -249,10 +221,9 @@
         self.__init__(items)
 
     def clear(self):
-        dict_impl = self._dict_impl()
-        dict_impl.clear(self)
-        dict_impl.__setattr__(self, 'lh', _nil)
-        dict_impl.__setattr__(self, 'lt', _nil)
+        dict.clear(self)
+        dict.__setattr__(self, 'lh', _nil)
+        dict.__setattr__(self, 'lt', _nil)
 
     def copy(self):
         return self.__class__(self)
@@ -285,8 +256,7 @@
 
     def popitem(self):
         try:
-            dict_impl = self._dict_impl()
-            key = dict_impl.__getattribute__(self, 'lt')
+            key = dict.__getattribute__(self, 'lt')
             return key, self.pop(key)
         except KeyError:
             raise KeyError("'popitem(): ordered dictionary is empty'")
@@ -294,11 +264,10 @@
     def riterkeys(self):
         """To iterate on keys in reversed order.
         """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
+        curr_key = dict.__getattribute__(self, 'lt')
         while curr_key != _nil:
             yield curr_key
-            curr_key = dict_impl.__getitem__(self, curr_key)[0]
+            curr_key = dict.__getitem__(self, curr_key)[0]
 
     __reversed__ = riterkeys
 
@@ -310,10 +279,9 @@
     def ritervalues(self):
         """To iterate on values in reversed order.
         """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
+        curr_key = dict.__getattribute__(self, 'lt')
         while curr_key != _nil:
-            curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
+            curr_key, val, _ = dict.__getitem__(self, curr_key)
             yield val
 
     def rvalues(self):
@@ -324,10 +292,9 @@
     def riteritems(self):
         """To iterate on (key, value) in reversed order.
         """
-        dict_impl = self._dict_impl()
-        curr_key = dict_impl.__getattribute__(self, 'lt')
+        curr_key = dict.__getattribute__(self, 'lt')
         while curr_key != _nil:
-            pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
+            pred_key, val, _ = dict.__getitem__(self, curr_key)
             yield curr_key, val
             curr_key = pred_key
 
@@ -338,34 +305,27 @@
 
     def firstkey(self):
         if self:
-            return self._dict_impl().__getattribute__(self, 'lh')
+            return dict.__getattribute__(self, 'lh')
         else:
             raise KeyError("'firstkey(): ordered dictionary is empty'")
 
     def lastkey(self):
         if self:
-            return self._dict_impl().__getattribute__(self, 'lt')
+            return dict.__getattribute__(self, 'lt')
         else:
             raise KeyError("'lastkey(): ordered dictionary is empty'")
 
     def as_dict(self):
-        return self._dict_impl()(self.items())
+        return dict(self.items())
 
     def _repr(self):
         """_repr(): low level repr of the whole data contained in the odict.
         Useful for debugging.
         """
-        dict_impl = self._dict_impl()
         form = "odict low level repr lh,lt,data: %r, %r, %s"
-        return form % (dict_impl.__getattribute__(self, 'lh'),
-                       dict_impl.__getattribute__(self, 'lt'),
-                       dict_impl.__repr__(self))
-
-
-class OrderedDict(_odict, dict):
-
-    def _dict_impl(self):
-        return dict
+        return form % (dict.__getattribute__(self, 'lh'),
+                       dict.__getattribute__(self, 'lt'),
+                       dict.__repr__(self))
 
 
 #==============================================================================
@@ -377,13 +337,13 @@
 #==============================================================================
 # Hybrid property/method
 #==============================================================================
-from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
+from sqlalchemy.ext.hybrid import hybrid_property
 
 
 #==============================================================================
-# kill FUNCTIONS
+# kill
 #==============================================================================
-if is_windows:
+if os.name == 'nt': # Windows
     import ctypes
 
     def kill(pid, sig):
@@ -394,175 +354,3 @@
 
 else:
     kill = os.kill
-
-
-#==============================================================================
-# itertools.product
-#==============================================================================
-
-try:
-    from itertools import product
-except ImportError:
-    def product(*args, **kwds):
-        # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
-        # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
-        pools = map(tuple, args) * kwds.get('repeat', 1)
-        result = [[]]
-        for pool in pools:
-            result = [x + [y] for x in result for y in pool]
-        for prod in result:
-            yield tuple(prod)
-
-
-#==============================================================================
-# BytesIO
-#==============================================================================
-
-try:
-    from io import BytesIO
-except ImportError:
-    from cStringIO import StringIO as BytesIO
-
-
-#==============================================================================
-# bytes
-#==============================================================================
-if __py_version__ >= (2, 6):
-    _bytes = bytes
-else:
-    # in py2.6 bytes is a synonym for str
-    _bytes = str
-
-if __py_version__ >= (2, 6):
-    _bytearray = bytearray
-else:
-    import array
-    # no idea if this is correct but all integration tests are passing
-    # i think we never use bytearray anyway
-    _bytearray = array
-
-
-#==============================================================================
-# deque
-#==============================================================================
-
-if __py_version__ >= (2, 6):
-    from collections import deque
-else:
-    #need to implement our own deque with maxlen
-    class deque(object):
-
-        def __init__(self, iterable=(), maxlen= -1):
-            if not hasattr(self, 'data'):
-                self.left = self.right = 0
-                self.data = {}
-            self.maxlen = maxlen or -1
-            self.extend(iterable)
-
-        def append(self, x):
-            self.data[self.right] = x
-            self.right += 1
-            if self.maxlen != -1 and len(self) > self.maxlen:
-                self.popleft()
-
-        def appendleft(self, x):
-            self.left -= 1
-            self.data[self.left] = x
-            if self.maxlen != -1 and len(self) > self.maxlen:
-                self.pop()
-
-        def pop(self):
-            if self.left == self.right:
-                raise IndexError('cannot pop from empty deque')
-            self.right -= 1
-            elem = self.data[self.right]
-            del self.data[self.right]
-            return elem
-
-        def popleft(self):
-            if self.left == self.right:
-                raise IndexError('cannot pop from empty deque')
-            elem = self.data[self.left]
-            del self.data[self.left]
-            self.left += 1
-            return elem
-
-        def clear(self):
-            self.data.clear()
-            self.left = self.right = 0
-
-        def extend(self, iterable):
-            for elem in iterable:
-                self.append(elem)
-
-        def extendleft(self, iterable):
-            for elem in iterable:
-                self.appendleft(elem)
-
-        def rotate(self, n=1):
-            if self:
-                n %= len(self)
-                for i in xrange(n):
-                    self.appendleft(self.pop())
-
-        def __getitem__(self, i):
-            if i < 0:
-                i += len(self)
-            try:
-                return self.data[i + self.left]
-            except KeyError:
-                raise IndexError
-
-        def __setitem__(self, i, value):
-            if i < 0:
-                i += len(self)
-            try:
-                self.data[i + self.left] = value
-            except KeyError:
-                raise IndexError
-
-        def __delitem__(self, i):
-            size = len(self)
-            if not (-size <= i < size):
-                raise IndexError
-            data = self.data
-            if i < 0:
-                i += size
-            for j in xrange(self.left + i, self.right - 1):
-                data[j] = data[j + 1]
-            self.pop()
-
-        def __len__(self):
-            return self.right - self.left
-
-        def __cmp__(self, other):
-            if type(self) != type(other):
-                return cmp(type(self), type(other))
-            return cmp(list(self), list(other))
-
-        def __repr__(self, _track=[]):
-            if id(self) in _track:
-                return '...'
-            _track.append(id(self))
-            r = 'deque(%r, maxlen=%s)' % (list(self), self.maxlen)
-            _track.remove(id(self))
-            return r
-
-        def __getstate__(self):
-            return (tuple(self),)
-
-        def __setstate__(self, s):
-            self.__init__(s[0])
-
-        def __hash__(self):
-            raise TypeError
-
-        def __copy__(self):
-            return self.__class__(self)
-
-        def __deepcopy__(self, memo={}):
-            from copy import deepcopy
-            result = self.__class__()
-            memo[id(self)] = result
-            result.__init__(deepcopy(tuple(self), memo))
-            return result
--- a/kallithea/lib/db_manage.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/db_manage.py	Sun Mar 31 21:28:56 2019 +0200
@@ -31,14 +31,16 @@
 import time
 import uuid
 import logging
-from os.path import dirname as dn, join as jn
+import sqlalchemy
+from os.path import dirname
 
-from kallithea import __dbversion__, __py_version__, EXTERN_TYPE_INTERNAL, DB_MIGRATIONS
+import alembic.config
+import alembic.command
+
 from kallithea.model.user import UserModel
-from kallithea.lib.utils import ask_ok
-from kallithea.model import init_model
+from kallithea.model.base import init_model
 from kallithea.model.db import User, Permission, Ui, \
-    Setting, UserToPerm, DbMigrateVersion, RepoGroup, \
+    Setting, UserToPerm, RepoGroup, \
     UserRepoGroupToPerm, CacheInvalidation, Repository
 
 from sqlalchemy.engine import create_engine
@@ -52,36 +54,30 @@
 log = logging.getLogger(__name__)
 
 
-def notify(msg):
-    """
-    Notification for migrations messages
-    """
-    ml = len(msg) + (4 * 2)
-    print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
-
-
 class DbManage(object):
-    def __init__(self, log_sql, dbconf, root, tests=False, SESSION=None, cli_args={}):
+    def __init__(self, dbconf, root, tests=False, SESSION=None, cli_args=None):
         self.dbname = dbconf.split('/')[-1]
         self.tests = tests
         self.root = root
         self.dburi = dbconf
-        self.log_sql = log_sql
         self.db_exists = False
-        self.cli_args = cli_args
+        self.cli_args = cli_args or {}
         self.init_db(SESSION=SESSION)
 
+    def _ask_ok(self, msg):
+        """Invoke ask_ok unless the force_ask option provides the answer"""
         force_ask = self.cli_args.get('force_ask')
         if force_ask is not None:
-            global ask_ok
-            ask_ok = lambda *args, **kwargs: force_ask
+            return force_ask
+        from kallithea.lib.utils2 import ask_ok
+        return ask_ok(msg)
 
     def init_db(self, SESSION=None):
         if SESSION:
             self.sa = SESSION
         else:
-            #init new sessions
-            engine = create_engine(self.dburi, echo=self.log_sql)
+            # init new sessions
+            engine = create_engine(self.dburi)
             init_model(engine)
             self.sa = Session()
 
@@ -94,103 +90,62 @@
         if self.tests:
             destroy = True
         else:
-            destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
+            destroy = self._ask_ok('Are you sure to destroy old database ? [y/n]')
         if not destroy:
             print 'Nothing done.'
             sys.exit(0)
         if destroy:
-            Base.metadata.drop_all()
+            # drop and re-create old schemas
+
+            url = sqlalchemy.engine.url.make_url(self.dburi)
+            database = url.database
+
+            # Some databases enforce foreign key constraints and Base.metadata.drop_all() doesn't work
+            if url.drivername == 'mysql':
+                url.database = None  # don't connect to the database (it might not exist)
+                engine = sqlalchemy.create_engine(url)
+                with engine.connect() as conn:
+                    conn.execute('DROP DATABASE IF EXISTS ' + database)
+                    conn.execute('CREATE DATABASE ' + database)
+            elif url.drivername == 'postgresql':
+                from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
+                url.database = 'postgres'  # connect to the system database (as the real one might not exist)
+                engine = sqlalchemy.create_engine(url)
+                with engine.connect() as conn:
+                    conn.connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
+                    conn.execute('DROP DATABASE IF EXISTS ' + database)
+                    conn.execute('CREATE DATABASE ' + database)
+            else:
+                # known to work on SQLite - possibly not on other databases with strong referential integrity
+                Base.metadata.drop_all()
 
         checkfirst = not override
         Base.metadata.create_all(checkfirst=checkfirst)
-        log.info('Created tables for %s', self.dbname)
-
-    def set_db_version(self):
-        ver = DbMigrateVersion()
-        ver.version = __dbversion__
-        ver.repository_id = DB_MIGRATIONS
-        ver.repository_path = 'versions'
-        self.sa.add(ver)
-        log.info('db version set to: %s', __dbversion__)
-
-    def upgrade(self):
-        """
-        Upgrades given database schema to given revision following
-        all needed steps, to perform the upgrade
-
-        """
-
-        from kallithea.lib.dbmigrate.migrate.versioning import api
-        from kallithea.lib.dbmigrate.migrate.exceptions import \
-            DatabaseNotControlledError
-
-        if 'sqlite' in self.dburi:
-            print (
-               '********************** WARNING **********************\n'
-               'Make sure your version of sqlite is at least 3.7.X.  \n'
-               'Earlier versions are known to fail on some migrations\n'
-               '*****************************************************\n')
-
-        upgrade = ask_ok('You are about to perform database upgrade, make '
-                         'sure You backed up your database before. '
-                         'Continue ? [y/n]')
-        if not upgrade:
-            print 'No upgrade performed'
-            sys.exit(0)
 
-        repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
-                             'kallithea/lib/dbmigrate')
-        db_uri = self.dburi
-
-        try:
-            curr_version = api.db_version(db_uri, repository_path)
-            msg = ('Found current database under version '
-                   'control with version %s' % curr_version)
-
-        except (RuntimeError, DatabaseNotControlledError):
-            curr_version = 1
-            msg = ('Current database is not under version control. Setting '
-                   'as version %s' % curr_version)
-            api.version_control(db_uri, repository_path, curr_version)
-
-        notify(msg)
-        if curr_version == __dbversion__:
-            print 'This database is already at the newest version'
-            sys.exit(0)
+        # Create an Alembic configuration and generate the version table,
+        # "stamping" it with the most recent Alembic migration revision, to
+        # tell Alembic that all the schema upgrades are already in effect.
+        alembic_cfg = alembic.config.Config()
+        alembic_cfg.set_main_option('script_location', 'kallithea:alembic')
+        alembic_cfg.set_main_option('sqlalchemy.url', self.dburi)
+        # This command will give an error in an Alembic multi-head scenario,
+        # but in practice, such a scenario should not come up during database
+        # creation, even during development.
+        alembic.command.stamp(alembic_cfg, 'head')
 
-        # clear cache keys
-        log.info("Clearing cache keys now...")
-        CacheInvalidation.clear_cache()
-
-        upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
-        notify('attempting to do database upgrade from '
-               'version %s to version %s' % (curr_version, __dbversion__))
-
-        # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
-        _step = None
-        for step in upgrade_steps:
-            notify('performing upgrade step %s' % step)
-            time.sleep(0.5)
-
-            api.upgrade(db_uri, repository_path, step)
-            notify('schema upgrade for step %s completed' % (step,))
-
-            _step = step
-
-        notify('upgrade to version %s successful' % _step)
+        log.info('Created tables for %s', self.dbname)
 
     def fix_repo_paths(self):
         """
         Fixes a old kallithea version path into new one without a '*'
         """
 
-        paths = self.sa.query(Ui)\
-                .filter(Ui.ui_key == '/')\
+        paths = Ui.query() \
+                .filter(Ui.ui_key == '/') \
                 .scalar()
 
         paths.ui_value = paths.ui_value.replace('*', '')
 
-        self.sa.add(paths)
         self.sa.commit()
 
     def fix_default_user(self):
@@ -198,15 +153,12 @@
         Fixes a old default user with some 'nicer' default values,
         used mostly for anonymous access
         """
-        def_user = self.sa.query(User)\
-                .filter(User.username == User.DEFAULT_USER)\
-                .one()
+        def_user = User.query().filter_by(is_default_user=True).one()
 
         def_user.name = 'Anonymous'
         def_user.lastname = 'User'
         def_user.email = 'anonymous@kallithea-scm.org'
 
-        self.sa.add(def_user)
         self.sa.commit()
 
     def fix_settings(self):
@@ -223,11 +175,9 @@
         if not self.tests:
             import getpass
 
-            # defaults
-            defaults = self.cli_args
-            username = defaults.get('username')
-            password = defaults.get('password')
-            email = defaults.get('email')
+            username = self.cli_args.get('username')
+            password = self.cli_args.get('password')
+            email = self.cli_args.get('email')
 
             def get_password():
                 password = getpass.getpass('Specify admin password '
@@ -247,7 +197,7 @@
             if password is None:
                 password = get_password()
                 if not password:
-                    #second try
+                    # second try
                     password = get_password()
                     if not password:
                         sys.exit()
@@ -256,7 +206,7 @@
             self.create_user(username, password, email, True)
         else:
             log.info('creating admin and regular test users')
-            from kallithea.tests import TEST_USER_ADMIN_LOGIN, \
+            from kallithea.tests.base import TEST_USER_ADMIN_LOGIN, \
             TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
             TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
             TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
@@ -271,88 +221,6 @@
             self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
                              TEST_USER_REGULAR2_EMAIL, False)
 
-    def create_ui_settings(self, repo_store_path):
-        """
-        Creates ui settings, fills out hooks
-        """
-
-        #HOOKS
-        hooks1_key = Ui.HOOK_UPDATE
-        hooks1_ = self.sa.query(Ui)\
-            .filter(Ui.ui_key == hooks1_key).scalar()
-
-        hooks1 = Ui() if hooks1_ is None else hooks1_
-        hooks1.ui_section = 'hooks'
-        hooks1.ui_key = hooks1_key
-        hooks1.ui_value = 'hg update >&2'
-        hooks1.ui_active = False
-        self.sa.add(hooks1)
-
-        hooks2_key = Ui.HOOK_REPO_SIZE
-        hooks2_ = self.sa.query(Ui)\
-            .filter(Ui.ui_key == hooks2_key).scalar()
-        hooks2 = Ui() if hooks2_ is None else hooks2_
-        hooks2.ui_section = 'hooks'
-        hooks2.ui_key = hooks2_key
-        hooks2.ui_value = 'python:kallithea.lib.hooks.repo_size'
-        self.sa.add(hooks2)
-
-        hooks3 = Ui()
-        hooks3.ui_section = 'hooks'
-        hooks3.ui_key = Ui.HOOK_PUSH
-        hooks3.ui_value = 'python:kallithea.lib.hooks.log_push_action'
-        self.sa.add(hooks3)
-
-        hooks4 = Ui()
-        hooks4.ui_section = 'hooks'
-        hooks4.ui_key = Ui.HOOK_PRE_PUSH
-        hooks4.ui_value = 'python:kallithea.lib.hooks.pre_push'
-        self.sa.add(hooks4)
-
-        hooks5 = Ui()
-        hooks5.ui_section = 'hooks'
-        hooks5.ui_key = Ui.HOOK_PULL
-        hooks5.ui_value = 'python:kallithea.lib.hooks.log_pull_action'
-        self.sa.add(hooks5)
-
-        hooks6 = Ui()
-        hooks6.ui_section = 'hooks'
-        hooks6.ui_key = Ui.HOOK_PRE_PULL
-        hooks6.ui_value = 'python:kallithea.lib.hooks.pre_pull'
-        self.sa.add(hooks6)
-
-        # enable largefiles
-        largefiles = Ui()
-        largefiles.ui_section = 'extensions'
-        largefiles.ui_key = 'largefiles'
-        largefiles.ui_value = ''
-        self.sa.add(largefiles)
-
-        # set default largefiles cache dir, defaults to
-        # /repo location/.cache/largefiles
-        largefiles = Ui()
-        largefiles.ui_section = 'largefiles'
-        largefiles.ui_key = 'usercache'
-        largefiles.ui_value = os.path.join(repo_store_path, '.cache',
-                                           'largefiles')
-        self.sa.add(largefiles)
-
-        # enable hgsubversion disabled by default
-        hgsubversion = Ui()
-        hgsubversion.ui_section = 'extensions'
-        hgsubversion.ui_key = 'hgsubversion'
-        hgsubversion.ui_value = ''
-        hgsubversion.ui_active = False
-        self.sa.add(hgsubversion)
-
-        # enable hggit disabled by default
-        hggit = Ui()
-        hggit.ui_section = 'extensions'
-        hggit.ui_key = 'hggit'
-        hggit.ui_value = ''
-        hggit.ui_active = False
-        self.sa.add(hggit)
-
     def create_auth_plugin_options(self, skip_existing=False):
         """
         Create default auth plugin settings, and make it active
@@ -361,8 +229,8 @@
         """
 
         for k, v, t in [('auth_plugins', 'kallithea.lib.auth_modules.auth_internal', 'list'),
-                     ('auth_internal_enabled', 'True', 'bool')]:
-            if skip_existing and Setting.get_by_name(k) != None:
+                        ('auth_internal_enabled', 'True', 'bool')]:
+            if skip_existing and Setting.get_by_name(k) is not None:
                 log.debug('Skipping option %s', k)
                 continue
             setting = Setting(k, v, t)
@@ -388,17 +256,15 @@
         def_usr = User.get_default_user()
         for g in RepoGroup.query().all():
             g.group_name = g.get_new_name(g.name)
-            self.sa.add(g)
             # get default perm
-            default = UserRepoGroupToPerm.query()\
-                .filter(UserRepoGroupToPerm.group == g)\
-                .filter(UserRepoGroupToPerm.user == def_usr)\
+            default = UserRepoGroupToPerm.query() \
+                .filter(UserRepoGroupToPerm.group == g) \
+                .filter(UserRepoGroupToPerm.user == def_usr) \
                 .scalar()
 
             if default is None:
                 log.debug('missing default permission for group %s adding', g)
-                perm_obj = RepoGroupModel()._create_default_perms(g)
-                self.sa.add(perm_obj)
+                RepoGroupModel()._create_default_perms(g)
 
     def reset_permissions(self, username):
         """
@@ -411,7 +277,7 @@
         if not default_user:
             return
 
-        u2p = UserToPerm.query()\
+        u2p = UserToPerm.query() \
             .filter(UserToPerm.user == default_user).all()
         fixed = False
         if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
@@ -422,11 +288,11 @@
         return fixed
 
     def update_repo_info(self):
-        RepoModel.update_repoinfo()
+        for repo in Repository.query():
+            repo.update_changeset_cache()
 
     def config_prompt(self, test_repo_path='', retries=3):
-        defaults = self.cli_args
-        _path = defaults.get('repos_location')
+        _path = self.cli_args.get('repos_location')
         if retries == 3:
             log.info('Setting up repositories config')
 
@@ -458,7 +324,7 @@
         # check write access, warn user about non writeable paths
         elif not os.access(path, os.W_OK) and path_ok:
             log.warning('No write permission to given path %s', path)
-            if not ask_ok('Given path %s is not writeable, do you want to '
+            if not self._ask_ok('Given path %s is not writeable, do you want to '
                           'continue with read only mode ? [y/n]' % (path,)):
                 log.error('Canceled by user')
                 sys.exit(-1)
@@ -466,8 +332,10 @@
         if retries == 0:
             sys.exit('max retries reached')
         if not path_ok:
+            if _path is not None:
+                sys.exit('Invalid repo path: %s' % _path)
             retries -= 1
-            return self.config_prompt(test_repo_path, retries)
+            return self.config_prompt(test_repo_path, retries) # recursing!!!
 
         real_path = os.path.normpath(os.path.realpath(path))
 
@@ -478,21 +346,28 @@
 
     def create_settings(self, path):
 
-        self.create_ui_settings(path)
-
         ui_config = [
-            ('web', 'push_ssl', 'false'),
-            ('web', 'allow_archive', 'gz zip bz2'),
-            ('web', 'allow_push', '*'),
-            ('web', 'baseurl', '/'),
-            ('paths', '/', path),
-            #('phases', 'publish', 'false')
+            ('web', 'allow_archive', 'gz zip bz2', True),
+            ('web', 'baseurl', '/', True),
+            ('paths', '/', path, True),
+            #('phases', 'publish', 'false', False)
+            ('hooks', Ui.HOOK_UPDATE, 'hg update >&2', False),
+            ('hooks', Ui.HOOK_REPO_SIZE, 'python:kallithea.lib.hooks.repo_size', True),
+            ('hooks', Ui.HOOK_PUSH_LOG, 'python:kallithea.lib.hooks.log_push_action', True),
+            ('hooks', Ui.HOOK_PUSH_LOCK, 'python:kallithea.lib.hooks.push_lock_handling', True),
+            ('hooks', Ui.HOOK_PULL_LOG, 'python:kallithea.lib.hooks.log_pull_action', True),
+            ('hooks', Ui.HOOK_PULL_LOCK, 'python:kallithea.lib.hooks.pull_lock_handling', True),
+            ('extensions', 'largefiles', '', True),
+            ('largefiles', 'usercache', os.path.join(path, '.cache', 'largefiles'), True),
+            ('extensions', 'hgsubversion', '', False),
+            ('extensions', 'hggit', '', False),
         ]
-        for section, key, value in ui_config:
+        for section, key, value, active in ui_config:
             ui_conf = Ui()
             setattr(ui_conf, 'ui_section', section)
             setattr(ui_conf, 'ui_key', key)
             setattr(ui_conf, 'ui_value', value)
+            setattr(ui_conf, 'ui_active', active)
             self.sa.add(ui_conf)
 
         settings = [
@@ -501,8 +376,8 @@
             ('ga_code', '', 'unicode'),
             ('show_public_icon', True, 'bool'),
             ('show_private_icon', True, 'bool'),
-            ('stylify_metatags', False, 'bool'),
-            ('dashboard_items', 100, 'int'),
+            ('stylify_metalabels', False, 'bool'),
+            ('dashboard_items', 100, 'int'), # TODO: call it page_size
             ('admin_grid_items', 25, 'int'),
             ('show_version', True, 'bool'),
             ('use_gravatar', True, 'bool'),
@@ -522,9 +397,9 @@
     def create_user(self, username, password, email='', admin=False):
         log.info('creating user %s', username)
         UserModel().create_or_update(username, password, email,
-                                     firstname='Kallithea', lastname='Admin',
+                                     firstname=u'Kallithea', lastname=u'Admin',
                                      active=True, admin=admin,
-                                     extern_type=EXTERN_TYPE_INTERNAL)
+                                     extern_type=User.DEFAULT_AUTH_TYPE)
 
     def create_default_user(self):
         log.info('creating default user')
@@ -532,14 +407,13 @@
         user = UserModel().create_or_update(username=User.DEFAULT_USER,
                                             password=str(uuid.uuid1())[:20],
                                             email='anonymous@kallithea-scm.org',
-                                            firstname='Anonymous',
-                                            lastname='User')
+                                            firstname=u'Anonymous',
+                                            lastname=u'User')
         # based on configuration options activate/deactivate this user which
         # controls anonymous access
         if self.cli_args.get('public_access') is False:
             log.info('Public access disabled')
             user.active = False
-            Session().add(user)
             Session().commit()
 
     def create_permissions(self):
@@ -549,7 +423,7 @@
         # module.(access|create|change|delete)_[name]
         # module.(none|read|write|admin)
         log.info('creating permissions')
-        PermissionModel(self.sa).create_permissions()
+        PermissionModel().create_permissions()
 
     def populate_default_permissions(self):
         """
@@ -557,14 +431,4 @@
         permissions that are missing, and not alter already defined ones
         """
         log.info('creating default user permissions')
-        PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
-
-    @staticmethod
-    def check_waitress():
-        """
-        Function executed at the end of setup
-        """
-        if not __py_version__ >= (2, 6):
-            notify('Python2.5 detected, please switch '
-                   'egg:waitress#main -> egg:Paste#http '
-                   'in your .ini file')
+        PermissionModel().create_default_permissions(user=User.DEFAULT_USER)
--- a/kallithea/lib/dbmigrate/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +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.lib.dbmigrate
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Database migration modules
-
-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 11, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-
-from kallithea.lib.utils import BasePasterCommand, Command, add_cache
-from kallithea.lib.db_manage import DbManage
-
-log = logging.getLogger(__name__)
-
-
-class UpgradeDb(BasePasterCommand):
-    """Command used for paster to upgrade our database to newer version
-    """
-
-    max_args = 1
-    min_args = 1
-
-    usage = "CONFIG_FILE"
-    summary = "Upgrades current db to newer version"
-    group_name = "Kallithea"
-
-    parser = Command.standard_parser(verbose=True)
-
-    def command(self):
-        from pylons import config
-        add_cache(config)
-
-        db_uri = config['sqlalchemy.db1.url']
-        dbmanage = DbManage(log_sql=True, dbconf=db_uri,
-                            root=config['here'], tests=False,
-                            cli_args=self.options.__dict__)
-        dbmanage.upgrade()
-
-    def update_parser(self):
-        self.parser.add_option('--sql',
-                      action='store_true',
-                      dest='just_sql',
-                      help="Prints upgrade sql for further investigation",
-                      default=False)
-
-        self.parser.add_option('--force-yes',
-                           action='store_true',
-                           dest='force_ask',
-                           default=None,
-                           help='Force yes to every question')
-        self.parser.add_option('--force-no',
-                           action='store_false',
-                           dest='force_ask',
-                           default=None,
-                           help='Force no to every question')
--- a/kallithea/lib/dbmigrate/migrate.cfg	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-[db_settings]
-# Used to identify which repository this database is versioned under.
-# You can use the name of your project.
-repository_id=kallithea_db_migrations
-
-# The name of the database table used to track the schema version.
-# This name shouldn't already be used by your project.
-# If this is changed once a database is under version control, you'll need to
-# change the table name in each database too.
-version_table=db_migrate_version
-
-# When committing a change script, Migrate will attempt to generate the
-# sql for all supported databases; normally, if one of them fails - probably
-# because you don't have that database installed - it is ignored and the
-# commit continues, perhaps ending successfully.
-# Databases in this list MUST compile successfully during a commit, or the
-# entire commit will fail. List the databases your application will actually
-# be using to ensure your updates to that database work properly.
-# This must be a list; example: ['postgres','sqlite']
-required_dbs=['sqlite']
--- a/kallithea/lib/dbmigrate/migrate/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-"""
-   SQLAlchemy migrate provides two APIs :mod:`migrate.versioning` for
-   database schema version and repository management and
-   :mod:`migrate.changeset` that allows to define database schema changes
-   using Python.
-"""
-
-from kallithea.lib.dbmigrate.migrate.versioning import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-__version__ = '0.7.3.dev'
--- a/kallithea/lib/dbmigrate/migrate/changeset/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-"""
-   This module extends SQLAlchemy and provides additional DDL [#]_
-   support.
-
-   .. [#] SQL Data Definition Language
-"""
-import re
-import warnings
-
-import sqlalchemy
-from sqlalchemy import __version__ as _sa_version
-
-warnings.simplefilter('always', DeprecationWarning)
-
-_sa_version = tuple(int(re.match("\d+", x).group(0))
-                    for x in _sa_version.split("."))
-SQLA_06 = _sa_version >= (0, 6)
-SQLA_07 = _sa_version >= (0, 7)
-
-del re
-del _sa_version
-
-from kallithea.lib.dbmigrate.migrate.changeset.schema import *
-from kallithea.lib.dbmigrate.migrate.changeset.constraint import *
-
-sqlalchemy.schema.Table.__bases__ += (ChangesetTable,)
-sqlalchemy.schema.Column.__bases__ += (ChangesetColumn,)
-sqlalchemy.schema.Index.__bases__ += (ChangesetIndex,)
-
-sqlalchemy.schema.DefaultClause.__bases__ += (ChangesetDefaultClause,)
--- a/kallithea/lib/dbmigrate/migrate/changeset/ansisql.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,293 +0,0 @@
-"""
-   Extensions to SQLAlchemy for altering existing tables.
-
-   At the moment, this isn't so much based off of ANSI as much as
-   things that just happen to work with multiple databases.
-"""
-import StringIO
-
-import sqlalchemy as sa
-from sqlalchemy.schema import SchemaVisitor
-from sqlalchemy.engine.default import DefaultDialect
-from sqlalchemy.sql import ClauseElement
-from sqlalchemy.schema import (ForeignKeyConstraint,
-                               PrimaryKeyConstraint,
-                               CheckConstraint,
-                               UniqueConstraint,
-                               Index)
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import constraint
-
-from sqlalchemy.schema import AddConstraint, DropConstraint
-from sqlalchemy.sql.compiler import DDLCompiler
-SchemaGenerator = SchemaDropper = DDLCompiler
-
-
-class AlterTableVisitor(SchemaVisitor):
-    """Common operations for ``ALTER TABLE`` statements."""
-
-    # engine.Compiler looks for .statement
-    # when it spawns off a new compiler
-    statement = ClauseElement()
-
-    def append(self, s):
-        """Append content to the SchemaIterator's query buffer."""
-
-        self.buffer.write(s)
-
-    def execute(self):
-        """Execute the contents of the SchemaIterator's buffer."""
-        try:
-            return self.connection.execute(self.buffer.getvalue())
-        finally:
-            self.buffer.truncate(0)
-
-    def __init__(self, dialect, connection, **kw):
-        self.connection = connection
-        self.buffer = StringIO.StringIO()
-        self.preparer = dialect.identifier_preparer
-        self.dialect = dialect
-
-    def traverse_single(self, elem):
-        ret = super(AlterTableVisitor, self).traverse_single(elem)
-        if ret:
-            # adapt to 0.6 which uses a string-returning
-            # object
-            self.append(" %s" % ret)
-
-    def _to_table(self, param):
-        """Returns the table object for the given param object."""
-        if isinstance(param, (sa.Column, sa.Index, sa.schema.Constraint)):
-            ret = param.table
-        else:
-            ret = param
-        return ret
-
-    def start_alter_table(self, param):
-        """Returns the start of an ``ALTER TABLE`` SQL-Statement.
-
-        Use the param object to determine the table name and use it
-        for building the SQL statement.
-
-        :param param: object to determine the table from
-        :type param: :class:`sqlalchemy.Column`, :class:`sqlalchemy.Index`,
-          :class:`sqlalchemy.schema.Constraint`, :class:`sqlalchemy.Table`,
-          or string (table name)
-        """
-        table = self._to_table(param)
-        self.append('\nALTER TABLE %s ' % self.preparer.format_table(table))
-        return table
-
-
-class ANSIColumnGenerator(AlterTableVisitor, SchemaGenerator):
-    """Extends ansisql generator for column creation (alter table add col)"""
-
-    def visit_column(self, column):
-        """Create a column (table already exists).
-
-        :param column: column object
-        :type column: :class:`sqlalchemy.Column` instance
-        """
-        if column.default is not None:
-            self.traverse_single(column.default)
-
-        table = self.start_alter_table(column)
-        self.append("ADD ")
-
-        self.append(self.get_column_specification(column))
-
-        for cons in column.constraints:
-            self.traverse_single(cons)
-        self.execute()
-
-        # ALTER TABLE STATEMENTS
-
-        # add indexes and unique constraints
-        if column.index_name:
-            Index(column.index_name,column).create()
-        elif column.unique_name:
-            constraint.UniqueConstraint(column,
-                                        name=column.unique_name).create()
-
-        # SA bounds FK constraints to table, add manually
-        for fk in column.foreign_keys:
-            self.add_foreignkey(fk.constraint)
-
-        # add primary key constraint if needed
-        if column.primary_key_name:
-            cons = constraint.PrimaryKeyConstraint(column,
-                                                   name=column.primary_key_name)
-            cons.create()
-
-    def add_foreignkey(self, fk):
-        self.connection.execute(AddConstraint(fk))
-
-class ANSIColumnDropper(AlterTableVisitor, SchemaDropper):
-    """Extends ANSI SQL dropper for column dropping (``ALTER TABLE
-    DROP COLUMN``).
-    """
-
-    def visit_column(self, column):
-        """Drop a column from its table.
-
-        :param column: the column object
-        :type column: :class:`sqlalchemy.Column`
-        """
-        table = self.start_alter_table(column)
-        self.append('DROP COLUMN %s' % self.preparer.format_column(column))
-        self.execute()
-
-
-class ANSISchemaChanger(AlterTableVisitor, SchemaGenerator):
-    """Manages changes to existing schema elements.
-
-    Note that columns are schema elements; ``ALTER TABLE ADD COLUMN``
-    is in SchemaGenerator.
-
-    All items may be renamed. Columns can also have many of their properties -
-    type, for example - changed.
-
-    Each function is passed a tuple, containing (object, name); where
-    object is a type of object you'd expect for that function
-    (ie. table for visit_table) and name is the object's new
-    name. NONE means the name is unchanged.
-    """
-
-    def visit_table(self, table):
-        """Rename a table. Other ops aren't supported."""
-        self.start_alter_table(table)
-        self.append("RENAME TO %s" % self.preparer.quote(table.new_name,
-                                                         table.quote))
-        self.execute()
-
-    def visit_index(self, index):
-        """Rename an index"""
-        if hasattr(self, '_validate_identifier'):
-            # SA <= 0.6.3
-            self.append("ALTER INDEX %s RENAME TO %s" % (
-                    self.preparer.quote(
-                        self._validate_identifier(
-                            index.name, True), index.quote),
-                    self.preparer.quote(
-                        self._validate_identifier(
-                            index.new_name, True), index.quote)))
-        else:
-            # SA >= 0.6.5
-            self.append("ALTER INDEX %s RENAME TO %s" % (
-                    self.preparer.quote(
-                        self._index_identifier(
-                            index.name), index.quote),
-                    self.preparer.quote(
-                        self._index_identifier(
-                            index.new_name), index.quote)))
-        self.execute()
-
-    def visit_column(self, delta):
-        """Rename/change a column."""
-        # ALTER COLUMN is implemented as several ALTER statements
-        keys = delta.keys()
-        if 'type' in keys:
-            self._run_subvisit(delta, self._visit_column_type)
-        if 'nullable' in keys:
-            self._run_subvisit(delta, self._visit_column_nullable)
-        if 'server_default' in keys:
-            # Skip 'default': only handle server-side defaults, others
-            # are managed by the app, not the db.
-            self._run_subvisit(delta, self._visit_column_default)
-        if 'name' in keys:
-            self._run_subvisit(delta, self._visit_column_name, start_alter=False)
-
-    def _run_subvisit(self, delta, func, start_alter=True):
-        """Runs visit method based on what needs to be changed on column"""
-        table = self._to_table(delta.table)
-        col_name = delta.current_name
-        if start_alter:
-            self.start_alter_column(table, col_name)
-        ret = func(table, delta.result_column, delta)
-        self.execute()
-
-    def start_alter_column(self, table, col_name):
-        """Starts ALTER COLUMN"""
-        self.start_alter_table(table)
-        self.append("ALTER COLUMN %s " % self.preparer.quote(col_name, table.quote))
-
-    def _visit_column_nullable(self, table, column, delta):
-        nullable = delta['nullable']
-        if nullable:
-            self.append("DROP NOT NULL")
-        else:
-            self.append("SET NOT NULL")
-
-    def _visit_column_default(self, table, column, delta):
-        default_text = self.get_column_default_string(column)
-        if default_text is not None:
-            self.append("SET DEFAULT %s" % default_text)
-        else:
-            self.append("DROP DEFAULT")
-
-    def _visit_column_type(self, table, column, delta):
-        type_ = delta['type']
-        type_text = str(type_.compile(dialect=self.dialect))
-        self.append("TYPE %s" % type_text)
-
-    def _visit_column_name(self, table, column, delta):
-        self.start_alter_table(table)
-        col_name = self.preparer.quote(delta.current_name, table.quote)
-        new_name = self.preparer.format_column(delta.result_column)
-        self.append('RENAME COLUMN %s TO %s' % (col_name, new_name))
-
-
-class ANSIConstraintCommon(AlterTableVisitor):
-    """
-    Migrate's constraints require a separate creation function from
-    SA's: Migrate's constraints are created independently of a table;
-    SA's are created at the same time as the table.
-    """
-
-    def get_constraint_name(self, cons):
-        """Gets a name for the given constraint.
-
-        If the name is already set it will be used otherwise the
-        constraint's :meth:`autoname <migrate.changeset.constraint.ConstraintChangeset.autoname>`
-        method is used.
-
-        :param cons: constraint object
-        """
-        if cons.name is not None:
-            ret = cons.name
-        else:
-            ret = cons.name = cons.autoname()
-        return self.preparer.quote(ret, cons.quote)
-
-    def visit_migrate_primary_key_constraint(self, *p, **k):
-        self._visit_constraint(*p, **k)
-
-    def visit_migrate_foreign_key_constraint(self, *p, **k):
-        self._visit_constraint(*p, **k)
-
-    def visit_migrate_check_constraint(self, *p, **k):
-        self._visit_constraint(*p, **k)
-
-    def visit_migrate_unique_constraint(self, *p, **k):
-        self._visit_constraint(*p, **k)
-
-class ANSIConstraintGenerator(ANSIConstraintCommon, SchemaGenerator):
-    def _visit_constraint(self, constraint):
-        constraint.name = self.get_constraint_name(constraint)
-        self.append(self.process(AddConstraint(constraint)))
-        self.execute()
-
-class ANSIConstraintDropper(ANSIConstraintCommon, SchemaDropper):
-    def _visit_constraint(self, constraint):
-        constraint.name = self.get_constraint_name(constraint)
-        self.append(self.process(DropConstraint(constraint, cascade=constraint.cascade)))
-        self.execute()
-
-
-class ANSIDialect(DefaultDialect):
-    columngenerator = ANSIColumnGenerator
-    columndropper = ANSIColumnDropper
-    schemachanger = ANSISchemaChanger
-    constraintgenerator = ANSIConstraintGenerator
-    constraintdropper = ANSIConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/constraint.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-"""
-   This module defines standalone schema constraint classes.
-"""
-from sqlalchemy import schema
-
-from kallithea.lib.dbmigrate.migrate.exceptions import *
-
-
-class ConstraintChangeset(object):
-    """Base class for Constraint classes."""
-
-    def _normalize_columns(self, cols, table_name=False):
-        """Given: column objects or names; return col names and
-        (maybe) a table"""
-        colnames = []
-        table = None
-        for col in cols:
-            if isinstance(col, schema.Column):
-                if col.table is not None and table is None:
-                    table = col.table
-                if table_name:
-                    col = '.'.join((col.table.name, col.name))
-                else:
-                    col = col.name
-            colnames.append(col)
-        return colnames, table
-
-    def __do_imports(self, visitor_name, *a, **kw):
-        engine = kw.pop('engine', self.table.bind)
-        from kallithea.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor,
-                                                                                 run_single_visitor)
-        visitorcallable = get_engine_visitor(engine, visitor_name)
-        run_single_visitor(engine, visitorcallable, self, *a, **kw)
-
-    def create(self, *a, **kw):
-        """Create the constraint in the database.
-
-        :param engine: the database engine to use. If this is \
-        :keyword:`None` the instance's engine will be used
-        :type engine: :class:`sqlalchemy.engine.base.Engine`
-        :param connection: reuse connection instead of creating new one.
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-        """
-        # TODO: set the parent here instead of in __init__
-        self.__do_imports('constraintgenerator', *a, **kw)
-
-    def drop(self, *a, **kw):
-        """Drop the constraint from the database.
-
-        :param engine: the database engine to use. If this is
-          :keyword:`None` the instance's engine will be used
-        :param cascade: Issue CASCADE drop if database supports it
-        :type engine: :class:`sqlalchemy.engine.base.Engine`
-        :type cascade: bool
-        :param connection: reuse connection instead of creating new one.
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-        :returns: Instance with cleared columns
-        """
-        self.cascade = kw.pop('cascade', False)
-        self.__do_imports('constraintdropper', *a, **kw)
-        # the spirit of Constraint objects is that they
-        # are immutable (just like in a DB.  they're only ADDed
-        # or DROPped).
-        #self.columns.clear()
-        return self
-
-
-class PrimaryKeyConstraint(ConstraintChangeset, schema.PrimaryKeyConstraint):
-    """Construct PrimaryKeyConstraint
-
-    Migrate's additional parameters:
-
-    :param cols: Columns in constraint.
-    :param table: If columns are passed as strings, this kw is required
-    :type table: Table instance
-    :type cols: strings or Column instances
-    """
-
-    __migrate_visit_name__ = 'migrate_primary_key_constraint'
-
-    def __init__(self, *cols, **kwargs):
-        colnames, table = self._normalize_columns(cols)
-        table = kwargs.pop('table', table)
-        super(PrimaryKeyConstraint, self).__init__(*colnames, **kwargs)
-        if table is not None:
-            self._set_parent(table)
-
-    def autoname(self):
-        """Mimic the database's automatic constraint names"""
-        return "%s_pkey" % self.table.name
-
-
-class ForeignKeyConstraint(ConstraintChangeset, schema.ForeignKeyConstraint):
-    """Construct ForeignKeyConstraint
-
-    Migrate's additional parameters:
-
-    :param columns: Columns in constraint
-    :param refcolumns: Columns that this FK reffers to in another table.
-    :param table: If columns are passed as strings, this kw is required
-    :type table: Table instance
-    :type columns: list of strings or Column instances
-    :type refcolumns: list of strings or Column instances
-    """
-
-    __migrate_visit_name__ = 'migrate_foreign_key_constraint'
-
-    def __init__(self, columns, refcolumns, *args, **kwargs):
-        colnames, table = self._normalize_columns(columns)
-        table = kwargs.pop('table', table)
-        refcolnames, reftable = self._normalize_columns(refcolumns,
-                                                        table_name=True)
-        super(ForeignKeyConstraint, self).__init__(
-            colnames, refcolnames, *args,**kwargs
-        )
-        if table is not None:
-            self._set_parent(table)
-
-    @property
-    def referenced(self):
-        return [e.column for e in self.elements]
-
-    @property
-    def reftable(self):
-        return self.referenced[0].table
-
-    def autoname(self):
-        """Mimic the database's automatic constraint names"""
-        if hasattr(self.columns, 'keys'):
-            # SA <= 0.5
-            firstcol = self.columns[self.columns.keys()[0]]
-            ret = "%(table)s_%(firstcolumn)s_fkey" % dict(
-                table=firstcol.table.name,
-                firstcolumn=firstcol.name,)
-        else:
-            # SA >= 0.6
-            ret = "%(table)s_%(firstcolumn)s_fkey" % dict(
-                table=self.table.name,
-                firstcolumn=self.columns[0],)
-        return ret
-
-
-class CheckConstraint(ConstraintChangeset, schema.CheckConstraint):
-    """Construct CheckConstraint
-
-    Migrate's additional parameters:
-
-    :param sqltext: Plain SQL text to check condition
-    :param columns: If not name is applied, you must supply this kw\
-    to autoname constraint
-    :param table: If columns are passed as strings, this kw is required
-    :type table: Table instance
-    :type columns: list of Columns instances
-    :type sqltext: string
-    """
-
-    __migrate_visit_name__ = 'migrate_check_constraint'
-
-    def __init__(self, sqltext, *args, **kwargs):
-        cols = kwargs.pop('columns', [])
-        if not cols and not kwargs.get('name', False):
-            raise InvalidConstraintError('You must either set "name"'
-                'parameter or "columns" to autogenarate it.')
-        colnames, table = self._normalize_columns(cols)
-        table = kwargs.pop('table', table)
-        schema.CheckConstraint.__init__(self, sqltext, *args, **kwargs)
-        if table is not None:
-            self._set_parent(table)
-        self.colnames = colnames
-
-    def autoname(self):
-        return "%(table)s_%(cols)s_check" % \
-            dict(table=self.table.name, cols="_".join(self.colnames))
-
-
-class UniqueConstraint(ConstraintChangeset, schema.UniqueConstraint):
-    """Construct UniqueConstraint
-
-    Migrate's additional parameters:
-
-    :param cols: Columns in constraint.
-    :param table: If columns are passed as strings, this kw is required
-    :type table: Table instance
-    :type cols: strings or Column instances
-
-    .. versionadded:: 0.6.0
-    """
-
-    __migrate_visit_name__ = 'migrate_unique_constraint'
-
-    def __init__(self, *cols, **kwargs):
-        self.colnames, table = self._normalize_columns(cols)
-        table = kwargs.pop('table', table)
-        super(UniqueConstraint, self).__init__(*self.colnames, **kwargs)
-        if table is not None:
-            self._set_parent(table)
-
-    def autoname(self):
-        """Mimic the database's automatic constraint names"""
-        return "%s_%s_key" % (self.table.name, '_'.join(self.colnames))
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-"""
-   This module contains database dialect specific changeset
-   implementations.
-"""
-__all__ = [
-    'postgres',
-    'sqlite',
-    'mysql',
-    'oracle',
-]
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/firebird.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-"""
-   Firebird database specific implementations of changeset classes.
-"""
-from sqlalchemy.databases import firebird as sa_base
-from sqlalchemy.schema import PrimaryKeyConstraint
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql
-
-
-FBSchemaGenerator = sa_base.FBDDLCompiler
-
-class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
-    """Firebird column generator implementation."""
-
-
-class FBColumnDropper(ansisql.ANSIColumnDropper):
-    """Firebird column dropper implementation."""
-
-    def visit_column(self, column):
-        """Firebird supports 'DROP col' instead of 'DROP COLUMN col' syntax
-
-        Drop primary key and unique constraints if dropped column is referencing it."""
-        if column.primary_key:
-            if column.table.primary_key.columns.contains_column(column):
-                column.table.primary_key.drop()
-                # TODO: recreate primary key if it references more than this column
-
-        for index in column.table.indexes:
-            # "column in index.columns" causes problems as all
-            # column objects compare equal and return a SQL expression
-            if column.name in [col.name for col in index.columns]:
-                index.drop()
-                # TODO: recreate index if it references more than this column
-
-        for cons in column.table.constraints:
-            if isinstance(cons,PrimaryKeyConstraint):
-                # will be deleted only when the column its on
-                # is deleted!
-                continue
-
-            should_drop = column.name in cons.columns
-            if should_drop:
-                self.start_alter_table(column)
-                self.append("DROP CONSTRAINT ")
-                self.append(self.preparer.format_constraint(cons))
-                self.execute()
-            # TODO: recreate unique constraint if it refenrences more than this column
-
-        self.start_alter_table(column)
-        self.append('DROP %s' % self.preparer.format_column(column))
-        self.execute()
-
-
-class FBSchemaChanger(ansisql.ANSISchemaChanger):
-    """Firebird schema changer implementation."""
-
-    def visit_table(self, table):
-        """Rename table not supported"""
-        raise exceptions.NotSupportedError(
-            "Firebird does not support renaming tables.")
-
-    def _visit_column_name(self, table, column, delta):
-        self.start_alter_table(table)
-        col_name = self.preparer.quote(delta.current_name, table.quote)
-        new_name = self.preparer.format_column(delta.result_column)
-        self.append('ALTER COLUMN %s TO %s' % (col_name, new_name))
-
-    def _visit_column_nullable(self, table, column, delta):
-        """Changing NULL is not supported"""
-        # TODO: http://www.firebirdfaq.org/faq103/
-        raise exceptions.NotSupportedError(
-            "Firebird does not support altering NULL behavior.")
-
-
-class FBConstraintGenerator(ansisql.ANSIConstraintGenerator):
-    """Firebird constraint generator implementation."""
-
-
-class FBConstraintDropper(ansisql.ANSIConstraintDropper):
-    """Firebird constraint dropper implementation."""
-
-    def cascade_constraint(self, constraint):
-        """Cascading constraints is not supported"""
-        raise exceptions.NotSupportedError(
-            "Firebird does not support cascading constraints")
-
-
-class FBDialect(ansisql.ANSIDialect):
-    columngenerator = FBColumnGenerator
-    columndropper = FBColumnDropper
-    schemachanger = FBSchemaChanger
-    constraintgenerator = FBConstraintGenerator
-    constraintdropper = FBConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/mysql.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-"""
-   MySQL database specific implementations of changeset classes.
-"""
-
-from sqlalchemy.databases import mysql as sa_base
-from sqlalchemy import types as sqltypes
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql
-
-
-MySQLSchemaGenerator = sa_base.MySQLDDLCompiler
-
-class MySQLColumnGenerator(MySQLSchemaGenerator, ansisql.ANSIColumnGenerator):
-    pass
-
-
-class MySQLColumnDropper(ansisql.ANSIColumnDropper):
-    pass
-
-
-class MySQLSchemaChanger(MySQLSchemaGenerator, ansisql.ANSISchemaChanger):
-
-    def visit_column(self, delta):
-        table = delta.table
-        colspec = self.get_column_specification(delta.result_column)
-        if delta.result_column.autoincrement:
-            primary_keys = [c for c in table.primary_key.columns
-                       if (c.autoincrement and
-                            isinstance(c.type, sqltypes.Integer) and
-                            not c.foreign_keys)]
-
-            if primary_keys:
-                first = primary_keys.pop(0)
-                if first.name == delta.current_name:
-                    colspec += " AUTO_INCREMENT"
-        old_col_name = self.preparer.quote(delta.current_name, table.quote)
-
-        self.start_alter_table(table)
-
-        self.append("CHANGE COLUMN %s " % old_col_name)
-        self.append(colspec)
-        self.execute()
-
-    def visit_index(self, param):
-        # If MySQL can do this, I can't find how
-        raise exceptions.NotSupportedError("MySQL cannot rename indexes")
-
-
-class MySQLConstraintGenerator(ansisql.ANSIConstraintGenerator):
-    pass
-
-
-class MySQLConstraintDropper(MySQLSchemaGenerator, ansisql.ANSIConstraintDropper):
-    def visit_migrate_check_constraint(self, *p, **k):
-        raise exceptions.NotSupportedError("MySQL does not support CHECK"
-            " constraints, use triggers instead.")
-
-
-class MySQLDialect(ansisql.ANSIDialect):
-    columngenerator = MySQLColumnGenerator
-    columndropper = MySQLColumnDropper
-    schemachanger = MySQLSchemaChanger
-    constraintgenerator = MySQLConstraintGenerator
-    constraintdropper = MySQLConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/oracle.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-"""
-   Oracle database specific implementations of changeset classes.
-"""
-import sqlalchemy as sa
-from sqlalchemy.databases import oracle as sa_base
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
-
-
-if not SQLA_06:
-    OracleSchemaGenerator = sa_base.OracleSchemaGenerator
-else:
-    OracleSchemaGenerator = sa_base.OracleDDLCompiler
-
-
-class OracleColumnGenerator(OracleSchemaGenerator, ansisql.ANSIColumnGenerator):
-    pass
-
-
-class OracleColumnDropper(ansisql.ANSIColumnDropper):
-    pass
-
-
-class OracleSchemaChanger(OracleSchemaGenerator, ansisql.ANSISchemaChanger):
-
-    def get_column_specification(self, column, **kwargs):
-        # Ignore the NOT NULL generated
-        override_nullable = kwargs.pop('override_nullable', None)
-        if override_nullable:
-            orig = column.nullable
-            column.nullable = True
-        ret = super(OracleSchemaChanger, self).get_column_specification(
-            column, **kwargs)
-        if override_nullable:
-            column.nullable = orig
-        return ret
-
-    def visit_column(self, delta):
-        keys = delta.keys()
-
-        if 'name' in keys:
-            self._run_subvisit(delta,
-                               self._visit_column_name,
-                               start_alter=False)
-
-        if len(set(('type', 'nullable', 'server_default')).intersection(keys)):
-            self._run_subvisit(delta,
-                               self._visit_column_change,
-                               start_alter=False)
-
-    def _visit_column_change(self, table, column, delta):
-        # Oracle cannot drop a default once created, but it can set it
-        # to null.  We'll do that if default=None
-        # http://forums.oracle.com/forums/message.jspa?messageID=1273234#1273234
-        dropdefault_hack = (column.server_default is None \
-                                and 'server_default' in delta.keys())
-        # Oracle apparently doesn't like it when we say "not null" if
-        # the column's already not null. Fudge it, so we don't need a
-        # new function
-        notnull_hack = ((not column.nullable) \
-                            and ('nullable' not in delta.keys()))
-        # We need to specify NULL if we're removing a NOT NULL
-        # constraint
-        null_hack = (column.nullable and ('nullable' in delta.keys()))
-
-        if dropdefault_hack:
-            column.server_default = sa.PassiveDefault(sa.sql.null())
-        if notnull_hack:
-            column.nullable = True
-        colspec = self.get_column_specification(column,
-            override_nullable=null_hack)
-        if null_hack:
-            colspec += ' NULL'
-        if notnull_hack:
-            column.nullable = False
-        if dropdefault_hack:
-            column.server_default = None
-
-        self.start_alter_table(table)
-        self.append("MODIFY (")
-        self.append(colspec)
-        self.append(")")
-
-
-class OracleConstraintCommon(object):
-
-    def get_constraint_name(self, cons):
-        # Oracle constraints can't guess their name like other DBs
-        if not cons.name:
-            raise exceptions.NotSupportedError(
-                "Oracle constraint names must be explicitly stated")
-        return cons.name
-
-
-class OracleConstraintGenerator(OracleConstraintCommon,
-                                ansisql.ANSIConstraintGenerator):
-    pass
-
-
-class OracleConstraintDropper(OracleConstraintCommon,
-                              ansisql.ANSIConstraintDropper):
-    pass
-
-
-class OracleDialect(ansisql.ANSIDialect):
-    columngenerator = OracleColumnGenerator
-    columndropper = OracleColumnDropper
-    schemachanger = OracleSchemaChanger
-    constraintgenerator = OracleConstraintGenerator
-    constraintdropper = OracleConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/postgres.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-"""
-   `PostgreSQL`_ database specific implementations of changeset classes.
-
-   .. _`PostgreSQL`: http://www.postgresql.org/
-"""
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql
-
-
-from sqlalchemy.databases import postgresql as sa_base
-PGSchemaGenerator = sa_base.PGDDLCompiler
-
-
-class PGColumnGenerator(PGSchemaGenerator, ansisql.ANSIColumnGenerator):
-    """PostgreSQL column generator implementation."""
-    pass
-
-
-class PGColumnDropper(ansisql.ANSIColumnDropper):
-    """PostgreSQL column dropper implementation."""
-    pass
-
-
-class PGSchemaChanger(ansisql.ANSISchemaChanger):
-    """PostgreSQL schema changer implementation."""
-    pass
-
-
-class PGConstraintGenerator(ansisql.ANSIConstraintGenerator):
-    """PostgreSQL constraint generator implementation."""
-    pass
-
-
-class PGConstraintDropper(ansisql.ANSIConstraintDropper):
-    """PostgreSQL constraint dropper implementation."""
-    pass
-
-
-class PGDialect(ansisql.ANSIDialect):
-    columngenerator = PGColumnGenerator
-    columndropper = PGColumnDropper
-    schemachanger = PGSchemaChanger
-    constraintgenerator = PGConstraintGenerator
-    constraintdropper = PGConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/sqlite.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-"""
-   `SQLite`_ database specific implementations of changeset classes.
-
-   .. _`SQLite`: http://www.sqlite.org/
-"""
-from UserDict import DictMixin
-from copy import copy
-
-from sqlalchemy.databases import sqlite as sa_base
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
-
-SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler
-
-
-class SQLiteCommon(object):
-
-    def _not_supported(self, op):
-        raise exceptions.NotSupportedError("SQLite does not support "
-            "%s; see http://www.sqlite.org/lang_altertable.html" % op)
-
-
-class SQLiteHelper(SQLiteCommon):
-
-    def recreate_table(self,table,column=None,delta=None):
-        table_name = self.preparer.format_table(table)
-
-        # we remove all indexes so as not to have
-        # problems during copy and re-create
-        for index in table.indexes:
-            index.drop()
-
-        self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)
-        self.execute()
-
-        insertion_string = self._modify_table(table, column, delta)
-
-        table.create(bind=self.connection)
-        self.append(insertion_string % {'table_name': table_name})
-        self.execute()
-        self.append('DROP TABLE migration_tmp')
-        self.execute()
-
-    def visit_column(self, delta):
-        if isinstance(delta, DictMixin):
-            column = delta.result_column
-            table = self._to_table(delta.table)
-        else:
-            column = delta
-            table = self._to_table(column.table)
-
-        self.recreate_table(table,column,delta)
-
-class SQLiteColumnGenerator(SQLiteSchemaGenerator,
-                            ansisql.ANSIColumnGenerator,
-                            # at the end so we get the normal
-                            # visit_column by default
-                            SQLiteHelper,
-                            SQLiteCommon
-                            ):
-    """SQLite ColumnGenerator"""
-
-    def _modify_table(self, table, column, delta):
-        columns = ' ,'.join(map(
-                self.preparer.format_column,
-                [c for c in table.columns if c.name!=column.name]))
-        return ('INSERT INTO %%(table_name)s (%(cols)s) '
-                'SELECT %(cols)s from migration_tmp')%{'cols':columns}
-
-    def visit_column(self,column):
-        if column.foreign_keys:
-            SQLiteHelper.visit_column(self,column)
-        else:
-            super(SQLiteColumnGenerator,self).visit_column(column)
-
-class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
-    """SQLite ColumnDropper"""
-
-    def _modify_table(self, table, column, delta):
-
-        columns = ' ,'.join(map(self.preparer.format_column, table.columns))
-        return 'INSERT INTO %(table_name)s SELECT ' + columns + \
-            ' from migration_tmp'
-
-    def visit_column(self,column):
-        # For SQLite, we *have* to remove the column here so the table
-        # is re-created properly.
-        column.remove_from_table(column.table,unset_table=False)
-        super(SQLiteColumnDropper,self).visit_column(column)
-
-
-class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
-    """SQLite SchemaChanger"""
-
-    def _modify_table(self, table, column, delta):
-        return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
-
-    def visit_index(self, index):
-        """Does not support ALTER INDEX"""
-        self._not_supported('ALTER INDEX')
-
-
-class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon):
-
-    def visit_migrate_primary_key_constraint(self, constraint):
-        tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )"
-        cols = ', '.join(map(self.preparer.format_column, constraint.columns))
-        tname = self.preparer.format_table(constraint.table)
-        name = self.get_constraint_name(constraint)
-        msg = tmpl % (name, tname, cols)
-        self.append(msg)
-        self.execute()
-
-    def _modify_table(self, table, column, delta):
-        return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
-
-    def visit_migrate_foreign_key_constraint(self, *p, **k):
-        self.recreate_table(p[0].table)
-
-    def visit_migrate_unique_constraint(self, *p, **k):
-        self.recreate_table(p[0].table)
-
-
-class SQLiteConstraintDropper(ansisql.ANSIColumnDropper,
-                              SQLiteCommon,
-                              ansisql.ANSIConstraintCommon):
-
-    def visit_migrate_primary_key_constraint(self, constraint):
-        tmpl = "DROP INDEX %s "
-        name = self.get_constraint_name(constraint)
-        msg = tmpl % (name)
-        self.append(msg)
-        self.execute()
-
-    def visit_migrate_foreign_key_constraint(self, *p, **k):
-        self._not_supported('ALTER TABLE DROP CONSTRAINT')
-
-    def visit_migrate_check_constraint(self, *p, **k):
-        self._not_supported('ALTER TABLE DROP CONSTRAINT')
-
-    def visit_migrate_unique_constraint(self, *p, **k):
-        self._not_supported('ALTER TABLE DROP CONSTRAINT')
-
-
-# TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index
-
-class SQLiteDialect(ansisql.ANSIDialect):
-    columngenerator = SQLiteColumnGenerator
-    columndropper = SQLiteColumnDropper
-    schemachanger = SQLiteSchemaChanger
-    constraintgenerator = SQLiteConstraintGenerator
-    constraintdropper = SQLiteConstraintDropper
--- a/kallithea/lib/dbmigrate/migrate/changeset/databases/visitor.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-"""
-   Module for visitor class mapping.
-"""
-import sqlalchemy as sa
-
-from kallithea.lib.dbmigrate.migrate.changeset import ansisql
-from kallithea.lib.dbmigrate.migrate.changeset.databases import (sqlite,
-                                         postgres,
-                                         mysql,
-                                         oracle,
-                                         firebird)
-
-
-# Map SA dialects to the corresponding Migrate extensions
-DIALECTS = {
-    "default": ansisql.ANSIDialect,
-    "sqlite": sqlite.SQLiteDialect,
-    "postgres": postgres.PGDialect,
-    "postgresql": postgres.PGDialect,
-    "mysql": mysql.MySQLDialect,
-    "oracle": oracle.OracleDialect,
-    "firebird": firebird.FBDialect,
-}
-
-
-def get_engine_visitor(engine, name):
-    """
-    Get the visitor implementation for the given database engine.
-
-    :param engine: SQLAlchemy Engine
-    :param name: Name of the visitor
-    :type name: string
-    :type engine: Engine
-    :returns: visitor
-    """
-    # TODO: link to supported visitors
-    return get_dialect_visitor(engine.dialect, name)
-
-
-def get_dialect_visitor(sa_dialect, name):
-    """
-    Get the visitor implementation for the given dialect.
-
-    Finds the visitor implementation based on the dialect class and
-    returns and instance initialized with the given name.
-
-    Binds dialect specific preparer to visitor.
-    """
-
-    # map sa dialect to migrate dialect and return visitor
-    sa_dialect_name = getattr(sa_dialect, 'name', 'default')
-    migrate_dialect_cls = DIALECTS[sa_dialect_name]
-    visitor = getattr(migrate_dialect_cls, name)
-
-    # bind preparer
-    visitor.preparer = sa_dialect.preparer(sa_dialect)
-
-    return visitor
-
-def run_single_visitor(engine, visitorcallable, element,
-    connection=None, **kwargs):
-    """Taken from :meth:`sqlalchemy.engine.base.Engine._run_single_visitor`
-    with support for migrate visitors.
-    """
-    if connection is None:
-        conn = engine.contextual_connect(close_with_result=False)
-    else:
-        conn = connection
-    visitor = visitorcallable(engine.dialect, conn)
-    try:
-        if hasattr(element, '__migrate_visit_name__'):
-            fn = getattr(visitor, 'visit_' + element.__migrate_visit_name__)
-        else:
-            fn = getattr(visitor, 'visit_' + element.__visit_name__)
-        fn(element, **kwargs)
-    finally:
-        if connection is None:
-            conn.close()
--- a/kallithea/lib/dbmigrate/migrate/changeset/schema.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,655 +0,0 @@
-"""
-   Schema module providing common schema operations.
-"""
-import warnings
-
-from UserDict import DictMixin
-
-import sqlalchemy
-
-from sqlalchemy.schema import ForeignKeyConstraint
-from sqlalchemy.schema import UniqueConstraint
-
-from kallithea.lib.dbmigrate.migrate.exceptions import *
-from kallithea.lib.dbmigrate.migrate.changeset import SQLA_06, SQLA_07
-from kallithea.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor,
-                                                                         run_single_visitor)
-
-
-__all__ = [
-    'create_column',
-    'drop_column',
-    'alter_column',
-    'rename_table',
-    'rename_index',
-    'ChangesetTable',
-    'ChangesetColumn',
-    'ChangesetIndex',
-    'ChangesetDefaultClause',
-    'ColumnDelta',
-]
-
-def create_column(column, table=None, *p, **kw):
-    """Create a column, given the table.
-
-    API to :meth:`ChangesetColumn.create`.
-    """
-    if table is not None:
-        return table.create_column(column, *p, **kw)
-    return column.create(*p, **kw)
-
-
-def drop_column(column, table=None, *p, **kw):
-    """Drop a column, given the table.
-
-    API to :meth:`ChangesetColumn.drop`.
-    """
-    if table is not None:
-        return table.drop_column(column, *p, **kw)
-    return column.drop(*p, **kw)
-
-
-def rename_table(table, name, engine=None, **kw):
-    """Rename a table.
-
-    If Table instance is given, engine is not used.
-
-    API to :meth:`ChangesetTable.rename`.
-
-    :param table: Table to be renamed.
-    :param name: New name for Table.
-    :param engine: Engine instance.
-    :type table: string or Table instance
-    :type name: string
-    :type engine: obj
-    """
-    table = _to_table(table, engine)
-    table.rename(name, **kw)
-
-
-def rename_index(index, name, table=None, engine=None, **kw):
-    """Rename an index.
-
-    If Index instance is given,
-    table and engine are not used.
-
-    API to :meth:`ChangesetIndex.rename`.
-
-    :param index: Index to be renamed.
-    :param name: New name for index.
-    :param table: Table to which Index is reffered.
-    :param engine: Engine instance.
-    :type index: string or Index instance
-    :type name: string
-    :type table: string or Table instance
-    :type engine: obj
-    """
-    index = _to_index(index, table, engine)
-    index.rename(name, **kw)
-
-
-def alter_column(*p, **k):
-    """Alter a column.
-
-    This is a helper function that creates a :class:`ColumnDelta` and
-    runs it.
-
-    :argument column:
-      The name of the column to be altered or a
-      :class:`ChangesetColumn` column representing it.
-
-    :param table:
-      A :class:`~sqlalchemy.schema.Table` or table name to
-      for the table where the column will be changed.
-
-    :param engine:
-      The :class:`~sqlalchemy.engine.base.Engine` to use for table
-      reflection and schema alterations.
-
-    :returns: A :class:`ColumnDelta` instance representing the change.
-
-
-    """
-
-    if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
-        k['table'] = p[0].table
-    if 'engine' not in k:
-        k['engine'] = k['table'].bind
-
-    # deprecation
-    if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
-        warnings.warn(
-            "Passing a Column object to alter_column is deprecated."
-            " Just pass in keyword parameters instead.",
-            MigrateDeprecationWarning
-            )
-    engine = k['engine']
-
-    # enough tests seem to break when metadata is always altered
-    # that this crutch has to be left in until they can be sorted
-    # out
-    k['alter_metadata']=True
-
-    delta = ColumnDelta(*p, **k)
-
-    visitorcallable = get_engine_visitor(engine, 'schemachanger')
-    engine._run_visitor(visitorcallable, delta)
-
-    return delta
-
-
-def _to_table(table, engine=None):
-    """Return if instance of Table, else construct new with metadata"""
-    if isinstance(table, sqlalchemy.Table):
-        return table
-
-    # Given: table name, maybe an engine
-    meta = sqlalchemy.MetaData()
-    if engine is not None:
-        meta.bind = engine
-    return sqlalchemy.Table(table, meta)
-
-
-def _to_index(index, table=None, engine=None):
-    """Return if instance of Index, else construct new with metadata"""
-    if isinstance(index, sqlalchemy.Index):
-        return index
-
-    # Given: index name; table name required
-    table = _to_table(table, engine)
-    ret = sqlalchemy.Index(index)
-    ret.table = table
-    return ret
-
-
-class ColumnDelta(DictMixin, sqlalchemy.schema.SchemaItem):
-    """Extracts the differences between two columns/column-parameters
-
-        May receive parameters arranged in several different ways:
-
-        * **current_column, new_column, \*p, \*\*kw**
-            Additional parameters can be specified to override column
-            differences.
-
-        * **current_column, \*p, \*\*kw**
-            Additional parameters alter current_column. Table name is extracted
-            from current_column object.
-            Name is changed to current_column.name from current_name,
-            if current_name is specified.
-
-        * **current_col_name, \*p, \*\*kw**
-            Table kw must specified.
-
-        :param table: Table at which current Column should be bound to.\
-        If table name is given, reflection will be used.
-        :type table: string or Table instance
-
-        :param metadata: A :class:`MetaData` instance to store
-                         reflected table names
-
-        :param engine: When reflecting tables, either engine or metadata must \
-        be specified to acquire engine object.
-        :type engine: :class:`Engine` instance
-        :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
-        `result_column` through :func:`dict` alike object.
-
-        * :class:`ColumnDelta`.result_column is altered column with new attributes
-
-        * :class:`ColumnDelta`.current_name is current name of column in db
-
-
-    """
-
-    # Column attributes that can be altered
-    diff_keys = ('name', 'type', 'primary_key', 'nullable',
-        'server_onupdate', 'server_default', 'autoincrement')
-    diffs = dict()
-    __visit_name__ = 'column'
-
-    def __init__(self, *p, **kw):
-        # 'alter_metadata' is not a public api. It exists purely
-        # as a crutch until the tests that fail when 'alter_metadata'
-        # behaviour always happens can be sorted out
-        self.alter_metadata = kw.pop("alter_metadata", False)
-
-        self.meta = kw.pop("metadata", None)
-        self.engine = kw.pop("engine", None)
-
-        # Things are initialized differently depending on how many column
-        # parameters are given. Figure out how many and call the appropriate
-        # method.
-        if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
-            # At least one column specified
-            if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
-                # Two columns specified
-                diffs = self.compare_2_columns(*p, **kw)
-            else:
-                # Exactly one column specified
-                diffs = self.compare_1_column(*p, **kw)
-        else:
-            # Zero columns specified
-            if not len(p) or not isinstance(p[0], basestring):
-                raise ValueError("First argument must be column name")
-            diffs = self.compare_parameters(*p, **kw)
-
-        self.apply_diffs(diffs)
-
-    def __repr__(self):
-        return '<ColumnDelta altermetadata=%r, %s>' % (
-            self.alter_metadata,
-            super(ColumnDelta, self).__repr__()
-            )
-
-    def __getitem__(self, key):
-        if key not in self.keys():
-            raise KeyError("No such diff key, available: %s" % self.diffs )
-        return getattr(self.result_column, key)
-
-    def __setitem__(self, key, value):
-        if key not in self.keys():
-            raise KeyError("No such diff key, available: %s" % self.diffs )
-        setattr(self.result_column, key, value)
-
-    def __delitem__(self, key):
-        raise NotImplementedError
-
-    def keys(self):
-        return self.diffs.keys()
-
-    def compare_parameters(self, current_name, *p, **k):
-        """Compares Column objects with reflection"""
-        self.table = k.pop('table')
-        self.result_column = self._table.c.get(current_name)
-        if len(p):
-            k = self._extract_parameters(p, k, self.result_column)
-        return k
-
-    def compare_1_column(self, col, *p, **k):
-        """Compares one Column object"""
-        self.table = k.pop('table', None)
-        if self.table is None:
-            self.table = col.table
-        self.result_column = col
-        if len(p):
-            k = self._extract_parameters(p, k, self.result_column)
-        return k
-
-    def compare_2_columns(self, old_col, new_col, *p, **k):
-        """Compares two Column objects"""
-        self.process_column(new_col)
-        self.table = k.pop('table', None)
-        # we cannot use bool() on table in SA06
-        if self.table is None:
-            self.table = old_col.table
-        if self.table is None:
-            new_col.table
-        self.result_column = old_col
-
-        # set differences
-        # leave out some stuff for later comp
-        for key in (set(self.diff_keys) - set(('type',))):
-            val = getattr(new_col, key, None)
-            if getattr(self.result_column, key, None) != val:
-                k.setdefault(key, val)
-
-        # inspect types
-        if not self.are_column_types_eq(self.result_column.type, new_col.type):
-            k.setdefault('type', new_col.type)
-
-        if len(p):
-            k = self._extract_parameters(p, k, self.result_column)
-        return k
-
-    def apply_diffs(self, diffs):
-        """Populate dict and column object with new values"""
-        self.diffs = diffs
-        for key in self.diff_keys:
-            if key in diffs:
-                setattr(self.result_column, key, diffs[key])
-
-        self.process_column(self.result_column)
-
-        # create an instance of class type if not yet
-        if 'type' in diffs and callable(self.result_column.type):
-            self.result_column.type = self.result_column.type()
-
-        # add column to the table
-        if self.table is not None and self.alter_metadata:
-            self.result_column.add_to_table(self.table)
-
-    def are_column_types_eq(self, old_type, new_type):
-        """Compares two types to be equal"""
-        ret = old_type.__class__ == new_type.__class__
-
-        # String length is a special case
-        if ret and isinstance(new_type, sqlalchemy.types.String):
-            ret = (getattr(old_type, 'length', None) == \
-                       getattr(new_type, 'length', None))
-        return ret
-
-    def _extract_parameters(self, p, k, column):
-        """Extracts data from p and modifies diffs"""
-        p = list(p)
-        while len(p):
-            if isinstance(p[0], basestring):
-                k.setdefault('name', p.pop(0))
-            elif isinstance(p[0], sqlalchemy.types.AbstractType):
-                k.setdefault('type', p.pop(0))
-            elif callable(p[0]):
-                p[0] = p[0]()
-            else:
-                break
-
-        if len(p):
-            new_col = column.copy_fixed()
-            new_col._init_items(*p)
-            k = self.compare_2_columns(column, new_col, **k)
-        return k
-
-    def process_column(self, column):
-        """Processes default values for column"""
-        # XXX: this is a snippet from SA processing of positional parameters
-        toinit = list()
-
-        if column.server_default is not None:
-            if isinstance(column.server_default, sqlalchemy.FetchedValue):
-                toinit.append(column.server_default)
-            else:
-                toinit.append(sqlalchemy.DefaultClause(column.server_default))
-        if column.server_onupdate is not None:
-            if isinstance(column.server_onupdate, FetchedValue):
-                toinit.append(column.server_default)
-            else:
-                toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
-                                            for_update=True))
-        if toinit:
-            column._init_items(*toinit)
-
-    def _get_table(self):
-        return getattr(self, '_table', None)
-
-    def _set_table(self, table):
-        if isinstance(table, basestring):
-            if self.alter_metadata:
-                if not self.meta:
-                    raise ValueError("metadata must be specified for table"
-                        " reflection when using alter_metadata")
-                meta = self.meta
-                if self.engine:
-                    meta.bind = self.engine
-            else:
-                if not self.engine and not self.meta:
-                    raise ValueError("engine or metadata must be specified"
-                        " to reflect tables")
-                if not self.engine:
-                    self.engine = self.meta.bind
-                meta = sqlalchemy.MetaData(bind=self.engine)
-            self._table = sqlalchemy.Table(table, meta, autoload=True)
-        elif isinstance(table, sqlalchemy.Table):
-            self._table = table
-            if not self.alter_metadata:
-                self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
-    def _get_result_column(self):
-        return getattr(self, '_result_column', None)
-
-    def _set_result_column(self, column):
-        """Set Column to Table based on alter_metadata evaluation."""
-        self.process_column(column)
-        if not hasattr(self, 'current_name'):
-            self.current_name = column.name
-        if self.alter_metadata:
-            self._result_column = column
-        else:
-            self._result_column = column.copy_fixed()
-
-    table = property(_get_table, _set_table)
-    result_column = property(_get_result_column, _set_result_column)
-
-
-class ChangesetTable(object):
-    """Changeset extensions to SQLAlchemy tables."""
-
-    def create_column(self, column, *p, **kw):
-        """Creates a column.
-
-        The column parameter may be a column definition or the name of
-        a column in this table.
-
-        API to :meth:`ChangesetColumn.create`
-
-        :param column: Column to be created
-        :type column: Column instance or string
-        """
-        if not isinstance(column, sqlalchemy.Column):
-            # It's a column name
-            column = getattr(self.c, str(column))
-        column.create(table=self, *p, **kw)
-
-    def drop_column(self, column, *p, **kw):
-        """Drop a column, given its name or definition.
-
-        API to :meth:`ChangesetColumn.drop`
-
-        :param column: Column to be dropped
-        :type column: Column instance or string
-        """
-        if not isinstance(column, sqlalchemy.Column):
-            # It's a column name
-            try:
-                column = getattr(self.c, str(column))
-            except AttributeError:
-                # That column isn't part of the table. We don't need
-                # its entire definition to drop the column, just its
-                # name, so create a dummy column with the same name.
-                column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
-        column.drop(table=self, *p, **kw)
-
-    def rename(self, name, connection=None, **kwargs):
-        """Rename this table.
-
-        :param name: New name of the table.
-        :type name: string
-        :param connection: reuse connection instead of creating new one.
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-        """
-        engine = self.bind
-        self.new_name = name
-        visitorcallable = get_engine_visitor(engine, 'schemachanger')
-        run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
-
-        # Fix metadata registration
-        self.name = name
-        self.deregister()
-        self._set_parent(self.metadata)
-
-    def _meta_key(self):
-        """Get the meta key for this table."""
-        return sqlalchemy.schema._get_table_key(self.name, self.schema)
-
-    def deregister(self):
-        """Remove this table from its metadata"""
-        if SQLA_07:
-            self.metadata._remove_table(self.name, self.schema)
-        else:
-            key = self._meta_key()
-            meta = self.metadata
-            if key in meta.tables:
-                del meta.tables[key]
-
-
-class ChangesetColumn(object):
-    """Changeset extensions to SQLAlchemy columns."""
-
-    def alter(self, *p, **k):
-        """Makes a call to :func:`alter_column` for the column this
-        method is called on.
-        """
-        if 'table' not in k:
-            k['table'] = self.table
-        if 'engine' not in k:
-            k['engine'] = k['table'].bind
-        return alter_column(self, *p, **k)
-
-    def create(self, table=None, index_name=None, unique_name=None,
-               primary_key_name=None, populate_default=True, connection=None, **kwargs):
-        """Create this column in the database.
-
-        Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
-        for most databases.
-
-        :param table: Table instance to create on.
-        :param index_name: Creates :class:`ChangesetIndex` on this column.
-        :param unique_name: Creates :class:\
-`~migrate.changeset.constraint.UniqueConstraint` on this column.
-        :param primary_key_name: Creates :class:\
-`~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
-        :param populate_default: If True, created column will be \
-populated with defaults
-        :param connection: reuse connection instead of creating new one.
-        :type table: Table instance
-        :type index_name: string
-        :type unique_name: string
-        :type primary_key_name: string
-        :type populate_default: bool
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-
-        :returns: self
-        """
-        self.populate_default = populate_default
-        self.index_name = index_name
-        self.unique_name = unique_name
-        self.primary_key_name = primary_key_name
-        for cons in ('index_name', 'unique_name', 'primary_key_name'):
-            self._check_sanity_constraints(cons)
-
-        self.add_to_table(table)
-        engine = self.table.bind
-        visitorcallable = get_engine_visitor(engine, 'columngenerator')
-        engine._run_visitor(visitorcallable, self, connection, **kwargs)
-
-        # TODO: reuse existing connection
-        if self.populate_default and self.default is not None:
-            stmt = table.update().values({self: engine._execute_default(self.default)})
-            engine.execute(stmt)
-
-        return self
-
-    def drop(self, table=None, connection=None, **kwargs):
-        """Drop this column from the database, leaving its table intact.
-
-        ``ALTER TABLE DROP COLUMN``, for most databases.
-
-        :param connection: reuse connection instead of creating new one.
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-        """
-        if table is not None:
-            self.table = table
-        engine = self.table.bind
-        visitorcallable = get_engine_visitor(engine, 'columndropper')
-        engine._run_visitor(visitorcallable, self, connection, **kwargs)
-        self.remove_from_table(self.table, unset_table=False)
-        self.table = None
-        return self
-
-    def add_to_table(self, table):
-        if table is not None  and self.table is None:
-            if SQLA_07:
-                table.append_column(self)
-            else:
-                self._set_parent(table)
-
-    def _col_name_in_constraint(self,cons,name):
-        return False
-
-    def remove_from_table(self, table, unset_table=True):
-        # TODO: remove primary keys, constraints, etc
-        if unset_table:
-            self.table = None
-
-        to_drop = set()
-        for index in table.indexes:
-            columns = []
-            for col in index.columns:
-                if col.name!=self.name:
-                    columns.append(col)
-            if columns:
-                index.columns=columns
-            else:
-                to_drop.add(index)
-        table.indexes = table.indexes - to_drop
-
-        to_drop = set()
-        for cons in table.constraints:
-            # TODO: deal with other types of constraint
-            if isinstance(cons,(ForeignKeyConstraint,
-                                UniqueConstraint)):
-                for col_name in cons.columns:
-                    if not isinstance(col_name,basestring):
-                        col_name = col_name.name
-                    if self.name==col_name:
-                        to_drop.add(cons)
-        table.constraints = table.constraints - to_drop
-
-        if table.c.contains_column(self):
-            if SQLA_07:
-                table._columns.remove(self)
-            else:
-                table.c.remove(self)
-
-    # TODO: this is fixed in 0.6
-    def copy_fixed(self, **kw):
-        """Create a copy of this ``Column``, with all attributes."""
-        return sqlalchemy.Column(self.name, self.type, self.default,
-            key=self.key,
-            primary_key=self.primary_key,
-            nullable=self.nullable,
-            quote=self.quote,
-            index=self.index,
-            unique=self.unique,
-            onupdate=self.onupdate,
-            autoincrement=self.autoincrement,
-            server_default=self.server_default,
-            server_onupdate=self.server_onupdate,
-            *[c.copy(**kw) for c in self.constraints])
-
-    def _check_sanity_constraints(self, name):
-        """Check if constraints names are correct"""
-        obj = getattr(self, name)
-        if (getattr(self, name[:-5]) and not obj):
-            raise InvalidConstraintError("Column.create() accepts index_name,"
-            " primary_key_name and unique_name to generate constraints")
-        if not isinstance(obj, basestring) and obj is not None:
-            raise InvalidConstraintError(
-            "%s argument for column must be constraint name" % name)
-
-
-class ChangesetIndex(object):
-    """Changeset extensions to SQLAlchemy Indexes."""
-
-    __visit_name__ = 'index'
-
-    def rename(self, name, connection=None, **kwargs):
-        """Change the name of an index.
-
-        :param name: New name of the Index.
-        :type name: string
-        :param connection: reuse connection instead of creating new one.
-        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
-        """
-        engine = self.table.bind
-        self.new_name = name
-        visitorcallable = get_engine_visitor(engine, 'schemachanger')
-        engine._run_visitor(visitorcallable, self, connection, **kwargs)
-        self.name = name
-
-
-class ChangesetDefaultClause(object):
-    """Implements comparison between :class:`DefaultClause` instances"""
-
-    def __eq__(self, other):
-        if isinstance(other, self.__class__):
-            if self.arg == other.arg:
-                return True
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
--- a/kallithea/lib/dbmigrate/migrate/exceptions.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-"""
-   Provide exception classes for :mod:`migrate`
-"""
-
-
-class Error(Exception):
-    """Error base class."""
-
-
-class ApiError(Error):
-    """Base class for API errors."""
-
-
-class KnownError(ApiError):
-    """A known error condition."""
-
-
-class UsageError(ApiError):
-    """A known error condition where help should be displayed."""
-
-
-class ControlledSchemaError(Error):
-    """Base class for controlled schema errors."""
-
-
-class InvalidVersionError(ControlledSchemaError):
-    """Invalid version number."""
-
-
-class DatabaseNotControlledError(ControlledSchemaError):
-    """Database should be under version control, but it's not."""
-
-
-class DatabaseAlreadyControlledError(ControlledSchemaError):
-    """Database shouldn't be under version control, but it is"""
-
-
-class WrongRepositoryError(ControlledSchemaError):
-    """This database is under version control by another repository."""
-
-
-class NoSuchTableError(ControlledSchemaError):
-    """The table does not exist."""
-
-
-class PathError(Error):
-    """Base class for path errors."""
-
-
-class PathNotFoundError(PathError):
-    """A path with no file was required; found a file."""
-
-
-class PathFoundError(PathError):
-    """A path with a file was required; found no file."""
-
-
-class RepositoryError(Error):
-    """Base class for repository errors."""
-
-
-class InvalidRepositoryError(RepositoryError):
-    """Invalid repository error."""
-
-
-class ScriptError(Error):
-    """Base class for script errors."""
-
-
-class InvalidScriptError(ScriptError):
-    """Invalid script error."""
-
-
-# migrate.changeset
-
-class NotSupportedError(Error):
-    """Not supported error"""
-
-
-class InvalidConstraintError(Error):
-    """Invalid constraint error"""
-
-class MigrateDeprecationWarning(DeprecationWarning):
-    """Warning for deprecated features in Migrate"""
--- a/kallithea/lib/dbmigrate/migrate/versioning/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-"""
-   This package provides functionality to create and manage
-   repositories of database schema changesets and to apply these
-   changesets to databases.
-"""
--- a/kallithea/lib/dbmigrate/migrate/versioning/api.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,384 +0,0 @@
-"""
-   This module provides an external API to the versioning system.
-
-   .. versionchanged:: 0.6.0
-    :func:`migrate.versioning.api.test` and schema diff functions
-    changed order of positional arguments so all accept `url` and `repository`
-    as first arguments.
-
-   .. versionchanged:: 0.5.4
-    ``--preview_sql`` displays source file when using SQL scripts.
-    If Python script is used, it runs the action with mocked engine and
-    returns captured SQL statements.
-
-   .. versionchanged:: 0.5.4
-    Deprecated ``--echo`` parameter in favour of new
-    :func:`migrate.versioning.util.construct_engine` behavior.
-"""
-
-# Dear migrate developers,
-#
-# please do not comment this module using sphinx syntax because its
-# docstrings are presented as user help and most users cannot
-# interpret sphinx annotated ReStructuredText.
-#
-# Thanks,
-# Jan Dittberner
-
-import sys
-import inspect
-import logging
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning import repository, schema, version, \
-    script as script_ # command name conflict
-from kallithea.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine
-
-
-log = logging.getLogger(__name__)
-command_desc = {
-    'help': 'displays help on a given command',
-    'create': 'create an empty repository at the specified path',
-    'script': 'create an empty change Python script',
-    'script_sql': 'create empty change SQL scripts for given database',
-    'version': 'display the latest version available in a repository',
-    'db_version': 'show the current version of the repository under version control',
-    'source': 'display the Python code for a particular version in this repository',
-    'version_control': 'mark a database as under this repository\'s version control',
-    'upgrade': 'upgrade a database to a later version',
-    'downgrade': 'downgrade a database to an earlier version',
-    'drop_version_control': 'removes version control from a database',
-    'manage': 'creates a Python script that runs Migrate with a set of default values',
-    'test': 'performs the upgrade and downgrade command on the given database',
-    'compare_model_to_db': 'compare MetaData against the current database state',
-    'create_model': 'dump the current database as a Python model to stdout',
-    'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData',
-    'update_db_from_model': 'modify the database to match the structure of the current MetaData',
-}
-__all__ = command_desc.keys()
-
-Repository = repository.Repository
-ControlledSchema = schema.ControlledSchema
-VerNum = version.VerNum
-PythonScript = script_.PythonScript
-SqlScript = script_.SqlScript
-
-
-# deprecated
-def help(cmd=None, **opts):
-    """%prog help COMMAND
-
-    Displays help on a given command.
-    """
-    if cmd is None:
-        raise exceptions.UsageError(None)
-    try:
-        func = globals()[cmd]
-    except:
-        raise exceptions.UsageError(
-            "'%s' isn't a valid command. Try 'help COMMAND'" % cmd)
-    ret = func.__doc__
-    if sys.argv[0]:
-        ret = ret.replace('%prog', sys.argv[0])
-    return ret
-
-@catch_known_errors
-def create(repository, name, **opts):
-    """%prog create REPOSITORY_PATH NAME [--table=TABLE]
-
-    Create an empty repository at the specified path.
-
-    You can specify the version_table to be used; by default, it is
-    'migrate_version'.  This table is created in all version-controlled
-    databases.
-    """
-    repo_path = Repository.create(repository, name, **opts)
-
-
-@catch_known_errors
-def script(description, repository, **opts):
-    """%prog script DESCRIPTION REPOSITORY_PATH
-
-    Create an empty change script using the next unused version number
-    appended with the given description.
-
-    For instance, manage.py script "Add initial tables" creates:
-    repository/versions/001_Add_initial_tables.py
-    """
-    repo = Repository(repository)
-    repo.create_script(description, **opts)
-
-
-@catch_known_errors
-def script_sql(database, description, repository, **opts):
-    """%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH
-
-    Create empty change SQL scripts for given DATABASE, where DATABASE
-    is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.)
-    or generic ('default').
-
-    For instance, manage.py script_sql postgresql description creates:
-    repository/versions/001_description_postgresql_upgrade.sql and
-    repository/versions/001_description_postgresql_downgrade.sql
-    """
-    repo = Repository(repository)
-    repo.create_script_sql(database, description, **opts)
-
-
-def version(repository, **opts):
-    """%prog version REPOSITORY_PATH
-
-    Display the latest version available in a repository.
-    """
-    repo = Repository(repository)
-    return repo.latest
-
-
-@with_engine
-def db_version(url, repository, **opts):
-    """%prog db_version URL REPOSITORY_PATH
-
-    Show the current version of the repository with the given
-    connection string, under version control of the specified
-    repository.
-
-    The url should be any valid SQLAlchemy connection string.
-    """
-    engine = opts.pop('engine')
-    schema = ControlledSchema(engine, repository)
-    return schema.version
-
-
-def source(version, dest=None, repository=None, **opts):
-    """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH
-
-    Display the Python code for a particular version in this
-    repository.  Save it to the file at DESTINATION or, if omitted,
-    send to stdout.
-    """
-    if repository is None:
-        raise exceptions.UsageError("A repository must be specified")
-    repo = Repository(repository)
-    ret = repo.version(version).script().source()
-    if dest is not None:
-        dest = open(dest, 'w')
-        dest.write(ret)
-        dest.close()
-        ret = None
-    return ret
-
-
-def upgrade(url, repository, version=None, **opts):
-    """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql]
-
-    Upgrade a database to a later version.
-
-    This runs the upgrade() function defined in your change scripts.
-
-    By default, the database is updated to the latest available
-    version. You may specify a version instead, if you wish.
-
-    You may preview the Python or SQL code to be executed, rather than
-    actually executing it, using the appropriate 'preview' option.
-    """
-    err = "Cannot upgrade a database of version %s to version %s. "\
-        "Try 'downgrade' instead."
-    return _migrate(url, repository, version, upgrade=True, err=err, **opts)
-
-
-def downgrade(url, repository, version, **opts):
-    """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql]
-
-    Downgrade a database to an earlier version.
-
-    This is the reverse of upgrade; this runs the downgrade() function
-    defined in your change scripts.
-
-    You may preview the Python or SQL code to be executed, rather than
-    actually executing it, using the appropriate 'preview' option.
-    """
-    err = "Cannot downgrade a database of version %s to version %s. "\
-        "Try 'upgrade' instead."
-    return _migrate(url, repository, version, upgrade=False, err=err, **opts)
-
-@with_engine
-def test(url, repository, **opts):
-    """%prog test URL REPOSITORY_PATH [VERSION]
-
-    Performs the upgrade and downgrade option on the given
-    database. This is not a real test and may leave the database in a
-    bad state. You should therefore better run the test on a copy of
-    your database.
-    """
-    engine = opts.pop('engine')
-    repos = Repository(repository)
-
-    # Upgrade
-    log.info("Upgrading...")
-    script = repos.version(None).script(engine.name, 'upgrade')
-    script.run(engine, 1)
-    log.info("done")
-
-    log.info("Downgrading...")
-    script = repos.version(None).script(engine.name, 'downgrade')
-    script.run(engine, -1)
-    log.info("done")
-    log.info("Success")
-
-
-@with_engine
-def version_control(url, repository, version=None, **opts):
-    """%prog version_control URL REPOSITORY_PATH [VERSION]
-
-    Mark a database as under this repository's version control.
-
-    Once a database is under version control, schema changes should
-    only be done via change scripts in this repository.
-
-    This creates the table version_table in the database.
-
-    The url should be any valid SQLAlchemy connection string.
-
-    By default, the database begins at version 0 and is assumed to be
-    empty.  If the database is not empty, you may specify a version at
-    which to begin instead. No attempt is made to verify this
-    version's correctness - the database schema is expected to be
-    identical to what it would be if the database were created from
-    scratch.
-    """
-    engine = opts.pop('engine')
-    ControlledSchema.create(engine, repository, version)
-
-
-@with_engine
-def drop_version_control(url, repository, **opts):
-    """%prog drop_version_control URL REPOSITORY_PATH
-
-    Removes version control from a database.
-    """
-    engine = opts.pop('engine')
-    schema = ControlledSchema(engine, repository)
-    schema.drop()
-
-
-def manage(file, **opts):
-    """%prog manage FILENAME [VARIABLES...]
-
-    Creates a script that runs Migrate with a set of default values.
-
-    For example::
-
-        %prog manage manage.py --repository=/path/to/repository \
---url=sqlite:///project.db
-
-    would create the script manage.py. The following two commands
-    would then have exactly the same results::
-
-        python manage.py version
-        %prog version --repository=/path/to/repository
-    """
-    Repository.create_manage_file(file, **opts)
-
-
-@with_engine
-def compare_model_to_db(url, repository, model, **opts):
-    """%prog compare_model_to_db URL REPOSITORY_PATH MODEL
-
-    Compare the current model (assumed to be a module level variable
-    of type sqlalchemy.MetaData) against the current database.
-
-    NOTE: This is EXPERIMENTAL.
-    """  # TODO: get rid of EXPERIMENTAL label
-    engine = opts.pop('engine')
-    return ControlledSchema.compare_model_to_db(engine, model, repository)
-
-
-@with_engine
-def create_model(url, repository, **opts):
-    """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True]
-
-    Dump the current database as a Python model to stdout.
-
-    NOTE: This is EXPERIMENTAL.
-    """  # TODO: get rid of EXPERIMENTAL label
-    engine = opts.pop('engine')
-    declarative = opts.get('declarative', False)
-    return ControlledSchema.create_model(engine, repository, declarative)
-
-
-@catch_known_errors
-@with_engine
-def make_update_script_for_model(url, repository, oldmodel, model, **opts):
-    """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH
-
-    Create a script changing the old Python model to the new (current)
-    Python model, sending to stdout.
-
-    NOTE: This is EXPERIMENTAL.
-    """  # TODO: get rid of EXPERIMENTAL label
-    engine = opts.pop('engine')
-    return PythonScript.make_update_script_for_model(
-        engine, oldmodel, model, repository, **opts)
-
-
-@with_engine
-def update_db_from_model(url, repository, model, **opts):
-    """%prog update_db_from_model URL REPOSITORY_PATH MODEL
-
-    Modify the database to match the structure of the current Python
-    model. This also sets the db_version number to the latest in the
-    repository.
-
-    NOTE: This is EXPERIMENTAL.
-    """  # TODO: get rid of EXPERIMENTAL label
-    engine = opts.pop('engine')
-    schema = ControlledSchema(engine, repository)
-    schema.update_db_from_model(model)
-
-@with_engine
-def _migrate(url, repository, version, upgrade, err, **opts):
-    engine = opts.pop('engine')
-    url = str(engine.url)
-    schema = ControlledSchema(engine, repository)
-    version = _migrate_version(schema, version, upgrade, err)
-
-    changeset = schema.changeset(version)
-    for ver, change in changeset:
-        nextver = ver + changeset.step
-        log.info('%s -> %s... ', ver, nextver)
-
-        if opts.get('preview_sql'):
-            if isinstance(change, PythonScript):
-                log.info(change.preview_sql(url, changeset.step, **opts))
-            elif isinstance(change, SqlScript):
-                log.info(change.source())
-
-        elif opts.get('preview_py'):
-            if not isinstance(change, PythonScript):
-                raise exceptions.UsageError("Python source can be only displayed"
-                    " for python migration files")
-            source_ver = max(ver, nextver)
-            module = schema.repository.version(source_ver).script().module
-            funcname = upgrade and "upgrade" or "downgrade"
-            func = getattr(module, funcname)
-            log.info(inspect.getsource(func))
-        else:
-            schema.runchange(ver, change, changeset.step)
-            log.info('done')
-
-
-def _migrate_version(schema, version, upgrade, err):
-    if version is None:
-        return version
-    # Version is specified: ensure we're upgrading in the right direction
-    # (current version < target version for upgrading; reverse for down)
-    version = VerNum(version)
-    cur = schema.version
-    if upgrade is not None:
-        if upgrade:
-            direction = cur <= version
-        else:
-            direction = cur >= version
-        if not direction:
-            raise exceptions.KnownError(err % (cur, version))
-    return version
--- a/kallithea/lib/dbmigrate/migrate/versioning/cfgparse.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-"""
-   Configuration parser module.
-"""
-
-from ConfigParser import ConfigParser
-
-from kallithea.lib.dbmigrate.migrate.versioning.config import *
-from kallithea.lib.dbmigrate.migrate.versioning import pathed
-
-
-class Parser(ConfigParser):
-    """A project configuration file."""
-
-    def to_dict(self, sections=None):
-        """It's easier to access config values like dictionaries"""
-        return self._sections
-
-
-class Config(pathed.Pathed, Parser):
-    """Configuration class."""
-
-    def __init__(self, path, *p, **k):
-        """Confirm the config file exists; read it."""
-        self.require_found(path)
-        pathed.Pathed.__init__(self, path)
-        Parser.__init__(self, *p, **k)
-        self.read(path)
--- a/kallithea/lib/dbmigrate/migrate/versioning/config.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-from sqlalchemy.util import OrderedDict
-
-
-__all__ = ['databases', 'operations']
-
-databases = ('sqlite', 'postgres', 'mysql', 'oracle', 'mssql', 'firebird')
-
-# Map operation names to function names
-operations = OrderedDict()
-operations['upgrade'] = 'upgrade'
-operations['downgrade'] = 'downgrade'
--- a/kallithea/lib/dbmigrate/migrate/versioning/genmodel.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
-"""
-Code to generate a Python model from a database or differences
-between a model and database.
-
-Some of this is borrowed heavily from the AutoCode project at:
-http://code.google.com/p/sqlautocode/
-"""
-
-import sys
-import logging
-
-import sqlalchemy
-
-from kallithea.lib.dbmigrate import migrate
-from kallithea.lib.dbmigrate.migrate import changeset
-
-
-log = logging.getLogger(__name__)
-HEADER = """
-## File autogenerated by genmodel.py
-
-from sqlalchemy import *
-meta = MetaData()
-"""
-
-DECLARATIVE_HEADER = """
-## File autogenerated by genmodel.py
-
-from sqlalchemy import *
-from sqlalchemy.ext import declarative
-
-Base = declarative.declarative_base()
-"""
-
-
-class ModelGenerator(object):
-    """Various transformations from an A, B diff.
-
-    In the implementation, A tends to be called the model and B
-    the database (although this is not true of all diffs).
-    The diff is directionless, but transformations apply the diff
-    in a particular direction, described in the method name.
-    """
-
-    def __init__(self, diff, engine, declarative=False):
-        self.diff = diff
-        self.engine = engine
-        self.declarative = declarative
-
-    def column_repr(self, col):
-        kwarg = []
-        if col.key != col.name:
-            kwarg.append('key')
-        if col.primary_key:
-            col.primary_key = True  # otherwise it dumps it as 1
-            kwarg.append('primary_key')
-        if not col.nullable:
-            kwarg.append('nullable')
-        if col.onupdate:
-            kwarg.append('onupdate')
-        if col.default:
-            if col.primary_key:
-                # I found that PostgreSQL automatically creates a
-                # default value for the sequence, but let's not show
-                # that.
-                pass
-            else:
-                kwarg.append('default')
-        args = ['%s=%r' % (k, getattr(col, k)) for k in kwarg]
-
-        # crs: not sure if this is good idea, but it gets rid of extra
-        # u''
-        name = col.name.encode('utf8')
-
-        type_ = col.type
-        for cls in col.type.__class__.__mro__:
-            if cls.__module__ == 'sqlalchemy.types' and \
-                not cls.__name__.isupper():
-                if cls is not type_.__class__:
-                    type_ = cls()
-                break
-
-        type_repr = repr(type_)
-        if type_repr.endswith('()'):
-            type_repr = type_repr[:-2]
-
-        constraints = [repr(cn) for cn in col.constraints]
-
-        data = {
-            'name': name,
-            'commonStuff': ', '.join([type_repr] + constraints + args),
-        }
-
-        if self.declarative:
-            return """%(name)s = Column(%(commonStuff)s)""" % data
-        else:
-            return """Column(%(name)r, %(commonStuff)s)""" % data
-
-    def _getTableDefn(self, table, metaName='meta'):
-        out = []
-        tableName = table.name
-        if self.declarative:
-            out.append("class %(table)s(Base):" % {'table': tableName})
-            out.append("    __tablename__ = '%(table)s'\n" %
-                            {'table': tableName})
-            for col in table.columns:
-                out.append("    %s" % self.column_repr(col))
-            out.append('\n')
-        else:
-            out.append("%(table)s = Table('%(table)s', %(meta)s," %
-                       {'table': tableName, 'meta': metaName})
-            for col in table.columns:
-                out.append("    %s," % self.column_repr(col))
-            out.append(")\n")
-        return out
-
-    def _get_tables(self,missingA=False,missingB=False,modified=False):
-        to_process = []
-        for bool_,names,metadata in (
-            (missingA,self.diff.tables_missing_from_A,self.diff.metadataB),
-            (missingB,self.diff.tables_missing_from_B,self.diff.metadataA),
-            (modified,self.diff.tables_different,self.diff.metadataA),
-                ):
-            if bool_:
-                for name in names:
-                    yield metadata.tables.get(name)
-
-    def genBDefinition(self):
-        """Generates the source code for a definition of B.
-
-        Assumes a diff where A is empty.
-
-        Was: toPython. Assume database (B) is current and model (A) is empty.
-        """
-
-        out = []
-        if self.declarative:
-            out.append(DECLARATIVE_HEADER)
-        else:
-            out.append(HEADER)
-        out.append("")
-        for table in self._get_tables(missingA=True):
-            out.extend(self._getTableDefn(table))
-        return '\n'.join(out)
-
-    def genB2AMigration(self, indent='    '):
-        """Generate a migration from B to A.
-
-        Was: toUpgradeDowngradePython
-        Assume model (A) is most current and database (B) is out-of-date.
-        """
-
-        decls = ['from migrate.changeset import schema',
-                 'pre_meta = MetaData()',
-                 'post_meta = MetaData()',
-                ]
-        upgradeCommands = ['pre_meta.bind = migrate_engine',
-                           'post_meta.bind = migrate_engine']
-        downgradeCommands = list(upgradeCommands)
-
-        for tn in self.diff.tables_missing_from_A:
-            pre_table = self.diff.metadataB.tables[tn]
-            decls.extend(self._getTableDefn(pre_table, metaName='pre_meta'))
-            upgradeCommands.append(
-                "pre_meta.tables[%(table)r].drop()" % {'table': tn})
-            downgradeCommands.append(
-                "pre_meta.tables[%(table)r].create()" % {'table': tn})
-
-        for tn in self.diff.tables_missing_from_B:
-            post_table = self.diff.metadataA.tables[tn]
-            decls.extend(self._getTableDefn(post_table, metaName='post_meta'))
-            upgradeCommands.append(
-                "post_meta.tables[%(table)r].create()" % {'table': tn})
-            downgradeCommands.append(
-                "post_meta.tables[%(table)r].drop()" % {'table': tn})
-
-        for (tn, td) in self.diff.tables_different.iteritems():
-            if td.columns_missing_from_A or td.columns_different:
-                pre_table = self.diff.metadataB.tables[tn]
-                decls.extend(self._getTableDefn(
-                    pre_table, metaName='pre_meta'))
-            if td.columns_missing_from_B or td.columns_different:
-                post_table = self.diff.metadataA.tables[tn]
-                decls.extend(self._getTableDefn(
-                    post_table, metaName='post_meta'))
-
-            for col in td.columns_missing_from_A:
-                upgradeCommands.append(
-                    'pre_meta.tables[%r].columns[%r].drop()' % (tn, col))
-                downgradeCommands.append(
-                    'pre_meta.tables[%r].columns[%r].create()' % (tn, col))
-            for col in td.columns_missing_from_B:
-                upgradeCommands.append(
-                    'post_meta.tables[%r].columns[%r].create()' % (tn, col))
-                downgradeCommands.append(
-                    'post_meta.tables[%r].columns[%r].drop()' % (tn, col))
-            for modelCol, databaseCol, modelDecl, databaseDecl in td.columns_different:
-                upgradeCommands.append(
-                    'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
-                    tn, modelCol.name, databaseCol.name))
-                downgradeCommands.append(
-                    'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
-                    tn, modelCol.name, databaseCol.name))
-
-        return (
-            '\n'.join(decls),
-            '\n'.join('%s%s' % (indent, line) for line in upgradeCommands),
-            '\n'.join('%s%s' % (indent, line) for line in downgradeCommands))
-
-    def _db_can_handle_this_change(self,td):
-        """Check if the database can handle going from B to A."""
-
-        if (td.columns_missing_from_B
-            and not td.columns_missing_from_A
-            and not td.columns_different):
-            # Even sqlite can handle column additions.
-            return True
-        else:
-            return not self.engine.url.drivername.startswith('sqlite')
-
-    def runB2A(self):
-        """Goes from B to A.
-
-        Was: applyModel. Apply model (A) to current database (B).
-        """
-
-        meta = sqlalchemy.MetaData(self.engine)
-
-        for table in self._get_tables(missingA=True):
-            table = table.tometadata(meta)
-            table.drop()
-        for table in self._get_tables(missingB=True):
-            table = table.tometadata(meta)
-            table.create()
-        for modelTable in self._get_tables(modified=True):
-            tableName = modelTable.name
-            modelTable = modelTable.tometadata(meta)
-            dbTable = self.diff.metadataB.tables[tableName]
-
-            td = self.diff.tables_different[tableName]
-
-            if self._db_can_handle_this_change(td):
-
-                for col in td.columns_missing_from_B:
-                    modelTable.columns[col].create()
-                for col in td.columns_missing_from_A:
-                    dbTable.columns[col].drop()
-                # XXX handle column changes here.
-            else:
-                # Sqlite doesn't support drop column, so you have to
-                # do more: create temp table, copy data to it, drop
-                # old table, create new table, copy data back.
-                #
-                # I wonder if this is guaranteed to be unique?
-                tempName = '_temp_%s' % modelTable.name
-
-                def getCopyStatement():
-                    preparer = self.engine.dialect.preparer
-                    commonCols = []
-                    for modelCol in modelTable.columns:
-                        if modelCol.name in dbTable.columns:
-                            commonCols.append(modelCol.name)
-                    commonColsStr = ', '.join(commonCols)
-                    return 'INSERT INTO %s (%s) SELECT %s FROM %s' % \
-                        (tableName, commonColsStr, commonColsStr, tempName)
-
-                # Move the data in one transaction, so that we don't
-                # leave the database in a nasty state.
-                connection = self.engine.connect()
-                trans = connection.begin()
-                try:
-                    connection.execute(
-                        'CREATE TEMPORARY TABLE %s as SELECT * from %s' % \
-                            (tempName, modelTable.name))
-                    # make sure the drop takes place inside our
-                    # transaction with the bind parameter
-                    modelTable.drop(bind=connection)
-                    modelTable.create(bind=connection)
-                    connection.execute(getCopyStatement())
-                    connection.execute('DROP TABLE %s' % tempName)
-                    trans.commit()
-                except:
-                    trans.rollback()
-                    raise
--- a/kallithea/lib/dbmigrate/migrate/versioning/migrate_repository.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-"""
-   Script to migrate repository from sqlalchemy <= 0.4.4 to the new
-   repository schema. This shouldn't use any other migrate modules, so
-   that it can work in any version.
-"""
-
-import os
-import sys
-import logging
-
-log = logging.getLogger(__name__)
-
-
-def usage():
-    """Gives usage information."""
-    print """Usage: %(prog)s repository-to-migrate
-
-    Upgrade your repository to the new flat format.
-
-    NOTE: You should probably make a backup before running this.
-    """ % {'prog': sys.argv[0]}
-
-    sys.exit(1)
-
-
-def delete_file(filepath):
-    """Deletes a file and prints a message."""
-    log.info('Deleting file: %s', filepath)
-    os.remove(filepath)
-
-
-def move_file(src, tgt):
-    """Moves a file and prints a message."""
-    log.info('Moving file %s to %s', src, tgt)
-    if os.path.exists(tgt):
-        raise Exception(
-            'Cannot move file %s because target %s already exists' % \
-                (src, tgt))
-    os.rename(src, tgt)
-
-
-def delete_directory(dirpath):
-    """Delete a directory and print a message."""
-    log.info('Deleting directory: %s', dirpath)
-    os.rmdir(dirpath)
-
-
-def migrate_repository(repos):
-    """Does the actual migration to the new repository format."""
-    log.info('Migrating repository at: %s to new format', repos)
-    versions = '%s/versions' % repos
-    dirs = os.listdir(versions)
-    # Only use int's in list.
-    numdirs = [int(dirname) for dirname in dirs if dirname.isdigit()]
-    numdirs.sort()  # Sort list.
-    for dirname in numdirs:
-        origdir = '%s/%s' % (versions, dirname)
-        log.info('Working on directory: %s', origdir)
-        files = os.listdir(origdir)
-        files.sort()
-        for filename in files:
-            # Delete compiled Python files.
-            if filename.endswith('.pyc') or filename.endswith('.pyo'):
-                delete_file('%s/%s' % (origdir, filename))
-
-            # Delete empty __init__.py files.
-            origfile = '%s/__init__.py' % origdir
-            if os.path.exists(origfile) and len(open(origfile).read()) == 0:
-                delete_file(origfile)
-
-            # Move sql upgrade scripts.
-            if filename.endswith('.sql'):
-                version, dbms, operation = filename.split('.', 3)[0:3]
-                origfile = '%s/%s' % (origdir, filename)
-                # For instance:  2.postgres.upgrade.sql ->
-                #  002_postgres_upgrade.sql
-                tgtfile = '%s/%03d_%s_%s.sql' % (
-                    versions, int(version), dbms, operation)
-                move_file(origfile, tgtfile)
-
-        # Move Python upgrade script.
-        pyfile = '%s.py' % dirname
-        pyfilepath = '%s/%s' % (origdir, pyfile)
-        if os.path.exists(pyfilepath):
-            tgtfile = '%s/%03d.py' % (versions, int(dirname))
-            move_file(pyfilepath, tgtfile)
-
-        # Try to remove directory. Will fail if it's not empty.
-        delete_directory(origdir)
-
-
-def main():
-    """Main function to be called when using this script."""
-    if len(sys.argv) != 2:
-        usage()
-    migrate_repository(sys.argv[1])
-
-
-if __name__ == '__main__':
-    main()
--- a/kallithea/lib/dbmigrate/migrate/versioning/pathed.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-"""
-   A path/directory class.
-"""
-
-import os
-import shutil
-import logging
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning.config import *
-from kallithea.lib.dbmigrate.migrate.versioning.util import KeyedInstance
-
-
-log = logging.getLogger(__name__)
-
-class Pathed(KeyedInstance):
-    """
-    A class associated with a path/directory tree.
-
-    Only one instance of this class may exist for a particular file;
-    __new__ will return an existing instance if possible
-    """
-    parent = None
-
-    @classmethod
-    def _key(cls, path):
-        return str(path)
-
-    def __init__(self, path):
-        self.path = path
-        if self.__class__.parent is not None:
-            self._init_parent(path)
-
-    def _init_parent(self, path):
-        """Try to initialize this object's parent, if it has one"""
-        parent_path = self.__class__._parent_path(path)
-        self.parent = self.__class__.parent(parent_path)
-        log.debug("Getting parent %r:%r", self.__class__.parent, parent_path)
-        self.parent._init_child(path, self)
-
-    def _init_child(self, child, path):
-        """Run when a child of this object is initialized.
-
-        Parameters: the child object; the path to this object (its
-        parent)
-        """
-
-    @classmethod
-    def _parent_path(cls, path):
-        """
-        Fetch the path of this object's parent from this object's path.
-        """
-        # os.path.dirname(), but strip directories like files (like
-        # unix basename)
-        #
-        # Treat directories like files...
-        if path[-1] == '/':
-            path = path[:-1]
-        ret = os.path.dirname(path)
-        return ret
-
-    @classmethod
-    def require_notfound(cls, path):
-        """Ensures a given path does not already exist"""
-        if os.path.exists(path):
-            raise exceptions.PathFoundError(path)
-
-    @classmethod
-    def require_found(cls, path):
-        """Ensures a given path already exists"""
-        if not os.path.exists(path):
-            raise exceptions.PathNotFoundError(path)
-
-    def __str__(self):
-        return self.path
--- a/kallithea/lib/dbmigrate/migrate/versioning/repository.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-"""
-   SQLAlchemy migrate repository management.
-"""
-import os
-import shutil
-import string
-import logging
-
-from pkg_resources import resource_filename
-from tempita import Template as TempitaTemplate
-
-import kallithea
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning import version, pathed, cfgparse
-from kallithea.lib.dbmigrate.migrate.versioning.template import Template
-from kallithea.lib.dbmigrate.migrate.versioning.config import *
-
-
-log = logging.getLogger(__name__)
-
-class Changeset(dict):
-    """A collection of changes to be applied to a database.
-
-    Changesets are bound to a repository and manage a set of
-    scripts from that repository.
-
-    Behaves like a dict, for the most part. Keys are ordered based on step value.
-    """
-
-    def __init__(self, start, *changes, **k):
-        """
-        Give a start version; step must be explicitly stated.
-        """
-        self.step = k.pop('step', 1)
-        self.start = version.VerNum(start)
-        self.end = self.start
-        for change in changes:
-            self.add(change)
-
-    def __iter__(self):
-        return iter(self.items())
-
-    def keys(self):
-        """
-        In a series of upgrades x -> y, keys are version x. Sorted.
-        """
-        ret = super(Changeset, self).keys()
-        # Reverse order if downgrading
-        ret.sort(reverse=(self.step < 1))
-        return ret
-
-    def values(self):
-        return [self[k] for k in self.keys()]
-
-    def items(self):
-        return zip(self.keys(), self.values())
-
-    def add(self, change):
-        """Add new change to changeset"""
-        key = self.end
-        self.end += self.step
-        self[key] = change
-
-    def run(self, *p, **k):
-        """Run the changeset scripts"""
-        for _version, script in self:
-            script.run(*p, **k)
-
-
-class Repository(pathed.Pathed):
-    """A project's change script repository"""
-
-    _config = 'migrate.cfg'
-    _versions = 'versions'
-
-    def __init__(self, path):
-        log.debug('Loading repository %s...', path)
-        self.verify(path)
-        super(Repository, self).__init__(path)
-        self.config = cfgparse.Config(os.path.join(self.path, self._config))
-        self.versions = version.Collection(os.path.join(self.path,
-                                                      self._versions))
-        log.debug('Repository %s loaded successfully', path)
-        log.debug('Config: %r', self.config.to_dict())
-
-    @classmethod
-    def verify(cls, path):
-        """
-        Ensure the target path is a valid repository.
-
-        :raises: :exc:`InvalidRepositoryError <migrate.exceptions.InvalidRepositoryError>`
-        """
-        # Ensure the existence of required files
-        try:
-            cls.require_found(path)
-            cls.require_found(os.path.join(path, cls._config))
-            cls.require_found(os.path.join(path, cls._versions))
-        except exceptions.PathNotFoundError as e:
-            raise exceptions.InvalidRepositoryError(path)
-
-    @classmethod
-    def prepare_config(cls, tmpl_dir, name, options=None):
-        """
-        Prepare a project configuration file for a new project.
-
-        :param tmpl_dir: Path to Repository template
-        :param config_file: Name of the config file in Repository template
-        :param name: Repository name
-        :type tmpl_dir: string
-        :type config_file: string
-        :type name: string
-        :returns: Populated config file
-        """
-        if options is None:
-            options = {}
-        options.setdefault('version_table', 'migrate_version')
-        options.setdefault('repository_id', name)
-        options.setdefault('required_dbs', [])
-        options.setdefault('use_timestamp_numbering', False)
-
-        tmpl = open(os.path.join(tmpl_dir, cls._config)).read()
-        ret = TempitaTemplate(tmpl).substitute(options)
-
-        # cleanup
-        del options['__template_name__']
-
-        return ret
-
-    @classmethod
-    def create(cls, path, name, **opts):
-        """Create a repository at a specified path"""
-        cls.require_notfound(path)
-        theme = opts.pop('templates_theme', None)
-        t_path = opts.pop('templates_path', None)
-
-        # Create repository
-        tmpl_dir = Template(t_path).get_repository(theme=theme)
-        shutil.copytree(tmpl_dir, path)
-
-        # Edit config defaults
-        config_text = cls.prepare_config(tmpl_dir, name, options=opts)
-        fd = open(os.path.join(path, cls._config), 'w')
-        fd.write(config_text)
-        fd.close()
-
-        opts['repository_name'] = name
-
-        # Create a management script
-        manager = os.path.join(path, 'manage.py')
-        Repository.create_manage_file(manager, templates_theme=theme,
-            templates_path=t_path, **opts)
-
-        return cls(path)
-
-    def create_script(self, description, **k):
-        """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`"""
-
-        k['use_timestamp_numbering'] = self.use_timestamp_numbering
-        self.versions.create_new_python_version(description, **k)
-
-    def create_script_sql(self, database, description, **k):
-        """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`"""
-        k['use_timestamp_numbering'] = self.use_timestamp_numbering
-        self.versions.create_new_sql_version(database, description, **k)
-
-    @property
-    def latest(self):
-        """API to :attr:`migrate.versioning.version.Collection.latest`"""
-        return self.versions.latest
-
-    @property
-    def version_table(self):
-        """Returns version_table name specified in config"""
-        return self.config.get('db_settings', 'version_table')
-
-    @property
-    def id(self):
-        """Returns repository id specified in config"""
-        # Adjust the value read from kallithea/lib/dbmigrate/migrate.cfg, normally "kallithea_db_migrations"
-        s = self.config.get('db_settings', 'repository_id')
-        if s == "kallithea_db_migrations":
-            s = kallithea.DB_MIGRATIONS
-        return s
-
-    @property
-    def use_timestamp_numbering(self):
-        """Returns use_timestamp_numbering specified in config"""
-        if self.config.has_option('db_settings', 'use_timestamp_numbering'):
-            return self.config.getboolean('db_settings', 'use_timestamp_numbering')
-        return False
-
-    def version(self, *p, **k):
-        """API to :attr:`migrate.versioning.version.Collection.version`"""
-        return self.versions.version(*p, **k)
-
-    @classmethod
-    def clear(cls):
-        # TODO: deletes repo
-        super(Repository, cls).clear()
-        version.Collection.clear()
-
-    def changeset(self, database, start, end=None):
-        """Create a changeset to migrate this database from ver. start to end/latest.
-
-        :param database: name of database to generate changeset
-        :param start: version to start at
-        :param end: version to end at (latest if None given)
-        :type database: string
-        :type start: int
-        :type end: int
-        :returns: :class:`Changeset instance <migration.versioning.repository.Changeset>`
-        """
-        start = version.VerNum(start)
-
-        if end is None:
-            end = self.latest
-        else:
-            end = version.VerNum(end)
-
-        if start <= end:
-            step = 1
-            range_mod = 1
-            op = 'upgrade'
-        else:
-            step = -1
-            range_mod = 0
-            op = 'downgrade'
-
-        versions = range(start + range_mod, end + range_mod, step)
-        changes = [self.version(v).script(database, op) for v in versions]
-        ret = Changeset(start, step=step, *changes)
-        return ret
-
-    @classmethod
-    def create_manage_file(cls, file_, **opts):
-        """Create a project management script (manage.py)
-
-        :param file_: Destination file to be written
-        :param opts: Options that are passed to :func:`migrate.versioning.shell.main`
-        """
-        mng_file = Template(opts.pop('templates_path', None))\
-            .get_manage(theme=opts.pop('templates_theme', None))
-
-        tmpl = open(mng_file).read()
-        fd = open(file_, 'w')
-        fd.write(TempitaTemplate(tmpl).substitute(opts))
-        fd.close()
--- a/kallithea/lib/dbmigrate/migrate/versioning/schema.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-"""
-   Database schema version management.
-"""
-import sys
-import logging
-
-from sqlalchemy import (Table, Column, MetaData, String, Text, Integer,
-    create_engine)
-from sqlalchemy.sql import and_
-from sqlalchemy import exc as sa_exceptions
-from sqlalchemy.sql import bindparam
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.changeset import SQLA_07
-from kallithea.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
-from kallithea.lib.dbmigrate.migrate.versioning.repository import Repository
-from kallithea.lib.dbmigrate.migrate.versioning.util import load_model
-from kallithea.lib.dbmigrate.migrate.versioning.version import VerNum
-
-
-log = logging.getLogger(__name__)
-
-
-class ControlledSchema(object):
-    """A database under version control"""
-
-    def __init__(self, engine, repository):
-        if isinstance(repository, basestring):
-            repository = Repository(repository)
-        self.engine = engine
-        self.repository = repository
-        self.meta = MetaData(engine)
-        self.load()
-
-    def __eq__(self, other):
-        """Compare two schemas by repositories and versions"""
-        return (self.repository is other.repository \
-            and self.version == other.version)
-
-    def load(self):
-        """Load controlled schema version info from DB"""
-        tname = self.repository.version_table
-        try:
-            if not hasattr(self, 'table') or self.table is None:
-                    self.table = Table(tname, self.meta, autoload=True)
-
-            result = self.engine.execute(self.table.select(
-                self.table.c.repository_id == str(self.repository.id)))
-
-            data = list(result)[0]
-        except:
-            cls, exc, tb = sys.exc_info()
-            raise exceptions.DatabaseNotControlledError, exc.__str__(), tb
-
-        self.version = data['version']
-        return data
-
-    def drop(self):
-        """
-        Remove version control from a database.
-        """
-        if SQLA_07:
-            try:
-                self.table.drop()
-            except sa_exceptions.DatabaseError:
-                raise exceptions.DatabaseNotControlledError(str(self.table))
-        else:
-            try:
-                self.table.drop()
-            except (sa_exceptions.SQLError):
-                raise exceptions.DatabaseNotControlledError(str(self.table))
-
-    def changeset(self, version=None):
-        """API to Changeset creation.
-
-        Uses self.version for start version and engine.name
-        to get database name.
-        """
-        database = self.engine.name
-        start_ver = self.version
-        changeset = self.repository.changeset(database, start_ver, version)
-        return changeset
-
-    def runchange(self, ver, change, step):
-        startver = ver
-        endver = ver + step
-        # Current database version must be correct! Don't run if corrupt!
-        if self.version != startver:
-            raise exceptions.InvalidVersionError("%s is not %s" % \
-                                                     (self.version, startver))
-        # Run the change
-        change.run(self.engine, step)
-
-        # Update/refresh database version
-        self.update_repository_table(startver, endver)
-        self.load()
-
-    def update_repository_table(self, startver, endver):
-        """Update version_table with new information"""
-        update = self.table.update(and_(self.table.c.version == int(startver),
-             self.table.c.repository_id == str(self.repository.id)))
-        self.engine.execute(update, version=int(endver))
-
-    def upgrade(self, version=None):
-        """
-        Upgrade (or downgrade) to a specified version, or latest version.
-        """
-        changeset = self.changeset(version)
-        for ver, change in changeset:
-            self.runchange(ver, change, changeset.step)
-
-    def update_db_from_model(self, model):
-        """
-        Modify the database to match the structure of the current Python model.
-        """
-        model = load_model(model)
-
-        diff = schemadiff.getDiffOfModelAgainstDatabase(
-            model, self.engine, excludeTables=[self.repository.version_table]
-            )
-        genmodel.ModelGenerator(diff,self.engine).runB2A()
-
-        self.update_repository_table(self.version, int(self.repository.latest))
-
-        self.load()
-
-    @classmethod
-    def create(cls, engine, repository, version=None):
-        """
-        Declare a database to be under a repository's version control.
-
-        :raises: :exc:`DatabaseAlreadyControlledError`
-        :returns: :class:`ControlledSchema`
-        """
-        # Confirm that the version # is valid: positive, integer,
-        # exists in repos
-        if isinstance(repository, basestring):
-            repository = Repository(repository)
-        version = cls._validate_version(repository, version)
-        table = cls._create_table_version(engine, repository, version)
-        # TODO: history table
-        # Load repository information and return
-        return cls(engine, repository)
-
-    @classmethod
-    def _validate_version(cls, repository, version):
-        """
-        Ensures this is a valid version number for this repository.
-
-        :raises: :exc:`InvalidVersionError` if invalid
-        :return: valid version number
-        """
-        if version is None:
-            version = 0
-        try:
-            version = VerNum(version) # raises valueerror
-            if version < 0 or version > repository.latest:
-                raise ValueError()
-        except ValueError:
-            raise exceptions.InvalidVersionError(version)
-        return version
-
-    @classmethod
-    def _create_table_version(cls, engine, repository, version):
-        """
-        Creates the versioning table in a database.
-
-        :raises: :exc:`DatabaseAlreadyControlledError`
-        """
-        # Create tables
-        tname = repository.version_table
-        meta = MetaData(engine)
-
-        table = Table(
-            tname, meta,
-            Column('repository_id', String(250), primary_key=True),
-            Column('repository_path', Text),
-            Column('version', Integer), )
-
-        # there can be multiple repositories/schemas in the same db
-        if not table.exists():
-            table.create()
-
-        # test for existing repository_id
-        s = table.select(table.c.repository_id == bindparam("repository_id"))
-        result = engine.execute(s, repository_id=repository.id)
-        if result.fetchone():
-            raise exceptions.DatabaseAlreadyControlledError
-
-        # Insert data
-        engine.execute(table.insert().values(
-                           repository_id=repository.id,
-                           repository_path=repository.path,
-                           version=int(version)))
-        return table
-
-    @classmethod
-    def compare_model_to_db(cls, engine, model, repository):
-        """
-        Compare the current model against the current database.
-        """
-        if isinstance(repository, basestring):
-            repository = Repository(repository)
-        model = load_model(model)
-
-        diff = schemadiff.getDiffOfModelAgainstDatabase(
-            model, engine, excludeTables=[repository.version_table])
-        return diff
-
-    @classmethod
-    def create_model(cls, engine, repository, declarative=False):
-        """
-        Dump the current database as a Python model.
-        """
-        if isinstance(repository, basestring):
-            repository = Repository(repository)
-
-        diff = schemadiff.getDiffOfModelAgainstDatabase(
-            MetaData(), engine, excludeTables=[repository.version_table]
-            )
-        return genmodel.ModelGenerator(diff, engine, declarative).genBDefinition()
--- a/kallithea/lib/dbmigrate/migrate/versioning/schemadiff.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,295 +0,0 @@
-"""
-   Schema differencing support.
-"""
-
-import logging
-import sqlalchemy
-
-from kallithea.lib.dbmigrate.migrate.changeset import SQLA_06
-from sqlalchemy.types import Float
-
-log = logging.getLogger(__name__)
-
-
-def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None):
-    """
-    Return differences of model against database.
-
-    :return: object which will evaluate to :keyword:`True` if there \
-      are differences else :keyword:`False`.
-    """
-    db_metadata = sqlalchemy.MetaData(engine)
-    db_metadata.reflect()
-
-    # sqlite will include a dynamically generated 'sqlite_sequence' table if
-    # there are autoincrement sequences in the database; this should not be
-    # compared.
-    if engine.dialect.name == 'sqlite':
-        if 'sqlite_sequence' in db_metadata.tables:
-            db_metadata.remove(db_metadata.tables['sqlite_sequence'])
-
-    return SchemaDiff(metadata, db_metadata,
-                      labelA='model',
-                      labelB='database',
-                      excludeTables=excludeTables)
-
-
-def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None):
-    """
-    Return differences of model against another model.
-
-    :return: object which will evaluate to :keyword:`True` if there \
-      are differences else :keyword:`False`.
-    """
-    return SchemaDiff(metadataA, metadataB, excludeTables=excludeTables)
-
-
-class ColDiff(object):
-    """
-    Container for differences in one :class:`~sqlalchemy.schema.Column`
-    between two :class:`~sqlalchemy.schema.Table` instances, ``A``
-    and ``B``.
-
-    .. attribute:: col_A
-
-      The :class:`~sqlalchemy.schema.Column` object for A.
-
-    .. attribute:: col_B
-
-      The :class:`~sqlalchemy.schema.Column` object for B.
-
-    .. attribute:: type_A
-
-      The most generic type of the :class:`~sqlalchemy.schema.Column`
-      object in A.
-
-    .. attribute:: type_B
-
-      The most generic type of the :class:`~sqlalchemy.schema.Column`
-      object in A.
-
-    """
-
-    diff = False
-
-    def __init__(self,col_A,col_B):
-        self.col_A = col_A
-        self.col_B = col_B
-
-        self.type_A = col_A.type
-        self.type_B = col_B.type
-
-        self.affinity_A = self.type_A._type_affinity
-        self.affinity_B = self.type_B._type_affinity
-
-        if self.affinity_A is not self.affinity_B:
-            self.diff = True
-            return
-
-        if isinstance(self.type_A,Float) or isinstance(self.type_B,Float):
-            if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)):
-                self.diff=True
-                return
-
-        for attr in ('precision','scale','length'):
-            A = getattr(self.type_A,attr,None)
-            B = getattr(self.type_B,attr,None)
-            if not (A is None or B is None) and A!=B:
-                self.diff=True
-                return
-
-    def __nonzero__(self):
-        return self.diff
-
-class TableDiff(object):
-    """
-    Container for differences in one :class:`~sqlalchemy.schema.Table`
-    between two :class:`~sqlalchemy.schema.MetaData` instances, ``A``
-    and ``B``.
-
-    .. attribute:: columns_missing_from_A
-
-      A sequence of column names that were found in B but weren't in
-      A.
-
-    .. attribute:: columns_missing_from_B
-
-      A sequence of column names that were found in A but weren't in
-      B.
-
-    .. attribute:: columns_different
-
-      A dictionary containing information about columns that were
-      found to be different.
-      It maps column names to a :class:`ColDiff` objects describing the
-      differences found.
-    """
-    __slots__ = (
-        'columns_missing_from_A',
-        'columns_missing_from_B',
-        'columns_different',
-        )
-
-    def __nonzero__(self):
-        return bool(
-            self.columns_missing_from_A or
-            self.columns_missing_from_B or
-            self.columns_different
-            )
-
-class SchemaDiff(object):
-    """
-    Compute the difference between two :class:`~sqlalchemy.schema.MetaData`
-    objects.
-
-    The string representation of a :class:`SchemaDiff` will summarise
-    the changes found between the two
-    :class:`~sqlalchemy.schema.MetaData` objects.
-
-    The length of a :class:`SchemaDiff` will give the number of
-    changes found, enabling it to be used much like a boolean in
-    expressions.
-
-    :param metadataA:
-      First :class:`~sqlalchemy.schema.MetaData` to compare.
-
-    :param metadataB:
-      Second :class:`~sqlalchemy.schema.MetaData` to compare.
-
-    :param labelA:
-      The label to use in messages about the first
-      :class:`~sqlalchemy.schema.MetaData`.
-
-    :param labelB:
-      The label to use in messages about the second
-      :class:`~sqlalchemy.schema.MetaData`.
-
-    :param excludeTables:
-      A sequence of table names to exclude.
-
-    .. attribute:: tables_missing_from_A
-
-      A sequence of table names that were found in B but weren't in
-      A.
-
-    .. attribute:: tables_missing_from_B
-
-      A sequence of table names that were found in A but weren't in
-      B.
-
-    .. attribute:: tables_different
-
-      A dictionary containing information about tables that were found
-      to be different.
-      It maps table names to a :class:`TableDiff` objects describing the
-      differences found.
-    """
-
-    def __init__(self,
-                 metadataA, metadataB,
-                 labelA='metadataA',
-                 labelB='metadataB',
-                 excludeTables=None):
-
-        self.metadataA, self.metadataB = metadataA, metadataB
-        self.labelA, self.labelB = labelA, labelB
-        self.label_width = max(len(labelA),len(labelB))
-        excludeTables = set(excludeTables or [])
-
-        A_table_names = set(metadataA.tables.keys())
-        B_table_names = set(metadataB.tables.keys())
-
-        self.tables_missing_from_A = sorted(
-            B_table_names - A_table_names - excludeTables
-            )
-        self.tables_missing_from_B = sorted(
-            A_table_names - B_table_names - excludeTables
-            )
-
-        self.tables_different = {}
-        for table_name in A_table_names.intersection(B_table_names):
-
-            td = TableDiff()
-
-            A_table = metadataA.tables[table_name]
-            B_table = metadataB.tables[table_name]
-
-            A_column_names = set(A_table.columns.keys())
-            B_column_names = set(B_table.columns.keys())
-
-            td.columns_missing_from_A = sorted(
-                B_column_names - A_column_names
-                )
-
-            td.columns_missing_from_B = sorted(
-                A_column_names - B_column_names
-                )
-
-            td.columns_different = {}
-
-            for col_name in A_column_names.intersection(B_column_names):
-
-                cd = ColDiff(
-                    A_table.columns.get(col_name),
-                    B_table.columns.get(col_name)
-                    )
-
-                if cd:
-                    td.columns_different[col_name]=cd
-
-            # XXX - index and constraint differences should
-            #       be checked for here
-
-            if td:
-                self.tables_different[table_name]=td
-
-    def __str__(self):
-        """ Summarize differences. """
-        out = []
-        column_template ='      %%%is: %%r' % self.label_width
-
-        for names,label in (
-            (self.tables_missing_from_A,self.labelA),
-            (self.tables_missing_from_B,self.labelB),
-            ):
-            if names:
-                out.append(
-                    '  tables missing from %s: %s' % (
-                        label,', '.join(sorted(names))
-                        )
-                    )
-
-        for name,td in sorted(self.tables_different.items()):
-            out.append(
-               '  table with differences: %s' % name
-               )
-            for names,label in (
-                (td.columns_missing_from_A,self.labelA),
-                (td.columns_missing_from_B,self.labelB),
-                ):
-                if names:
-                    out.append(
-                        '    %s missing these columns: %s' % (
-                            label,', '.join(sorted(names))
-                            )
-                        )
-            for name,cd in td.columns_different.items():
-                out.append('    column with differences: %s' % name)
-                out.append(column_template % (self.labelA,cd.col_A))
-                out.append(column_template % (self.labelB,cd.col_B))
-
-        if out:
-            out.insert(0, 'Schema diffs:')
-            return '\n'.join(out)
-        else:
-            return 'No schema diffs'
-
-    def __len__(self):
-        """
-        Used in bool evaluation, return of 0 means no diffs.
-        """
-        return (
-            len(self.tables_missing_from_A) +
-            len(self.tables_missing_from_B) +
-            len(self.tables_different)
-            )
--- a/kallithea/lib/dbmigrate/migrate/versioning/script/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-from kallithea.lib.dbmigrate.migrate.versioning.script.base import BaseScript
-from kallithea.lib.dbmigrate.migrate.versioning.script.py import PythonScript
-from kallithea.lib.dbmigrate.migrate.versioning.script.sql import SqlScript
--- a/kallithea/lib/dbmigrate/migrate/versioning/script/base.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-import logging
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning.config import operations
-from kallithea.lib.dbmigrate.migrate.versioning import pathed
-
-
-log = logging.getLogger(__name__)
-
-class BaseScript(pathed.Pathed):
-    """Base class for other types of scripts.
-    All scripts have the following properties:
-
-    source (script.source())
-      The source code of the script
-    version (script.version())
-      The version number of the script
-    operations (script.operations())
-      The operations defined by the script: upgrade(), downgrade() or both.
-      Returns a tuple of operations.
-      Can also check for an operation with ex. script.operation(Script.ops.up)
-    """ # TODO: sphinxfy this and implement it correctly
-
-    def __init__(self, path):
-        log.debug('Loading script %s...', path)
-        self.verify(path)
-        super(BaseScript, self).__init__(path)
-        log.debug('Script %s loaded successfully', path)
-
-    @classmethod
-    def verify(cls, path):
-        """Ensure this is a valid script
-        This version simply ensures the script file's existence
-
-        :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
-        """
-        try:
-            cls.require_found(path)
-        except:
-            raise exceptions.InvalidScriptError(path)
-
-    def source(self):
-        """:returns: source code of the script.
-        :rtype: string
-        """
-        fd = open(self.path)
-        ret = fd.read()
-        fd.close()
-        return ret
-
-    def run(self, engine):
-        """Core of each BaseScript subclass.
-        This method executes the script.
-        """
-        raise NotImplementedError()
--- a/kallithea/lib/dbmigrate/migrate/versioning/script/py.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-import shutil
-import warnings
-import logging
-import inspect
-from StringIO import StringIO
-
-from kallithea.lib.dbmigrate import migrate
-from kallithea.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
-from kallithea.lib.dbmigrate.migrate.versioning.config import operations
-from kallithea.lib.dbmigrate.migrate.versioning.template import Template
-from kallithea.lib.dbmigrate.migrate.versioning.script import base
-from kallithea.lib.dbmigrate.migrate.versioning.util import import_path, load_model, with_engine
-from kallithea.lib.dbmigrate.migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError
-
-log = logging.getLogger(__name__)
-__all__ = ['PythonScript']
-
-
-class PythonScript(base.BaseScript):
-    """Base for Python scripts"""
-
-    @classmethod
-    def create(cls, path, **opts):
-        """Create an empty migration script at specified path
-
-        :returns: :class:`PythonScript instance <migrate.versioning.script.py.PythonScript>`"""
-        cls.require_notfound(path)
-
-        src = Template(opts.pop('templates_path', None)).get_script(theme=opts.pop('templates_theme', None))
-        shutil.copy(src, path)
-
-        return cls(path)
-
-    @classmethod
-    def make_update_script_for_model(cls, engine, oldmodel,
-                                     model, repository, **opts):
-        """Create a migration script based on difference between two SA models.
-
-        :param repository: path to migrate repository
-        :param oldmodel: dotted.module.name:SAClass or SAClass object
-        :param model: dotted.module.name:SAClass or SAClass object
-        :param engine: SQLAlchemy engine
-        :type repository: string or :class:`Repository instance <migrate.versioning.repository.Repository>`
-        :type oldmodel: string or Class
-        :type model: string or Class
-        :type engine: Engine instance
-        :returns: Upgrade / Downgrade script
-        :rtype: string
-        """
-
-        if isinstance(repository, basestring):
-            # oh dear, an import cycle!
-            from kallithea.lib.dbmigrate.migrate.versioning.repository import Repository
-            repository = Repository(repository)
-
-        oldmodel = load_model(oldmodel)
-        model = load_model(model)
-
-        # Compute differences.
-        diff = schemadiff.getDiffOfModelAgainstModel(
-            model,
-            oldmodel,
-            excludeTables=[repository.version_table])
-        # TODO: diff can be False (there is no difference?)
-        decls, upgradeCommands, downgradeCommands = \
-            genmodel.ModelGenerator(diff,engine).genB2AMigration()
-
-        # Store differences into file.
-        src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
-        f = open(src)
-        contents = f.read()
-        f.close()
-
-        # generate source
-        search = 'def upgrade(migrate_engine):'
-        contents = contents.replace(search, '\n\n'.join((decls, search)), 1)
-        if upgradeCommands:
-            contents = contents.replace('    pass', upgradeCommands, 1)
-        if downgradeCommands:
-            contents = contents.replace('    pass', downgradeCommands, 1)
-        return contents
-
-    @classmethod
-    def verify_module(cls, path):
-        """Ensure path is a valid script
-
-        :param path: Script location
-        :type path: string
-        :raises: :exc:`InvalidScriptError <migrate.exceptions.InvalidScriptError>`
-        :returns: Python module
-        """
-        # Try to import and get the upgrade() func
-        module = import_path(path)
-        try:
-            assert callable(module.upgrade)
-        except Exception as e:
-            raise InvalidScriptError(path + ': %s' % str(e))
-        return module
-
-    def preview_sql(self, url, step, **args):
-        """Mocks SQLAlchemy Engine to store all executed calls in a string
-        and runs :meth:`PythonScript.run <migrate.versioning.script.py.PythonScript.run>`
-
-        :returns: SQL file
-        """
-        buf = StringIO()
-        args['engine_arg_strategy'] = 'mock'
-        args['engine_arg_executor'] = lambda s, p = '': buf.write(str(s) + p)
-
-        @with_engine
-        def go(url, step, **kw):
-            engine = kw.pop('engine')
-            self.run(engine, step)
-            return buf.getvalue()
-
-        return go(url, step, **args)
-
-    def run(self, engine, step):
-        """Core method of Script file.
-        Executes :func:`update` or :func:`downgrade` functions
-
-        :param engine: SQLAlchemy Engine
-        :param step: Operation to run
-        :type engine: string
-        :type step: int
-        """
-        if step > 0:
-            op = 'upgrade'
-        elif step < 0:
-            op = 'downgrade'
-        else:
-            raise ScriptError("%d is not a valid step" % step)
-
-        funcname = base.operations[op]
-        script_func = self._func(funcname)
-
-        # check for old way of using engine
-        if not inspect.getargspec(script_func)[0]:
-            raise TypeError("upgrade/downgrade functions must accept engine"
-                " parameter (since version 0.5.4)")
-
-        script_func(engine)
-
-    @property
-    def module(self):
-        """Calls :meth:`migrate.versioning.script.py.verify_module`
-        and returns it.
-        """
-        if not hasattr(self, '_module'):
-            self._module = self.verify_module(self.path)
-        return self._module
-
-    def _func(self, funcname):
-        if not hasattr(self.module, funcname):
-            msg = "Function '%s' is not defined in this script"
-            raise ScriptError(msg % funcname)
-        return getattr(self.module, funcname)
--- a/kallithea/lib/dbmigrate/migrate/versioning/script/sql.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-import logging
-import shutil
-
-from kallithea.lib.dbmigrate.migrate.versioning.script import base
-from kallithea.lib.dbmigrate.migrate.versioning.template import Template
-
-
-log = logging.getLogger(__name__)
-
-class SqlScript(base.BaseScript):
-    """A file containing plain SQL statements."""
-
-    @classmethod
-    def create(cls, path, **opts):
-        """Create an empty migration script at specified path
-
-        :returns: :class:`SqlScript instance <migrate.versioning.script.sql.SqlScript>`"""
-        cls.require_notfound(path)
-
-        src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None))
-        shutil.copy(src, path)
-        return cls(path)
-
-    # TODO: why is step parameter even here?
-    def run(self, engine, step=None, executemany=True):
-        """Runs SQL script through raw dbapi execute call"""
-        text = self.source()
-        # Don't rely on SA's autocommit here
-        # (SA uses .startswith to check if a commit is needed. What if script
-        # starts with a comment?)
-        conn = engine.connect()
-        try:
-            trans = conn.begin()
-            try:
-                # HACK: SQLite doesn't allow multiple statements through
-                # its execute() method, but it provides executescript() instead
-                dbapi = conn.engine.raw_connection()
-                if executemany and getattr(dbapi, 'executescript', None):
-                    dbapi.executescript(text)
-                else:
-                    conn.execute(text)
-                trans.commit()
-            except:
-                trans.rollback()
-                raise
-        finally:
-            conn.close()
--- a/kallithea/lib/dbmigrate/migrate/versioning/shell.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-"""The migrate command-line tool."""
-
-import sys
-import inspect
-import logging
-from optparse import OptionParser, BadOptionError
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning import api
-from kallithea.lib.dbmigrate.migrate.versioning.config import *
-from kallithea.lib.dbmigrate.migrate.versioning.util import asbool
-
-
-alias = dict(
-    s=api.script,
-    vc=api.version_control,
-    dbv=api.db_version,
-    v=api.version,
-)
-
-def alias_setup():
-    global alias
-    for key, val in alias.iteritems():
-        setattr(api, key, val)
-alias_setup()
-
-
-class PassiveOptionParser(OptionParser):
-
-    def _process_args(self, largs, rargs, values):
-        """little hack to support all --some_option=value parameters"""
-
-        while rargs:
-            arg = rargs[0]
-            if arg == "--":
-                del rargs[0]
-                return
-            elif arg[0:2] == "--":
-                # if parser does not know about the option
-                # pass it along (make it anonymous)
-                try:
-                    opt = arg.split('=', 1)[0]
-                    self._match_long_opt(opt)
-                except BadOptionError:
-                    largs.append(arg)
-                    del rargs[0]
-                else:
-                    self._process_long_opt(rargs, values)
-            elif arg[:1] == "-" and len(arg) > 1:
-                self._process_short_opts(rargs, values)
-            elif self.allow_interspersed_args:
-                largs.append(arg)
-                del rargs[0]
-
-def main(argv=None, **kwargs):
-    """Shell interface to :mod:`migrate.versioning.api`.
-
-    kwargs are default options that can be overridden with passing
-    --some_option as command line option
-
-    :param disable_logging: Let migrate configure logging
-    :type disable_logging: bool
-    """
-    if argv is not None:
-        argv = argv
-    else:
-        argv = list(sys.argv[1:])
-    commands = list(api.__all__)
-    commands.sort()
-
-    usage = """%%prog COMMAND ...
-
-    Available commands:
-        %s
-
-    Enter "%%prog help COMMAND" for information on a particular command.
-    """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands])
-
-    parser = PassiveOptionParser(usage=usage)
-    parser.add_option("-d", "--debug",
-                     action="store_true",
-                     dest="debug",
-                     default=False,
-                     help="Shortcut to turn on DEBUG mode for logging")
-    parser.add_option("-q", "--disable_logging",
-                      action="store_true",
-                      dest="disable_logging",
-                      default=False,
-                      help="Use this option to disable logging configuration")
-    help_commands = ['help', '-h', '--help']
-    HELP = False
-
-    try:
-        command = argv.pop(0)
-        if command in help_commands:
-            HELP = True
-            command = argv.pop(0)
-    except IndexError:
-        parser.print_help()
-        return
-
-    command_func = getattr(api, command, None)
-    if command_func is None or command.startswith('_'):
-        parser.error("Invalid command %s" % command)
-
-    parser.set_usage(inspect.getdoc(command_func))
-    f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func)
-    for arg in f_args:
-        parser.add_option(
-            "--%s" % arg,
-            dest=arg,
-            action='store',
-            type="string")
-
-    # display help of the current command
-    if HELP:
-        parser.print_help()
-        return
-
-    options, args = parser.parse_args(argv)
-
-    # override kwargs with anonymous parameters
-    override_kwargs = dict()
-    for arg in list(args):
-        if arg.startswith('--'):
-            args.remove(arg)
-            if '=' in arg:
-                opt, value = arg[2:].split('=', 1)
-            else:
-                opt = arg[2:]
-                value = True
-            override_kwargs[opt] = value
-
-    # override kwargs with options if user is overwriting
-    for key, value in options.__dict__.iteritems():
-        if value is not None:
-            override_kwargs[key] = value
-
-    # arguments that function accepts without passed kwargs
-    f_required = list(f_args)
-    candidates = dict(kwargs)
-    candidates.update(override_kwargs)
-    for key, value in candidates.iteritems():
-        if key in f_args:
-            f_required.remove(key)
-
-    # map function arguments to parsed arguments
-    for arg in args:
-        try:
-            kw = f_required.pop(0)
-        except IndexError:
-            parser.error("Too many arguments for command %s: %s" % (command,
-                                                                    arg))
-        kwargs[kw] = arg
-
-    # apply overrides
-    kwargs.update(override_kwargs)
-
-    # configure options
-    for key, value in options.__dict__.iteritems():
-        kwargs.setdefault(key, value)
-
-    # configure logging
-    if not asbool(kwargs.pop('disable_logging', False)):
-        # filter to log =< INFO into stdout and rest to stderr
-        class SingleLevelFilter(logging.Filter):
-            def __init__(self, min=None, max=None):
-                self.min = min or 0
-                self.max = max or 100
-
-            def filter(self, record):
-                return self.min <= record.levelno <= self.max
-
-        logger = logging.getLogger()
-        h1 = logging.StreamHandler(sys.stdout)
-        f1 = SingleLevelFilter(max=logging.INFO)
-        h1.addFilter(f1)
-        h2 = logging.StreamHandler(sys.stderr)
-        f2 = SingleLevelFilter(min=logging.WARN)
-        h2.addFilter(f2)
-        logger.addHandler(h1)
-        logger.addHandler(h2)
-
-        if options.debug:
-            logger.setLevel(logging.DEBUG)
-        else:
-            logger.setLevel(logging.INFO)
-
-    log = logging.getLogger(__name__)
-
-    # check if all args are given
-    try:
-        num_defaults = len(f_defaults)
-    except TypeError:
-        num_defaults = 0
-    f_args_default = f_args[len(f_args) - num_defaults:]
-    required = list(set(f_required) - set(f_args_default))
-    if required:
-        parser.error("Not enough arguments for command %s: %s not specified" \
-            % (command, ', '.join(required)))
-
-    # handle command
-    try:
-        ret = command_func(**kwargs)
-        if ret is not None:
-            log.info(ret)
-    except (exceptions.UsageError, exceptions.KnownError) as e:
-        parser.error(e.args[0])
-
-if __name__ == "__main__":
-    main()
--- a/kallithea/lib/dbmigrate/migrate/versioning/template.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-import os
-import shutil
-import sys
-
-from pkg_resources import resource_filename
-
-from kallithea.lib.dbmigrate.migrate.versioning.config import *
-from kallithea.lib.dbmigrate.migrate.versioning import pathed
-
-
-class Collection(pathed.Pathed):
-    """A collection of templates of a specific type"""
-    _mask = None
-
-    def get_path(self, file):
-        return os.path.join(self.path, str(file))
-
-
-class RepositoryCollection(Collection):
-    _mask = '%s'
-
-class ScriptCollection(Collection):
-    _mask = '%s.py_tmpl'
-
-class ManageCollection(Collection):
-    _mask = '%s.py_tmpl'
-
-class SQLScriptCollection(Collection):
-    _mask = '%s.py_tmpl'
-
-class Template(pathed.Pathed):
-    """Finds the paths/packages of various Migrate templates.
-
-    :param path: Templates are loaded from kallithea.lib.dbmigrate.migrate package
-    if `path` is not provided.
-    """
-    pkg = 'kallithea.lib.dbmigrate.migrate.versioning.templates'
-    _manage = 'manage.py_tmpl'
-
-    def __new__(cls, path=None):
-        if path is None:
-            path = cls._find_path(cls.pkg)
-        return super(Template, cls).__new__(cls, path)
-
-    def __init__(self, path=None):
-        if path is None:
-            path = Template._find_path(self.pkg)
-        super(Template, self).__init__(path)
-        self.repository = RepositoryCollection(os.path.join(path, 'repository'))
-        self.script = ScriptCollection(os.path.join(path, 'script'))
-        self.manage = ManageCollection(os.path.join(path, 'manage'))
-        self.sql_script = SQLScriptCollection(os.path.join(path, 'sql_script'))
-
-    @classmethod
-    def _find_path(cls, pkg):
-        """Returns absolute path to dotted python package."""
-        tmp_pkg = pkg.rsplit('.', 1)
-
-        if len(tmp_pkg) != 1:
-            return resource_filename(tmp_pkg[0], tmp_pkg[1])
-        else:
-            return resource_filename(tmp_pkg[0], '')
-
-    def _get_item(self, collection, theme=None):
-        """Locates and returns collection.
-
-        :param collection: name of collection to locate
-        :param type_: type of subfolder in collection (defaults to "_default")
-        :returns: (package, source)
-        :rtype: str, str
-        """
-        item = getattr(self, collection)
-        theme_mask = getattr(item, '_mask')
-        theme = theme_mask % (theme or 'default')
-        return item.get_path(theme)
-
-    def get_repository(self, *a, **kw):
-        """Calls self._get_item('repository', *a, **kw)"""
-        return self._get_item('repository', *a, **kw)
-
-    def get_script(self, *a, **kw):
-        """Calls self._get_item('script', *a, **kw)"""
-        return self._get_item('script', *a, **kw)
-
-    def get_sql_script(self, *a, **kw):
-        """Calls self._get_item('sql_script', *a, **kw)"""
-        return self._get_item('sql_script', *a, **kw)
-
-    def get_manage(self, *a, **kw):
-        """Calls self._get_item('manage', *a, **kw)"""
-        return self._get_item('manage', *a, **kw)
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-#!/usr/bin/env python2
-from migrate.versioning.shell import main
-
-if __name__ == '__main__':
-    main(%(defaults)s)
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-#!/usr/bin/env python2
-from migrate.versioning.shell import main
-
-{{py:
-_vars = locals().copy()
-del _vars['__template_name__']
-_vars.pop('repository_name', None)
-defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
-}}
-
-if __name__ == '__main__':
-    main({{ defaults }})
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-import sys
-
-from sqlalchemy import engine_from_config
-from paste.deploy.loadwsgi import ConfigLoader
-
-from migrate.versioning.shell import main
-from {{ locals().pop('repository_name') }}.model import migrations
-
-
-if '-c' in sys.argv:
-    pos = sys.argv.index('-c')
-    conf_path = sys.argv[pos + 1]
-    del sys.argv[pos:pos + 2]
-else:
-    conf_path = 'development.ini'
-
-{{py:
-_vars = locals().copy()
-del _vars['__template_name__']
-defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()])
-}}
-
-conf_dict = ConfigLoader(conf_path).parser._sections['app:main']
-
-# migrate supports passing url as an existing Engine instance (since 0.6.0)
-# usage: migrate -c path/to/config.ini COMMANDS
-if __name__ == '__main__':
-    main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }})
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/README	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-This is a database migration repository.
-
-More information at
-http://code.google.com/p/sqlalchemy-migrate/
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-[db_settings]
-# Used to identify which repository this database is versioned under.
-# You can use the name of your project.
-repository_id={{ locals().pop('repository_id') }}
-
-# The name of the database table used to track the schema version.
-# This name shouldn't already be used by your project.
-# If this is changed once a database is under version control, you'll need to
-# change the table name in each database too.
-version_table={{ locals().pop('version_table') }}
-
-# When committing a change script, Migrate will attempt to generate the
-# sql for all supported databases; normally, if one of them fails - probably
-# because you don't have that database installed - it is ignored and the
-# commit continues, perhaps ending successfully.
-# Databases in this list MUST compile successfully during a commit, or the
-# entire commit will fail. List the databases your application will actually
-# be using to ensure your updates to that database work properly.
-# This must be a list; example: ['postgres','sqlite']
-required_dbs={{ locals().pop('required_dbs') }}
-
-# When creating new change scripts, Migrate will stamp the new script with
-# a version number. By default this is latest_version + 1. You can set this
-# to 'true' to tell Migrate to use the UTC timestamp instead.
-use_timestamp_numbering='false'
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-This is a database migration repository.
-
-More information at
-http://code.google.com/p/sqlalchemy-migrate/
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-[db_settings]
-# Used to identify which repository this database is versioned under.
-# You can use the name of your project.
-repository_id={{ locals().pop('repository_id') }}
-
-# The name of the database table used to track the schema version.
-# This name shouldn't already be used by your project.
-# If this is changed once a database is under version control, you'll need to
-# change the table name in each database too.
-version_table={{ locals().pop('version_table') }}
-
-# When committing a change script, Migrate will attempt to generate the
-# sql for all supported databases; normally, if one of them fails - probably
-# because you don't have that database installed - it is ignored and the
-# commit continues, perhaps ending successfully.
-# Databases in this list MUST compile successfully during a commit, or the
-# entire commit will fail. List the databases your application will actually
-# be using to ensure your updates to that database work properly.
-# This must be a list; example: ['postgres','sqlite']
-required_dbs={{ locals().pop('required_dbs') }}
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-from sqlalchemy import *
-from migrate import *
-
-def upgrade(migrate_engine):
-    # Upgrade operations go here. Don't create your own engine; bind migrate_engine
-    # to your metadata
-    pass
-
-def downgrade(migrate_engine):
-    # Operations to reverse the above upgrade go here.
-    pass
--- a/kallithea/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-from sqlalchemy import *
-from migrate import *
-
-def upgrade(migrate_engine):
-    # Upgrade operations go here. Don't create your own engine; bind migrate_engine
-    # to your metadata
-    pass
-
-def downgrade(migrate_engine):
-    # Operations to reverse the above upgrade go here.
-    pass
--- a/kallithea/lib/dbmigrate/migrate/versioning/util/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-""".. currentmodule:: migrate.versioning.util"""
-
-import warnings
-import logging
-from decorator import decorator
-from pkg_resources import EntryPoint
-
-from sqlalchemy import create_engine
-from sqlalchemy.engine import Engine
-from sqlalchemy.pool import StaticPool
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning.util.keyedinstance import KeyedInstance
-from kallithea.lib.dbmigrate.migrate.versioning.util.importpath import import_path
-
-
-log = logging.getLogger(__name__)
-
-def load_model(dotted_name):
-    """Import module and use module-level variable".
-
-    :param dotted_name: path to model in form of string: ``some.python.module:Class``
-
-    .. versionchanged:: 0.5.4
-
-    """
-    if isinstance(dotted_name, basestring):
-        if ':' not in dotted_name:
-            # backwards compatibility
-            warnings.warn('model should be in form of module.model:User '
-                'and not module.model.User', exceptions.MigrateDeprecationWarning)
-            dotted_name = ':'.join(dotted_name.rsplit('.', 1))
-        return EntryPoint.parse('x=%s' % dotted_name).load(False)
-    else:
-        # Assume it's already loaded.
-        return dotted_name
-
-def asbool(obj):
-    """Do everything to use object as bool"""
-    if isinstance(obj, basestring):
-        obj = obj.strip().lower()
-        if obj in ['true', 'yes', 'on', 'y', 't', '1']:
-            return True
-        elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
-            return False
-        else:
-            raise ValueError("String is not true/false: %r" % obj)
-    if obj in (True, False):
-        return bool(obj)
-    else:
-        raise ValueError("String is not true/false: %r" % obj)
-
-def guess_obj_type(obj):
-    """Do everything to guess object type from string
-
-    Tries to convert to `int`, `bool` and finally returns if not succeded.
-
-    .. versionadded: 0.5.4
-    """
-
-    result = None
-
-    try:
-        result = int(obj)
-    except:
-        pass
-
-    if result is None:
-        try:
-            result = asbool(obj)
-        except:
-            pass
-
-    if result is not None:
-        return result
-    else:
-        return obj
-
-@decorator
-def catch_known_errors(f, *a, **kw):
-    """Decorator that catches known api errors
-
-    .. versionadded: 0.5.4
-    """
-
-    try:
-        return f(*a, **kw)
-    except exceptions.PathFoundError as e:
-        raise exceptions.KnownError("The path %s already exists" % e.args[0])
-
-def construct_engine(engine, **opts):
-    """.. versionadded:: 0.5.4
-
-    Constructs and returns SQLAlchemy engine.
-
-    Currently, there are 2 ways to pass create_engine options to :mod:`migrate.versioning.api` functions:
-
-    :param engine: connection string or a existing engine
-    :param engine_dict: python dictionary of options to pass to `create_engine`
-    :param engine_arg_*: keyword parameters to pass to `create_engine` (evaluated with :func:`migrate.versioning.util.guess_obj_type`)
-    :type engine_dict: dict
-    :type engine: string or Engine instance
-    :type engine_arg_*: string
-    :returns: SQLAlchemy Engine
-
-    .. note::
-
-        keyword parameters override ``engine_dict`` values.
-
-    """
-    if isinstance(engine, Engine):
-        return engine
-    elif not isinstance(engine, basestring):
-        raise ValueError("you need to pass either an existing engine or a database uri")
-
-    # get options for create_engine
-    if opts.get('engine_dict') and isinstance(opts['engine_dict'], dict):
-        kwargs = opts['engine_dict']
-    else:
-        kwargs = dict()
-
-    # DEPRECATED: handle echo the old way
-    echo = asbool(opts.get('echo', False))
-    if echo:
-        warnings.warn('echo=True parameter is deprecated, pass '
-            'engine_arg_echo=True or engine_dict={"echo": True}',
-            exceptions.MigrateDeprecationWarning)
-        kwargs['echo'] = echo
-
-    # parse keyword arguments
-    for key, value in opts.iteritems():
-        if key.startswith('engine_arg_'):
-            kwargs[key[11:]] = guess_obj_type(value)
-
-    log.debug('Constructing engine')
-    # TODO: return create_engine(engine, poolclass=StaticPool, **kwargs)
-    # seems like 0.5.x branch does not work with engine.dispose and staticpool
-    return create_engine(engine, **kwargs)
-
-@decorator
-def with_engine(f, *a, **kw):
-    """Decorator for :mod:`migrate.versioning.api` functions
-    to safely close resources after function usage.
-
-    Passes engine parameters to :func:`construct_engine` and
-    resulting parameter is available as kw['engine'].
-
-    Engine is disposed after wrapped function is executed.
-
-    .. versionadded: 0.6.0
-    """
-    url = a[0]
-    engine = construct_engine(url, **kw)
-
-    try:
-        kw['engine'] = engine
-        return f(*a, **kw)
-    finally:
-        if isinstance(engine, Engine) and engine is not url:
-            log.debug('Disposing SQLAlchemy engine %s', engine)
-            engine.dispose()
-
-
-class Memoize:
-    """Memoize(fn) - an instance which acts like fn but memoizes its arguments
-       Will only work on functions with non-mutable arguments
-
-       ActiveState Code 52201
-    """
-    def __init__(self, fn):
-        self.fn = fn
-        self.memo = {}
-
-    def __call__(self, *args):
-        if not self.memo.has_key(args):
-            self.memo[args] = self.fn(*args)
-        return self.memo[args]
--- a/kallithea/lib/dbmigrate/migrate/versioning/util/importpath.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-import os
-import sys
-
-def import_path(fullpath):
-    """ Import a file with full path specification. Allows one to
-        import from anywhere, something __import__ does not do.
-    """
-    # http://zephyrfalcon.org/weblog/arch_d7_2002_08_31.html
-    path, filename = os.path.split(fullpath)
-    filename, ext = os.path.splitext(filename)
-    sys.path.append(path)
-    module = __import__(filename)
-    reload(module) # Might be out of date during tests
-    del sys.path[-1]
-    return module
--- a/kallithea/lib/dbmigrate/migrate/versioning/util/keyedinstance.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-class KeyedInstance(object):
-    """A class whose instances have a unique identifier of some sort
-    No two instances with the same unique ID should exist - if we try to create
-    a second instance, the first should be returned.
-    """
-
-    _instances = dict()
-
-    def __new__(cls, *p, **k):
-        instances = cls._instances
-        clskey = str(cls)
-        if clskey not in instances:
-            instances[clskey] = dict()
-        instances = instances[clskey]
-
-        key = cls._key(*p, **k)
-        if key not in instances:
-            instances[key] = super(KeyedInstance, cls).__new__(cls)
-        return instances[key]
-
-    @classmethod
-    def _key(cls, *p, **k):
-        """Given a unique identifier, return a dictionary key
-        This should be overridden by child classes, to specify which parameters
-        should determine an object's uniqueness
-        """
-        raise NotImplementedError()
-
-    @classmethod
-    def clear(cls):
-        # Allow cls.clear() as well as uniqueInstance.clear(cls)
-        if str(cls) in cls._instances:
-            del cls._instances[str(cls)]
--- a/kallithea/lib/dbmigrate/migrate/versioning/version.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,238 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import shutil
-import logging
-
-from kallithea.lib.dbmigrate.migrate import exceptions
-from kallithea.lib.dbmigrate.migrate.versioning import pathed, script
-from datetime import datetime
-
-
-log = logging.getLogger(__name__)
-
-class VerNum(object):
-    """A version number that behaves like a string and int at the same time"""
-
-    _instances = dict()
-
-    def __new__(cls, value):
-        val = str(value)
-        if val not in cls._instances:
-            cls._instances[val] = super(VerNum, cls).__new__(cls)
-        ret = cls._instances[val]
-        return ret
-
-    def __init__(self,value):
-        self.value = str(int(value))
-        if self < 0:
-            raise ValueError("Version number cannot be negative")
-
-    def __add__(self, value):
-        ret = int(self) + int(value)
-        return VerNum(ret)
-
-    def __sub__(self, value):
-        return self + (int(value) * -1)
-
-    def __cmp__(self, value):
-        return int(self) - int(value)
-
-    def __repr__(self):
-        return "<VerNum(%s)>" % self.value
-
-    def __str__(self):
-        return str(self.value)
-
-    def __int__(self):
-        return int(self.value)
-
-
-class Collection(pathed.Pathed):
-    """A collection of versioning scripts in a repository"""
-
-    FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*')
-
-    def __init__(self, path):
-        """Collect current version scripts in repository
-        and store them in self.versions
-        """
-        super(Collection, self).__init__(path)
-
-        # Create temporary list of files, allowing skipped version numbers.
-        files = os.listdir(path)
-        if '1' in files:
-            # deprecation
-            raise Exception('It looks like you have a repository in the old '
-                'format (with directories for each version). '
-                'Please convert repository before proceeding.')
-
-        tempVersions = dict()
-        for filename in files:
-            match = self.FILENAME_WITH_VERSION.match(filename)
-            if match:
-                num = int(match.group(1))
-                tempVersions.setdefault(num, []).append(filename)
-            else:
-                pass  # Must be a helper file or something, let's ignore it.
-
-        # Create the versions member where the keys
-        # are VerNum's and the values are Version's.
-        self.versions = dict()
-        for num, files in tempVersions.items():
-            self.versions[VerNum(num)] = Version(num, path, files)
-
-    @property
-    def latest(self):
-        """:returns: Latest version in Collection"""
-        return max([VerNum(0)] + self.versions.keys())
-
-    def _next_ver_num(self, use_timestamp_numbering):
-        if use_timestamp_numbering:
-            return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S')))
-        else:
-            return self.latest + 1
-
-    def create_new_python_version(self, description, **k):
-        """Create Python files for new version"""
-        ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
-        extra = str_to_filename(description)
-
-        if extra:
-            if extra == '_':
-                extra = ''
-            elif not extra.startswith('_'):
-                extra = '_%s' % extra
-
-        filename = '%03d%s.py' % (ver, extra)
-        filepath = self._version_path(filename)
-
-        script.PythonScript.create(filepath, **k)
-        self.versions[ver] = Version(ver, self.path, [filename])
-
-    def create_new_sql_version(self, database, description, **k):
-        """Create SQL files for new version"""
-        ver = self._next_ver_num(k.pop('use_timestamp_numbering', False))
-        self.versions[ver] = Version(ver, self.path, [])
-
-        extra = str_to_filename(description)
-
-        if extra:
-            if extra == '_':
-                extra = ''
-            elif not extra.startswith('_'):
-                extra = '_%s' % extra
-
-        # Create new files.
-        for op in ('upgrade', 'downgrade'):
-            filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op)
-            filepath = self._version_path(filename)
-            script.SqlScript.create(filepath, **k)
-            self.versions[ver].add_script(filepath)
-
-    def version(self, vernum=None):
-        """Returns latest Version if vernum is not given.
-        Otherwise, returns wanted version"""
-        if vernum is None:
-            vernum = self.latest
-        return self.versions[VerNum(vernum)]
-
-    @classmethod
-    def clear(cls):
-        super(Collection, cls).clear()
-
-    def _version_path(self, ver):
-        """Returns path of file in versions repository"""
-        return os.path.join(self.path, str(ver))
-
-
-class Version(object):
-    """A single version in a collection
-    :param vernum: Version Number
-    :param path: Path to script files
-    :param filelist: List of scripts
-    :type vernum: int, VerNum
-    :type path: string
-    :type filelist: list
-    """
-
-    def __init__(self, vernum, path, filelist):
-        self.version = VerNum(vernum)
-
-        # Collect scripts in this folder
-        self.sql = dict()
-        self.python = None
-
-        for script in filelist:
-            self.add_script(os.path.join(path, script))
-
-    def script(self, database=None, operation=None):
-        """Returns SQL or Python Script"""
-        for db in (database, 'default'):
-            # Try to return a .sql script first
-            try:
-                return self.sql[db][operation]
-            except KeyError:
-                continue  # No .sql script exists
-
-        # TODO: maybe add force Python parameter?
-        ret = self.python
-
-        assert ret is not None, \
-            "There is no script for %d version" % self.version
-        return ret
-
-    def add_script(self, path):
-        """Add script to Collection/Version"""
-        if path.endswith(Extensions.py):
-            self._add_script_py(path)
-        elif path.endswith(Extensions.sql):
-            self._add_script_sql(path)
-
-    SQL_FILENAME = re.compile(r'^.*\.sql')
-
-    def _add_script_sql(self, path):
-        basename = os.path.basename(path)
-        match = self.SQL_FILENAME.match(basename)
-
-        if match:
-            basename = basename.replace('.sql', '')
-            parts = basename.split('_')
-            if len(parts) < 3:
-                raise exceptions.ScriptError(
-                    "Invalid SQL script name %s " % basename + \
-                    "(needs to be ###_description_database_operation.sql)")
-            version = parts[0]
-            op = parts[-1]
-            dbms = parts[-2]
-        else:
-            raise exceptions.ScriptError(
-                "Invalid SQL script name %s " % basename + \
-                "(needs to be ###_description_database_operation.sql)")
-
-        # File the script into a dictionary
-        self.sql.setdefault(dbms, {})[op] = script.SqlScript(path)
-
-    def _add_script_py(self, path):
-        if self.python is not None:
-            raise exceptions.ScriptError('You can only have one Python script '
-                'per version, but you have: %s and %s' % (self.python, path))
-        self.python = script.PythonScript(path)
-
-
-class Extensions:
-    """A namespace for file extensions"""
-    py = 'py'
-    sql = 'sql'
-
-def str_to_filename(s):
-    """Replaces spaces, (double and single) quotes
-    and double underscores to underscores
-    """
-
-    s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_")
-    while '__' in s:
-        s = s.replace('__', '_')
-    return s
--- a/kallithea/lib/dbmigrate/schema/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +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.lib.dbmigrate.schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Schemas for migrations
-
-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: Nov 1, 2011
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
--- a/kallithea/lib/dbmigrate/schema/db_1_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-from sqlalchemy import *
-from sqlalchemy.orm import relation, class_mapper
-from sqlalchemy.orm.session import Session
-from kallithea.model.meta import Base
-
-class BaseModel(object):
-    """Base Model for all classess
-
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session.query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session.delete(obj)
-        Session.commit()
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
-                      UniqueConstraint('user_id', 'follows_user_id')
-                      , {'useexisting':True})
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
-
-    user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relation('Repository')
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __repr__(self):
-        return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
--- a/kallithea/lib/dbmigrate/schema/db_1_2_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1097 +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.lib.dbmigrate.schema.db_1_2_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.2.X
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import logging
-import datetime
-import traceback
-from datetime import date
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    generate_api_key, safe_unicode
-from kallithea.lib.exceptions import UserGroupsAssignedException
-from kallithea.lib.compat import json
-
-from kallithea.model.meta import Base, Session
-from kallithea.lib.caching_query import FromCache
-
-from kallithea import DB_PREFIX
-
-log = logging.getLogger(__name__)
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-class ModelSerializer(json.JSONEncoder):
-    """
-    Simple Serializer for JSON,
-
-    usage::
-
-        to make object customized for serialization implement a __json__
-        method that will return a dict for serialization into json
-
-    example::
-
-        class Task(object):
-
-            def __init__(self, name, value):
-                self.name = name
-                self.value = value
-
-            def __json__(self):
-                return dict(name=self.name,
-                            value=self.value)
-
-    """
-
-    def default(self, obj):
-
-        if hasattr(obj, '__json__'):
-            return obj.__json__()
-        else:
-            return json.JSONEncoder.default(self, obj)
-
-class BaseModel(object):
-    """Base Model for all classes
-
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session.query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session.delete(obj)
-        Session.commit()
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if v == 'ldap_active':
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.app_settings_name, self.app_settings_value)
-
-
-    @classmethod
-    def get_by_name(cls, ldap_key):
-        return cls.query()\
-            .filter(cls.app_settings_name == ldap_key).scalar()
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name:row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'pretxnchangegroup.push_logger'
-    HOOK_PULL = 'preoutgoing.pull_logger'
-
-    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key)
-
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
-                                    cls.HOOK_REPO_SIZE,
-                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
-                                    cls.HOOK_REPO_SIZE,
-                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key).scalar() or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session.add(new_ui)
-        Session.commit()
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
-    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
-    name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    user_log = relationship('UserLog', cascade='all')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.name, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.name, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    def __repr__(self):
-        try:
-            return "<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                             self.user_id, self.username)
-        except:
-            return self.__class__.__name__
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False):
-        if case_insensitive:
-            return Session.query(cls).filter(cls.username.ilike(username)).scalar()
-        else:
-            return Session.query(cls).filter(cls.username == username).scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key):
-        return cls.query().filter(cls.api_key == api_key).one()
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-
-        self.last_login = datetime.datetime.now()
-        Session.add(self)
-        Session.commit()
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def create(cls, form_data):
-        from kallithea.lib.auth import get_crypt_password
-
-        try:
-            new_user = cls()
-            for k, v in form_data.items():
-                if k == 'password':
-                    v = get_crypt_password(v)
-                setattr(new_user, k, v)
-
-            new_user.api_key = generate_api_key()
-            Session.add(new_user)
-            Session.commit()
-            return new_user
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = {'extend_existing':True}
-    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = {'extend_existing':True}
-
-    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-
-    def __repr__(self):
-        return '<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.users_group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.users_group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache("sql_cache_short",
-                                          "get_user_%s" % group_name))
-        return gr.scalar()
-
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    @classmethod
-    def create(cls, form_data):
-        try:
-            new_users_group = cls()
-            for k, v in form_data.items():
-                setattr(new_users_group, k, v)
-
-            Session.add(new_users_group)
-            Session.commit()
-            return new_users_group
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-    @classmethod
-    def update(cls, users_group_id, form_data):
-
-        try:
-            users_group = cls.get(users_group_id, cache=False)
-
-            for k, v in form_data.items():
-                if k == 'users_group_members':
-                    users_group.members = []
-                    Session.flush()
-                    members_list = []
-                    if v:
-                        v = [v] if isinstance(v, basestring) else v
-                        for u_id in set(v):
-                            member = UserGroupMember(users_group_id, u_id)
-                            members_list.append(member)
-                    setattr(users_group, 'members', members_list)
-                setattr(users_group, k, v)
-
-            Session.add(users_group)
-            Session.commit()
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-    @classmethod
-    def delete(cls, users_group_id):
-        try:
-
-            # check if this group is not assigned to repo
-            assigned_groups = UserGroupRepoToPerm.query()\
-                .filter(UserGroupRepoToPerm.users_group_id ==
-                        users_group_id).all()
-
-            if assigned_groups:
-                raise UserGroupsAssignedException('RepoGroup assigned to %s' %
-                                                   assigned_groups)
-
-            users_group = cls.get(users_group_id, cache=False)
-            Session.delete(users_group)
-            Session.commit()
-        except:
-            log.error(traceback.format_exc())
-            Session.rollback()
-            raise
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = {'extend_existing':True}
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-    @staticmethod
-    def add_user_to_group(group, user):
-        ugm = UserGroupMember()
-        ugm.users_group = group
-        ugm.user = user
-        Session.add(ugm)
-        Session.commit()
-        return ugm
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
-
-    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
-    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
-    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
-
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
-
-    logs = relationship('UserLog', cascade='all')
-
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.repo_id, self.repo_name)
-
-    @classmethod
-    def url_sep(cls):
-        return '/'
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session.query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.one()
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session.query(Ui).filter(Ui.ui_key ==
-                                              cls.url_sep())
-        q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session.query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*p)
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from mercurial import ui
-        from mercurial import config
-        baseui = ui.ui()
-
-        #clean the baseui object
-        baseui._ocfg = config.config()
-        baseui._ucfg = config.config()
-        baseui._tcfg = config.config()
-
-
-        ret = Ui.query()\
-            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
-
-        hg_ui = ret
-        for ui_ in hg_ui:
-            if ui_.ui_active:
-                log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
-                          ui_.ui_key, ui_.ui_value)
-                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
-
-        return baseui
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    @LazyProperty
-    def scm_instance(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance_cached(self):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        inv = self.invalidate
-        if inv is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(inv.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-
-        repo_full_path = self.repo_full_path
-
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class Group(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
-                      CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
-    __mapper_args__ = {'order_by':'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    parent_group = relationship('Group', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls):
-        from webhelpers.html import literal as _literal
-        repo_groups = [('', '')]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in cls.query().all()])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return '/'
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache("sql_cache_short",
-                                          "get_group_%s" % group_name))
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return Group.query().filter(Group.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(Group.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(Group.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query().filter(Repository.group == self)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return Group.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = {'extend_existing':True}
-    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.permission_id, self.permission_name)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission')
-
-    @classmethod
-    def has_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        return cls.query().filter(cls.user_id == user_id)\
-            .filter(cls.permission == perm).scalar() is not None
-
-    @classmethod
-    def grant_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        new = cls()
-        new.user_id = user_id
-        new.permission = perm
-        try:
-            Session.add(new)
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-    @classmethod
-    def revoke_perm(cls, user_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        try:
-            cls.query().filter(cls.user_id == user_id) \
-                .filter(cls.permission == perm).delete()
-            Session.commit()
-        except:
-            Session.rollback()
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    def __repr__(self):
-        return '<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = {'extend_existing':True}
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-    @classmethod
-    def has_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        return cls.query().filter(cls.users_group_id ==
-                                         users_group_id)\
-                                         .filter(cls.permission == perm)\
-                                         .scalar() is not None
-
-    @classmethod
-    def grant_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        new = cls()
-        new.users_group_id = users_group_id
-        new.permission = perm
-        try:
-            Session.add(new)
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-    @classmethod
-    def revoke_perm(cls, users_group_id, perm):
-        if not isinstance(perm, Permission):
-            raise Exception('perm needs to be an instance of Permission class')
-
-        try:
-            cls.query().filter(cls.users_group_id == users_group_id) \
-                .filter(cls.permission == perm).delete()
-            Session.commit()
-        except:
-            Session.rollback()
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'group_to_perm'
-    __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
-
-    group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
-                      UniqueConstraint('user_id', 'follows_user_id')
-                      , {'extend_existing':True})
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __repr__(self):
-        return "<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-        return cls.query()\
-                .filter(CacheInvalidation.cache_key == key)\
-                .filter(CacheInvalidation.cache_active == False)\
-                .scalar()
-
-    @classmethod
-    def set_invalidate(cls, key):
-        """
-        Mark this Cache key for invalidation
-
-        :param key:
-        """
-
-        log.debug('marking %s for invalidation', key)
-        inv_obj = Session.query(cls)\
-            .filter(cls.cache_key == key).scalar()
-        if inv_obj:
-            inv_obj.cache_active = False
-        else:
-            log.debug('cache key not found in invalidation db -> creating one')
-            inv_obj = CacheInvalidation(key)
-
-        try:
-            Session.add(inv_obj)
-            Session.commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session.rollback()
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = Session.query(CacheInvalidation)\
-            .filter(CacheInvalidation.cache_key == key).scalar()
-        inv_obj.cache_active = True
-        Session.add(inv_obj)
-        Session.commit()
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = {'extend_existing':True}
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_3_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1322 +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.lib.dbmigrate.schema.db_1_3_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.3.X
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-
-import os
-import logging
-import datetime
-import traceback
-from collections import defaultdict
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-import hashlib
-
-from kallithea import DB_PREFIX
-
-log = logging.getLogger(__name__)
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class ModelSerializer(json.JSONEncoder):
-    """
-    Simple Serializer for JSON,
-
-    usage::
-
-        to make object customized for serialization implement a __json__
-        method that will return a dict for serialization into json
-
-    example::
-
-        class Task(object):
-
-            def __init__(self, name, value):
-                self.name = name
-                self.value = value
-
-            def __json__(self):
-                return dict(name=self.name,
-                            value=self.value)
-
-    """
-
-    def default(self, obj):
-
-        if hasattr(obj, '__json__'):
-            return obj.__json__()
-        else:
-            return json.JSONEncoder.default(self, obj)
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        for k, val in getattr(self, '__json__', lambda: {})().iteritems():
-            d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session.query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session.delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name == 'ldap_active':
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, ldap_key):
-        return cls.query()\
-            .filter(cls.app_settings_name == ldap_key).scalar()
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name:row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'pretxnchangegroup.push_logger'
-    HOOK_PULL = 'preoutgoing.pull_logger'
-
-    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key)
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
-                                    cls.HOOK_REPO_SIZE,
-                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
-                                    cls.HOOK_REPO_SIZE,
-                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key).scalar() or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session.add(new_ui)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
-    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
-    name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    user_log = relationship('UserLog', cascade='all')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.name, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.name, self.lastname)
-                if (self.name and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.name, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.name, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % email))
-        return q.scalar()
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session.add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    def __json__(self):
-        return dict(
-            user_id=self.user_id,
-            first_name=self.name,
-            last_name=self.lastname,
-            email=self.email,
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
-    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-
-    def __unicode__(self):
-        return u'<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
-    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
-    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
-
-    logs = relationship('UserLog')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
-                                   self.repo_name)
-
-    @classmethod
-    def url_sep(cls):
-        return '/'
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session.query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session.query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session.query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*p)
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from mercurial import ui
-        from mercurial import config
-        baseui = ui.ui()
-
-        #clean the baseui object
-        baseui._ocfg = config.config()
-        baseui._ucfg = config.config()
-        baseui._tcfg = config.config()
-
-        ret = Ui.query()\
-            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
-
-        hg_ui = ret
-        for ui_ in hg_ui:
-            if ui_.ui_active:
-                log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
-                          ui_.ui_key, ui_.ui_value)
-                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
-
-        return baseui
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    @LazyProperty
-    def scm_instance(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance_cached(self):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-        log.debug('Getting cached instance of repo')
-        inv = self.invalidate
-        if inv is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(inv.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls):
-        from webhelpers.html import literal as _literal
-        repo_groups = [('', '')]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in cls.query().all()])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return '/'
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session.query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session.add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<user:%s => %s >' % (self.user, self.repository)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session.add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine':'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-    @classmethod
-    def clear_cache(cls):
-        cls.query().delete()
-
-    @classmethod
-    def _get_key(cls, key):
-        """
-        Wrapper for generating a key, together with a prefix
-
-        :param key:
-        """
-        import kallithea
-        prefix = ''
-        iid = kallithea.CONFIG.get('instance_id')
-        if iid:
-            prefix = iid
-        return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.cache_key == key).scalar()
-
-    @classmethod
-    def _get_or_create_key(cls, key, prefix, org_key):
-        inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
-        if not inv_obj:
-            try:
-                inv_obj = CacheInvalidation(key, org_key)
-                Session.add(inv_obj)
-                Session.commit()
-            except Exception:
-                log.error(traceback.format_exc())
-                Session.rollback()
-        return inv_obj
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-
-        key, _prefix, _org_key = cls._get_key(key)
-        inv = cls._get_or_create_key(key, _prefix, _org_key)
-
-        if inv and inv.cache_active is False:
-            return inv
-
-    @classmethod
-    def set_invalidate(cls, key):
-        """
-        Mark this Cache key for invalidation
-
-        :param key:
-        """
-
-        key, _prefix, _org_key = cls._get_key(key)
-        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
-        log.debug('marking %s key[s] %s for invalidation', len(inv_objs),
-                                                             _org_key)
-        try:
-            for inv_obj in inv_objs:
-                if inv_obj:
-                    inv_obj.cache_active = False
-
-                Session.add(inv_obj)
-            Session.commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session.rollback()
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = cls.get_by_key(key)
-        inv_obj.cache_active = True
-        Session.add(inv_obj)
-        Session.commit()
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
-    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    revision = Column('revision', String(40), nullable=False)
-    line_no = Column('line_no', Unicode(10), nullable=True)
-    f_path = Column('f_path', Unicode(1000), nullable=True)
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
-    text = Column('text', Unicode(25000), nullable=False)
-    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-
-    @classmethod
-    def get_users(cls, revision):
-        """
-        Returns user associated with this changesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        return Session.query(User)\
-                .filter(cls.revision == revision)\
-                .join(ChangesetComment.author).all()
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-
-    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
-    subject = Column('subject', Unicode(512), nullable=True)
-    body = Column('body', Unicode(50000), nullable=True)
-    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session.add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session.add(self)
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine':'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
-
-## this is migration from 1_4_0, but now it's here to overcome a problem of
-## attaching a FK to this from 1_3_0 !
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
--- a/kallithea/lib/dbmigrate/schema/db_1_4_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1814 +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.lib.dbmigrate.schema.db_1_4_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.4.X
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import logging
-import datetime
-import traceback
-import hashlib
-import time
-from collections import defaultdict
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-from kallithea import DB_PREFIX
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name == 'ldap_active':
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    DEFAULT_USER = 'default'
-    DEFAULT_PERMISSIONS = [
-        'hg.register.manual_activate', 'hg.create.repository',
-        'hg.fork.repository', 'repository.read', 'group.read'
-    ]
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog', cascade='all')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
-    repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-
-    def __unicode__(self):
-        return u'<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
-
-        data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
-        )
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*p)
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def inject_ui(cls, repo, extras={}):
-        from kallithea.lib.vcs.backends.hg import MercurialRepository
-        from kallithea.lib.vcs.backends.git import GitRepository
-        required = (MercurialRepository, GitRepository)
-        if not isinstance(repo, required):
-            raise Exception('repo must be instance of %s' % (','.join(required)))
-
-        # inject ui extra param to log this action via push logger
-        for k, v in extras.items():
-            repo._repo.ui.setconfig('extras', k, v)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None
-        )
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id):
-        repo.locked = [user_id, time.time()]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_last_change(self, last_change=None):
-        if last_change is None:
-            last_change = datetime.datetime.now()
-        if self.updated_on is None or self.updated_on != last_change:
-            log.debug('updated repo %s with new date %s', self, last_change)
-            self.updated_on = last_change
-            Session().add(self)
-            Session().commit()
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        :type revisions: list
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(repo_name=self.repo_name)
-
-    @LazyProperty
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, cache_map=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-        log.debug('Getting cached instance of repo')
-
-        if cache_map:
-            # get using prefilled cache_map
-            invalidate_repo = cache_map[self.repo_name]
-            if invalidate_repo:
-                invalidate_repo = (None if invalidate_repo.cache_active
-                                   else invalidate_repo)
-        else:
-            # get from invalidate
-            invalidate_repo = self.invalidate
-
-        if invalidate_repo is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(invalidate_repo.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls, check_perms=False):
-        from webhelpers.html import literal as _literal
-        from kallithea.model.scm import ScmModel
-        groups = cls.query().all()
-        if check_perms:
-            #filter group user have access to, it's done
-            #magically inside ScmModel based on current user
-            groups = ScmModel().get_repos_groups(groups)
-        repo_groups = [('', '')]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        all_ = []
-
-        def _get_members(root_gr):
-            for r in root_gr.repositories:
-                all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    PERMS = [
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository Group no access')),
-        ('group.read', _('Repository Group read access')),
-        ('group.write', _('Repository Group write access')),
-        ('group.admin', _('Repository Group admin access')),
-
-        ('hg.admin', _('Kallithea Administrator')),
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-        ('hg.register.none', _('Register disabled')),
-        ('hg.register.manual_activate', _('Register new user with Kallithea '
-                                          'with manual activation')),
-
-        ('hg.register.auto_activate', _('Register new user with Kallithea '
-                                        'with auto activation')),
-    ]
-
-    # defines which permissions are more important higher the more important
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<user:%s => %s >' % (self.user, self.repository)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-
-    @property
-    def prefix(self):
-        _split = self.cache_key.split(self.cache_args, 1)
-        if _split and len(_split) == 2:
-            return _split[0]
-        return ''
-
-    @classmethod
-    def clear_cache(cls):
-        cls.query().delete()
-
-    @classmethod
-    def _get_key(cls, key):
-        """
-        Wrapper for generating a key, together with a prefix
-
-        :param key:
-        """
-        import kallithea
-        prefix = ''
-        org_key = key
-        iid = kallithea.CONFIG.get('instance_id')
-        if iid:
-            prefix = iid
-
-        return "%s%s" % (prefix, key), prefix, org_key
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.cache_key == key).scalar()
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        return cls.query().filter(cls.cache_args == repo_name).all()
-
-    @classmethod
-    def _get_or_create_key(cls, key, repo_name, commit=True):
-        inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
-        if not inv_obj:
-            try:
-                inv_obj = CacheInvalidation(key, repo_name)
-                Session().add(inv_obj)
-                if commit:
-                    Session().commit()
-            except Exception:
-                log.error(traceback.format_exc())
-                Session().rollback()
-        return inv_obj
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-        repo_name = key
-        repo_name = remove_suffix(repo_name, '_README')
-        repo_name = remove_suffix(repo_name, '_RSS')
-        repo_name = remove_suffix(repo_name, '_ATOM')
-
-        # adds instance prefix
-        key, _prefix, _org_key = cls._get_key(key)
-        inv = cls._get_or_create_key(key, repo_name)
-
-        if inv and inv.cache_active is False:
-            return inv
-
-    @classmethod
-    def set_invalidate(cls, key=None, repo_name=None):
-        """
-        Mark this Cache key for invalidation, either by key or whole
-        cache sets based on repo_name
-
-        :param key:
-        """
-        if key:
-            key, _prefix, _org_key = cls._get_key(key)
-            inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
-        elif repo_name:
-            inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s',
-                  len(inv_objs), key, repo_name)
-        try:
-            for inv_obj in inv_objs:
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = cls.get_by_key(key)
-        inv_obj.cache_active = True
-        Session().add(inv_obj)
-        Session().commit()
-
-    @classmethod
-    def get_cache_map(cls):
-
-        class cachemapdict(dict):
-
-            def __init__(self, *args, **kwargs):
-                fixkey = kwargs.get('fixkey')
-                if fixkey:
-                    del kwargs['fixkey']
-                self.fixkey = fixkey
-                super(cachemapdict, self).__init__(*args, **kwargs)
-
-            def __getattr__(self, name):
-                key = name
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                if key in self.__dict__:
-                    return self.__dict__[key]
-                else:
-                    return self[key]
-
-            def __getitem__(self, key):
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                try:
-                    return super(cachemapdict, self).__getitem__(key)
-                except KeyError:
-                    return
-
-        cache_map = cachemapdict(fixkey=True)
-        for obj in cls.query().all():
-            cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
-        return cache_map
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    def __json__(self):
-        return dict(
-          revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_5_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1841 +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.lib.dbmigrate.schema.db_1_5_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.5.2
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import logging
-import datetime
-import traceback
-import hashlib
-import time
-from collections import defaultdict
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix, remove_prefix
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-from kallithea import DB_PREFIX
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    DEFAULT_USER = 'default'
-    DEFAULT_PERMISSIONS = [
-        'hg.register.manual_activate', 'hg.create.repository',
-        'hg.fork.repository', 'repository.read', 'group.read'
-    ]
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-
-    def __unicode__(self):
-        return u'<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
-
-        data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
-        )
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-
-    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   self.repo_name)
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*p)
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def inject_ui(cls, repo, extras={}):
-        from kallithea.lib.vcs.backends.hg import MercurialRepository
-        from kallithea.lib.vcs.backends.git import GitRepository
-        required = (MercurialRepository, GitRepository)
-        if not isinstance(repo, required):
-            raise Exception('repo must be instance of %s' % (','.join(required)))
-
-        # inject ui extra param to log this action via push logger
-        for k, v in extras.items():
-            repo._repo.ui.setconfig('extras', k, v)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None
-        )
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id):
-        repo.locked = [user_id, time.time()]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_last_change(self, last_change=None):
-        if last_change is None:
-            last_change = datetime.datetime.now()
-        if self.updated_on is None or self.updated_on != last_change:
-            log.debug('updated repo %s with new date %s', self, last_change)
-            self.updated_on = last_change
-            Session().add(self)
-            Session().commit()
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        :type revisions: list
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(repo_name=self.repo_name)
-
-    @LazyProperty
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, cache_map=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-        log.debug('Getting cached instance of repo')
-
-        if cache_map:
-            # get using prefilled cache_map
-            invalidate_repo = cache_map[self.repo_name]
-            if invalidate_repo:
-                invalidate_repo = (None if invalidate_repo.cache_active
-                                   else invalidate_repo)
-        else:
-            # get from invalidate
-            invalidate_repo = self.invalidate
-
-        if invalidate_repo is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(invalidate_repo.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls, check_perms=False):
-        from webhelpers.html import literal as _literal
-        from kallithea.model.scm import ScmModel
-        groups = cls.query().all()
-        if check_perms:
-            #filter group user have access to, it's done
-            #magically inside ScmModel based on current user
-            groups = ScmModel().get_repos_groups(groups)
-        repo_groups = [('', '')]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        all_ = []
-
-        def _get_members(root_gr):
-            for r in root_gr.repositories:
-                all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    PERMS = [
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository Group no access')),
-        ('group.read', _('Repository Group read access')),
-        ('group.write', _('Repository Group write access')),
-        ('group.admin', _('Repository Group admin access')),
-
-        ('hg.admin', _('Kallithea Administrator')),
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-        ('hg.register.none', _('Register disabled')),
-        ('hg.register.manual_activate', _('Register new user with Kallithea '
-                                          'with manual activation')),
-
-        ('hg.register.auto_activate', _('Register new user with Kallithea '
-                                        'with auto activation')),
-    ]
-
-    # defines which permissions are more important higher the more important
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        'hg.create.repository':1
-    }
-
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-    ]
-
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<user:%s => %s >' % (self.user, self.repository)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-
-    @property
-    def prefix(self):
-        _split = self.cache_key.split(self.cache_args, 1)
-        if _split and len(_split) == 2:
-            return _split[0]
-        return ''
-
-    @classmethod
-    def clear_cache(cls):
-        cls.query().delete()
-
-    @classmethod
-    def _get_key(cls, key):
-        """
-        Wrapper for generating a key, together with a prefix
-
-        :param key:
-        """
-        import kallithea
-        prefix = ''
-        org_key = key
-        iid = kallithea.CONFIG.get('instance_id')
-        if iid:
-            prefix = iid
-
-        return "%s%s" % (prefix, key), prefix, org_key
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.cache_key == key).scalar()
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        return cls.query().filter(cls.cache_args == repo_name).all()
-
-    @classmethod
-    def _get_or_create_key(cls, key, repo_name, commit=True):
-        inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
-        if not inv_obj:
-            try:
-                inv_obj = CacheInvalidation(key, repo_name)
-                Session().add(inv_obj)
-                if commit:
-                    Session().commit()
-            except Exception:
-                log.error(traceback.format_exc())
-                Session().rollback()
-        return inv_obj
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-        repo_name = key
-        repo_name = remove_suffix(repo_name, '_README')
-        repo_name = remove_suffix(repo_name, '_RSS')
-        repo_name = remove_suffix(repo_name, '_ATOM')
-
-        # adds instance prefix
-        key, _prefix, _org_key = cls._get_key(key)
-        inv = cls._get_or_create_key(key, repo_name)
-
-        if inv and inv.cache_active is False:
-            return inv
-
-    @classmethod
-    def set_invalidate(cls, key=None, repo_name=None):
-        """
-        Mark this Cache key for invalidation, either by key or whole
-        cache sets based on repo_name
-
-        :param key:
-        """
-        if key:
-            key, _prefix, _org_key = cls._get_key(key)
-            inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
-        elif repo_name:
-            inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s',
-                  len(inv_objs), key, repo_name)
-        try:
-            for inv_obj in inv_objs:
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = cls.get_by_key(key)
-        inv_obj.cache_active = True
-        Session().add(inv_obj)
-        Session().commit()
-
-    @classmethod
-    def get_cache_map(cls):
-
-        class cachemapdict(dict):
-
-            def __init__(self, *args, **kwargs):
-                fixkey = kwargs.get('fixkey')
-                if fixkey:
-                    del kwargs['fixkey']
-                self.fixkey = fixkey
-                super(cachemapdict, self).__init__(*args, **kwargs)
-
-            def __getattr__(self, name):
-                key = name
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                if key in self.__dict__:
-                    return self.__dict__[key]
-                else:
-                    return self[key]
-
-            def __getitem__(self, key):
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                try:
-                    return super(cachemapdict, self).__getitem__(key)
-                except KeyError:
-                    return
-
-        cache_map = cachemapdict(fixkey=True)
-        for obj in cls.query().all():
-            cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
-        return cache_map
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    def __json__(self):
-        return dict(
-          revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_5_2.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1962 +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.lib.dbmigrate.schema.db_1_5_2
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.5.X
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import logging
-import datetime
-import traceback
-import hashlib
-import time
-from collections import defaultdict
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix, remove_prefix
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    DEFAULT_USER = 'default'
-    DEFAULT_PERMISSIONS = [
-        'hg.register.manual_activate', 'hg.create.repository',
-        'hg.fork.repository', 'repository.read', 'group.read'
-    ]
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPv4Network(ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-
-    def __unicode__(self):
-        return u'<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
-
-        data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
-        )
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*p)
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def inject_ui(cls, repo, extras={}):
-        from kallithea.lib.vcs.backends.hg import MercurialRepository
-        from kallithea.lib.vcs.backends.git import GitRepository
-        required = (MercurialRepository, GitRepository)
-        if not isinstance(repo, required):
-            raise Exception('repo must be instance of %s' % (','.join(required)))
-
-        # inject ui extra param to log this action via push logger
-        for k, v in extras.items():
-            repo._repo.ui.setconfig('extras', k, v)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache
-        )
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id):
-        repo.locked = [user_id, time.time()]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        :type revisions: list
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(repo_name=self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @LazyProperty
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, cache_map=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-        log.debug('Getting cached instance of repo')
-
-        if cache_map:
-            # get using prefilled cache_map
-            invalidate_repo = cache_map[self.repo_name]
-            if invalidate_repo:
-                invalidate_repo = (None if invalidate_repo.cache_active
-                                   else invalidate_repo)
-        else:
-            # get from invalidate
-            invalidate_repo = self.invalidate
-
-        if invalidate_repo is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(invalidate_repo.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository', alias)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls, check_perms=False):
-        from webhelpers.html import literal as _literal
-        from kallithea.model.scm import ScmModel
-        groups = cls.query().all()
-        if check_perms:
-            #filter group user have access to, it's done
-            #magically inside ScmModel based on current user
-            groups = ScmModel().get_repos_groups(groups)
-        repo_groups = [('', '')]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        all_ = []
-
-        def _get_members(root_gr):
-            for r in root_gr.repositories:
-                all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    PERMS = [
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository Group no access')),
-        ('group.read', _('Repository Group read access')),
-        ('group.write', _('Repository Group write access')),
-        ('group.admin', _('Repository Group admin access')),
-
-        ('hg.admin', _('Kallithea Administrator')),
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-        ('hg.register.none', _('Register disabled')),
-        ('hg.register.manual_activate', _('Register new user with Kallithea '
-                                          'with manual activation')),
-
-        ('hg.register.auto_activate', _('Register new user with Kallithea '
-                                        'with auto activation')),
-    ]
-
-    # defines which permissions are more important higher the more important
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<user:%s => %s >' % (self.user, self.repository)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-
-    @property
-    def prefix(self):
-        _split = self.cache_key.split(self.cache_args, 1)
-        if _split and len(_split) == 2:
-            return _split[0]
-        return ''
-
-    @classmethod
-    def clear_cache(cls):
-        cls.query().delete()
-
-    @classmethod
-    def _get_key(cls, key):
-        """
-        Wrapper for generating a key, together with a prefix
-
-        :param key:
-        """
-        import kallithea
-        prefix = ''
-        org_key = key
-        iid = kallithea.CONFIG.get('instance_id')
-        if iid:
-            prefix = iid
-
-        return "%s%s" % (prefix, key), prefix, org_key
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.cache_key == key).scalar()
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        return cls.query().filter(cls.cache_args == repo_name).all()
-
-    @classmethod
-    def _get_or_create_key(cls, key, repo_name, commit=True):
-        inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
-        if not inv_obj:
-            try:
-                inv_obj = CacheInvalidation(key, repo_name)
-                Session().add(inv_obj)
-                if commit:
-                    Session().commit()
-            except Exception:
-                log.error(traceback.format_exc())
-                Session().rollback()
-        return inv_obj
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-        repo_name = key
-        repo_name = remove_suffix(repo_name, '_README')
-        repo_name = remove_suffix(repo_name, '_RSS')
-        repo_name = remove_suffix(repo_name, '_ATOM')
-
-        # adds instance prefix
-        key, _prefix, _org_key = cls._get_key(key)
-        inv = cls._get_or_create_key(key, repo_name)
-
-        if inv and inv.cache_active is False:
-            return inv
-
-    @classmethod
-    def set_invalidate(cls, key=None, repo_name=None):
-        """
-        Mark this Cache key for invalidation, either by key or whole
-        cache sets based on repo_name
-
-        :param key:
-        """
-        if key:
-            key, _prefix, _org_key = cls._get_key(key)
-            inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
-        elif repo_name:
-            inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s',
-                  len(inv_objs), key, repo_name)
-        try:
-            for inv_obj in inv_objs:
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = cls.get_by_key(key)
-        inv_obj.cache_active = True
-        Session().add(inv_obj)
-        Session().commit()
-
-    @classmethod
-    def get_cache_map(cls):
-
-        class cachemapdict(dict):
-
-            def __init__(self, *args, **kwargs):
-                fixkey = kwargs.get('fixkey')
-                if fixkey:
-                    del kwargs['fixkey']
-                self.fixkey = fixkey
-                super(cachemapdict, self).__init__(*args, **kwargs)
-
-            def __getattr__(self, name):
-                key = name
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                if key in self.__dict__:
-                    return self.__dict__[key]
-                else:
-                    return self[key]
-
-            def __getitem__(self, key):
-                if self.fixkey:
-                    key, _prefix, _org_key = cls._get_key(key)
-                try:
-                    return super(cachemapdict, self).__getitem__(key)
-                except KeyError:
-                    return
-
-        cache_map = cachemapdict(fixkey=True)
-        for obj in cls.query().all():
-            cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
-        return cache_map
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    def __json__(self):
-        return dict(
-          revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_6_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2041 +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.lib.dbmigrate.schema.db_1_6_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea <=1.5.X
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import logging
-import datetime
-import traceback
-import hashlib
-import time
-from collections import defaultdict
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_suffix, remove_prefix, time_to_datetime
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    DEFAULT_USER = 'default'
-    DEFAULT_PERMISSIONS = [
-        'hg.register.manual_activate', 'hg.create.repository',
-        'hg.fork.repository', 'repository.read', 'group.read'
-    ]
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-
-    def __unicode__(self):
-        return u'<userGroup(%s)>' % (self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
-
-        data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
-        )
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id):
-        repo.locked = [user_id, time.time()]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        :type revisions: list
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    @property
-    def invalidate(self):
-        return CacheInvalidation.invalidate(self.repo_name)
-
-    def set_invalidate(self):
-        """
-        set a cache for invalidation for this instance
-        """
-        CacheInvalidation.set_invalidate(repo_name=self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @LazyProperty
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, cache_map=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-        log.debug('Getting cached instance of repo')
-
-        if cache_map:
-            # get using prefilled cache_map
-            invalidate_repo = cache_map[self.repo_name]
-            if invalidate_repo:
-                invalidate_repo = (None if invalidate_repo.cache_active
-                                   else invalidate_repo)
-        else:
-            # get from invalidate
-            invalidate_repo = self.invalidate
-
-        if invalidate_repo is not None:
-            region_invalidate(_c, None, rn)
-            # update our cache
-            CacheInvalidation.set_valid(invalidate_repo.cache_key)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                  self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', '-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    PERMS = [
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('hg.admin', _('Kallithea Administrator')),
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-        ('hg.register.none', _('Register disabled')),
-        ('hg.register.manual_activate', _('Register new user with Kallithea '
-                                          'with manual activation')),
-
-        ('hg.register.auto_activate', _('Register new user with Kallithea '
-                                        'with auto activation')),
-    ]
-
-    # defines which permissions are more important higher the more important
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<user:%s => %s >' % (self.user, self.repository)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    # cache_id, not used
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    # cache_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__,
-                                  self.cache_id, self.cache_key)
-
-    def get_prefix(self):
-        """
-        Guess prefix that might have been used in _get_cache_key to generate self.cache_key .
-        Only used for informational purposes in repo_edit.html .
-        """
-        _split = self.cache_key.split(self.cache_args, 1)
-        if len(_split) == 2:
-            return _split[0]
-        return ''
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def _get_or_create_inv_obj(cls, key, repo_name, commit=True):
-        inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
-        if not inv_obj:
-            try:
-                inv_obj = CacheInvalidation(key, repo_name)
-                Session().add(inv_obj)
-                if commit:
-                    Session().commit()
-            except Exception:
-                log.error(traceback.format_exc())
-                Session().rollback()
-        return inv_obj
-
-    @classmethod
-    def invalidate(cls, key):
-        """
-        Returns Invalidation object if this given key should be invalidated
-        None otherwise. `cache_active = False` means that this cache
-        state is not valid and needs to be invalidated
-
-        :param key:
-        """
-        repo_name = key
-        repo_name = remove_suffix(repo_name, '_README')
-        repo_name = remove_suffix(repo_name, '_RSS')
-        repo_name = remove_suffix(repo_name, '_ATOM')
-
-        cache_key = cls._get_cache_key(key)
-        inv = cls._get_or_create_inv_obj(cache_key, repo_name)
-
-        if inv and not inv.cache_active:
-            return inv
-
-    @classmethod
-    def set_invalidate(cls, key=None, repo_name=None):
-        """
-        Mark this Cache key for invalidation, either by key or whole
-        cache sets based on repo_name
-
-        :param key:
-        """
-        invalidated_keys = []
-        if key:
-            assert not repo_name
-            cache_key = cls._get_cache_key(key)
-            inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all()
-        else:
-            assert repo_name
-            inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                inv_obj.cache_active = False
-                log.debug('marking %s key for invalidation based on key=%s,repo_name=%s',
-                  inv_obj, key, safe_str(repo_name))
-                invalidated_keys.append(inv_obj.cache_key)
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-        return invalidated_keys
-
-    @classmethod
-    def set_valid(cls, key):
-        """
-        Mark this cache key as active and currently cached
-
-        :param key:
-        """
-        inv_obj = cls.query().filter(cls.cache_key == key).scalar()
-        inv_obj.cache_active = True
-        Session().add(inv_obj)
-        Session().commit()
-
-    @classmethod
-    def get_cache_map(cls):
-
-        class cachemapdict(dict):
-
-            def __init__(self, *args, **kwargs):
-                self.fixkey = kwargs.pop('fixkey', False)
-                super(cachemapdict, self).__init__(*args, **kwargs)
-
-            def __getattr__(self, name):
-                cache_key = name
-                if self.fixkey:
-                    cache_key = cls._get_cache_key(name)
-                if cache_key in self.__dict__:
-                    return self.__dict__[cache_key]
-                else:
-                    return self[cache_key]
-
-            def __getitem__(self, name):
-                cache_key = name
-                if self.fixkey:
-                    cache_key = cls._get_cache_key(name)
-                try:
-                    return super(cachemapdict, self).__getitem__(cache_key)
-                except KeyError:
-                    return None
-
-        cache_map = cachemapdict(fixkey=True)
-        for obj in cls.query().all():
-            cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
-        return cache_map
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-          revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_7_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2223 +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.lib.dbmigrate.schema.db_1_7_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k='', v=''):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    DEFAULT_USER = 'default'
-
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, users_group_id, cache=False):
-        users_group = cls.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
-
-    def get_api_data(self):
-        users_group = self
-
-        data = dict(
-            users_group_id=users_group.users_group_id,
-            group_name=users_group.users_group_name,
-            active=users_group.users_group_active,
-        )
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', '-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    users_group = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8'}
-    )
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
-    stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
-    commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    # cache_id, not used
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    # cache_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
-    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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-          revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
-    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
-    read = Column('read', Boolean, default=False)
-    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
-
-    user = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
-    gist_expires = Column('gist_expires', Float(), 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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8'},
-    )
-    repository_id = Column('repository_id', String(250), primary_key=True)
-    repository_path = Column('repository_path', Text)
-    version = Column('version', Integer)
--- a/kallithea/lib/dbmigrate/schema/db_1_8_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2270 +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.lib.dbmigrate.schema.db_1_8_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX +  'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        if self.app_settings_name in ["ldap_active",
-                                      "default_repo_enable_statistics",
-                                      "default_repo_enable_locking",
-                                      "default_repo_private",
-                                      "default_repo_enable_downloads"]:
-            v = str2bool(v)
-        return v
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key)
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_ldap_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('ldap_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        mods += [('git', str(check_git_version()))]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': platform.platform(),
-            'kallithea_version': kallithea.__version__
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    DEFAULT_USER = 'default'
-
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            ldap_dn=user.ldap_dn,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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, assert_unicode=None), nullable=False, unique=True, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_0_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2330 +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.lib.dbmigrate.schema.db_2_0_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            return safe_str(self.__unicode__())
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': platform.platform(),
-            'kallithea_version': kallithea.__version__,
-            'git_version': str(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    DEFAULT_USER = 'default'
-
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    #for migration reasons, this is going to be later deleted
-    ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                     self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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, assert_unicode=None), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    # don't trigger lazy load for migrations
-    #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_0_1.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2331 +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.lib.dbmigrate.schema.db_2_0_1
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-from kallithea import DB_PREFIX
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
-            'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    DEFAULT_USER = 'default'
-
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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, assert_unicode=None), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        cs = self.get_changeset(self.landing_rev) or self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-    #TODO: create this field in migrations
-    #created_on = Column('created_on', 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')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_0_2.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2352 +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.lib.dbmigrate.schema.db_2_0_2
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
-            'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
-                                   self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    DEFAULT_USER = 'default'
-
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        return q.scalar()
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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, assert_unicode=None), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def landing_rev(self):
-        # always should return [rev_type, rev]
-        if self._landing_revision:
-            _rev_info = self._landing_revision.split(':')
-            if len(_rev_info) < 2:
-                _rev_info.insert(0, 'rev')
-            return [_rev_info[0], _rev_info[1]]
-        return [None, None]
-
-    @landing_rev.setter
-    def landing_rev(self, val):
-        if ':' not in val:
-            raise ValueError('value must be delimited with `:` and consist '
-                             'of <rev_type>:<rev>, got %s instead' % val)
-        self._landing_revision = val
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        _rev_type, _rev = self.landing_rev
-        cs = self.get_changeset(_rev)
-        if isinstance(cs, EmptyChangeset):
-            return self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-    def __json__(self):
-        return dict(landing_rev = self.landing_rev)
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2391 +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.lib.dbmigrate.schema.db_2_1_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
-            'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
-                                    self.ui_key, self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(255, convert_unicode=False, assert_unicode=None), 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)
-    name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False, fallback=True):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        res = q.scalar()
-
-        if fallback and not res:
-            #fallback to additional keys
-            _res = UserApiKeys.query()\
-                .filter(UserApiKeys.api_key == api_key)\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time()))\
-                .first()
-            if _res:
-                res = _res.user
-        return res
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserApiKeys(Base, BaseModel):
-    __tablename__ = 'user_api_keys'
-    __table_args__ = (
-        Index('uak_api_key_idx', 'api_key'),
-        UniqueConstraint('api_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), 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 = relationship('User', lazy='joined')
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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, assert_unicode=None), nullable=True, unique=False, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=True)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-    def __unicode__(self):
-        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
-                                            self.user_id, self.ip_addr)
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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, assert_unicode=None), 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, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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, assert_unicode=None), nullable=False, unique=True, default=None)
-    user_group_description = Column("user_group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    users_group_active = Column("users_group_active", 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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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, assert_unicode=None))
-    field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
-    field_type = Column("field_type", String(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
-
-    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, assert_unicode=None), nullable=False, unique=True, default=None)
-    clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
-    repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), 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)
-    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, assert_unicode=None), nullable=True, unique=None, default=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-    _landing_revision = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
-    _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def landing_rev(self):
-        # always should return [rev_type, rev]
-        if self._landing_revision:
-            _rev_info = self._landing_revision.split(':')
-            if len(_rev_info) < 2:
-                _rev_info.insert(0, 'rev')
-            return [_rev_info[0], _rev_info[1]]
-        return [None, None]
-
-    @landing_rev.setter
-    def landing_rev(self, val):
-        if ':' not in val:
-            raise ValueError('value must be delimited with `:` and consist '
-                             'of <rev_type>:<rev>, got %s instead' % val)
-        self._landing_revision = val
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        from urlparse import urlparse
-        import urllib
-        parsed_url = urlparse(h.canonical_url('home'))
-        default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
-        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
-        args = {
-           'user': '',
-           'pass': '',
-           'scheme': parsed_url.scheme,
-           'netloc': parsed_url.netloc,
-           'prefix': decoded_path,
-           'path': self.repo_name
-        }
-
-        args.update(override)
-        return default_clone_uri % args
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        _rev_type, _rev = self.landing_rev
-        cs = self.get_changeset(_rev)
-        if isinstance(cs, EmptyChangeset):
-            return self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-    def __json__(self):
-        return dict(landing_rev = self.landing_rev)
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), 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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        from webhelpers.html import literal as _literal
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-        sep = ' &raquo; '
-        _name = lambda k: _literal(sep.join(k))
-
-        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
-                              for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    # cache_args is a repo_name
-    cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    GIST_PRIVATE = u'private'
-
-    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_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)
-
-    owner = relationship('User')
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_2_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2448 +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.lib.dbmigrate.schema.db_2_2_0
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
-    get_clone_url
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    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_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)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
-            'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
-                                    self.ui_key, self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    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)
-    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)
-    _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)
-    #_user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-    #extra API keys
-    user_api_keys = relationship('UserApiKeys', cascade='all')
-
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def api_keys(self):
-        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
-        return [self.api_key] + [x.api_key for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    @hybrid_property
-    def user_data(self):
-        if not self._user_data:
-            return {}
-
-        try:
-            return json.loads(self._user_data)
-        except TypeError:
-            return {}
-
-    @user_data.setter
-    def user_data(self, val):
-        try:
-            self._user_data = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False, fallback=True):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        res = q.scalar()
-
-        if fallback and not res:
-            #fallback to additional keys
-            _res = UserApiKeys.query()\
-                .filter(UserApiKeys.api_key == api_key)\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time()))\
-                .first()
-            if _res:
-                res = _res.user
-        return res
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            api_keys=user.api_keys,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserApiKeys(Base, BaseModel):
-    __tablename__ = 'user_api_keys'
-    __table_args__ = (
-        Index('uak_api_key_idx', 'api_key'),
-        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
-        UniqueConstraint('api_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = relationship('User', lazy='joined')
-
-    @property
-    def expired(self):
-        if self.expires == -1:
-            return False
-        return time.time() > self.expires
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-    def __unicode__(self):
-        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
-                                            self.user_id, self.ip_addr)
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    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)
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
-    DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
-
-    STATE_CREATED = 'repo_state_created'
-    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)
-    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)
-    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)
-    _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)
-    _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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def landing_rev(self):
-        # always should return [rev_type, rev]
-        if self._landing_revision:
-            _rev_info = self._landing_revision.split(':')
-            if len(_rev_info) < 2:
-                _rev_info.insert(0, 'rev')
-            return [_rev_info[0], _rev_info[1]]
-        return [None, None]
-
-    @landing_rev.setter
-    def landing_rev(self, val):
-        if ':' not in val:
-            raise ValueError('value must be delimited with `:` and consist '
-                             'of <rev_type>:<rev>, got %s instead' % val)
-        self._landing_revision = val
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        qualified_home_url = h.canonical_url('home')
-
-        uri_tmpl = None
-        if 'uri_tmpl' in override:
-            uri_tmpl = override['uri_tmpl']
-            del override['uri_tmpl']
-
-        # we didn't override our tmpl from **overrides
-        if not uri_tmpl:
-            uri_tmpl = self.DEFAULT_CLONE_URI
-            try:
-                from pylons import tmpl_context as c
-                uri_tmpl = c.clone_uri_tmpl
-            except Exception:
-                # in any case if we call this outside of request context,
-                # ie, not having tmpl_context set up
-                pass
-
-        return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualified_home_url=qualified_home_url,
-                             repo_name=self.repo_name,
-                             repo_id=self.repo_id, **override)
-
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        _rev_type, _rev = self.landing_rev
-        cs = self.get_changeset(_rev)
-        if isinstance(cs, EmptyChangeset):
-            return self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-    def __json__(self):
-        return dict(landing_rev = self.landing_rev)
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def _generate_choice(cls, repo_group):
-        from webhelpers.html import literal as _literal
-        _name = lambda k: _literal(cls.SEP.join(k))
-        return repo_group.group_id, _name(repo_group.full_path_splitted)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-
-        repo_groups.extend([cls._generate_choice(x) for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
-        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.create.write_on_repogroup.true',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    # 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-        log.debug('for repo %s got %s invalidation objects', repo_name, inv_objs)
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    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_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)
-
-    owner = relationship('User')
-
-    def __repr__(self):
-        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/schema/db_2_2_3.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2494 +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.lib.dbmigrate.schema.db_2_2_3
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Database Models for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 08, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import os
-import time
-import logging
-import datetime
-import traceback
-import hashlib
-import collections
-import functools
-
-from sqlalchemy import *
-from sqlalchemy.ext.hybrid import hybrid_property
-from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
-from beaker.cache import cache_region, region_invalidate
-from webob.exc import HTTPNotFound
-
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea.lib.vcs import get_backend
-from kallithea.lib.vcs.utils.helpers import get_scm
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.lib.vcs.backends.base import EmptyChangeset
-
-from kallithea.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
-    safe_unicode, remove_prefix, time_to_datetime, aslist, Optional, safe_int, \
-    get_clone_url
-from kallithea.lib.compat import json
-from kallithea.lib.caching_query import FromCache
-
-from kallithea.model.meta import Base, Session
-
-URL_SEP = '/'
-log = logging.getLogger(__name__)
-
-from kallithea import DB_PREFIX
-
-#==============================================================================
-# BASE CLASSES
-#==============================================================================
-
-_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
-
-
-class BaseModel(object):
-    """
-    Base Model for all classess
-    """
-
-    @classmethod
-    def _get_keys(cls):
-        """return column names for this model """
-        return class_mapper(cls).c.keys()
-
-    def get_dict(self):
-        """
-        return dict with keys and values corresponding
-        to this model data """
-
-        d = {}
-        for k in self._get_keys():
-            d[k] = getattr(self, k)
-
-        # also use __json__() if present to get additional fields
-        _json_attr = getattr(self, '__json__', None)
-        if _json_attr:
-            # update with attributes from __json__
-            if callable(_json_attr):
-                _json_attr = _json_attr()
-            for k, val in _json_attr.iteritems():
-                d[k] = val
-        return d
-
-    def get_appstruct(self):
-        """return list with keys and values tuples corresponding
-        to this model data """
-
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
-
-    def populate_obj(self, populate_dict):
-        """populate model with data from given populate_dict"""
-
-        for k in self._get_keys():
-            if k in populate_dict:
-                setattr(self, k, populate_dict[k])
-
-    @classmethod
-    def query(cls):
-        return Session().query(cls)
-
-    @classmethod
-    def get(cls, id_):
-        if id_:
-            return cls.query().get(id_)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        try:
-            id_ = int(id_)
-        except (TypeError, ValueError):
-            raise HTTPNotFound
-
-        res = cls.query().get(id_)
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
-    def delete(cls, id_):
-        obj = cls.query().get(id_)
-        Session().delete(obj)
-
-    def __repr__(self):
-        if hasattr(self, '__unicode__'):
-            # python repr needs to return str
-            try:
-                return safe_str(self.__unicode__())
-            except UnicodeDecodeError:
-                pass
-        return '<DB:%s>' % (self.__class__.__name__)
-
-
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    SETTINGS_TYPES = {
-        'str': safe_str,
-        'int': safe_int,
-        'unicode': safe_unicode,
-        'bool': str2bool,
-        'list': functools.partial(aslist, sep=',')
-    }
-    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_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)
-
-    def __init__(self, key='', val='', type='unicode'):
-        self.app_settings_name = key
-        self.app_settings_value = val
-        self.app_settings_type = type
-
-    @validates('_app_settings_value')
-    def validate_settings_value(self, key, val):
-        assert type(val) == unicode
-        return val
-
-    @hybrid_property
-    def app_settings_value(self):
-        v = self._app_settings_value
-        _type = self.app_settings_type
-        converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
-        return converter(v)
-
-    @app_settings_value.setter
-    def app_settings_value(self, val):
-        """
-        Setter that will always make sure we use unicode in app_settings_value
-
-        :param val:
-        """
-        self._app_settings_value = safe_unicode(val)
-
-    @hybrid_property
-    def app_settings_type(self):
-        return self._app_settings_type
-
-    @app_settings_type.setter
-    def app_settings_type(self, val):
-        if val not in self.SETTINGS_TYPES:
-            raise Exception('type must be one of %s got %s'
-                            % (self.SETTINGS_TYPES.keys(), val))
-        self._app_settings_type = val
-
-    def __unicode__(self):
-        return u"<%s('%s:%s[%s]')>" % (
-            self.__class__.__name__,
-            self.app_settings_name, self.app_settings_value, self.app_settings_type
-        )
-
-    @classmethod
-    def get_by_name(cls, key):
-        return cls.query()\
-            .filter(cls.app_settings_name == key).scalar()
-
-    @classmethod
-    def get_by_name_or_create(cls, key, val='', type='unicode'):
-        res = cls.get_by_name(key)
-        if not res:
-            res = cls(key, val, type)
-        return res
-
-    @classmethod
-    def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
-        """
-        Creates or updates Kallithea setting. If updates is triggered it will only
-        update parameters that are explicityl set Optional instance will be skipped
-
-        :param key:
-        :param val:
-        :param type:
-        :return:
-        """
-        res = cls.get_by_name(key)
-        if not res:
-            val = Optional.extract(val)
-            type = Optional.extract(type)
-            res = cls(key, val, type)
-        else:
-            res.app_settings_name = key
-            if not isinstance(val, Optional):
-                # update if set
-                res.app_settings_value = val
-            if not isinstance(type, Optional):
-                # update if set
-                res.app_settings_type = type
-        return res
-
-    @classmethod
-    def get_app_settings(cls, cache=False):
-
-        ret = cls.query()
-
-        if cache:
-            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
-
-        if not ret:
-            raise Exception('Could not get application settings !')
-        settings = {}
-        for each in ret:
-            settings[each.app_settings_name] = \
-                each.app_settings_value
-
-        return settings
-
-    @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
-    def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('auth_')).all()
-        fd = {}
-        for row in ret:
-            fd.update({row.app_settings_name: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
-                .filter(cls.app_settings_name.startswith('default_')).all()
-        fd = {}
-        for row in ret:
-            key = row.app_settings_name
-            if strip_prefix:
-                key = remove_prefix(key, prefix='default_')
-            fd.update({key: row.app_settings_value})
-
-        return fd
-
-    @classmethod
-    def get_server_info(cls):
-        import pkg_resources
-        import platform
-        import kallithea
-        from kallithea.lib.utils import check_git_version
-        mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
-        info = {
-            'modules': sorted(mods, key=lambda k: k[0].lower()),
-            'py_version': platform.python_version(),
-            'platform': safe_unicode(platform.platform()),
-            'kallithea_version': kallithea.__version__,
-            'git_version': safe_unicode(check_git_version()),
-            'git_path': kallithea.CONFIG.get('git_path')
-        }
-        return info
-
-
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = (
-        UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-
-    HOOK_UPDATE = 'changegroup.update'
-    HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    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)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
-
-    @classmethod
-    def get_builtin_hooks(cls):
-        q = cls.query()
-        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        return q.all()
-
-    @classmethod
-    def get_custom_hooks(cls):
-        q = cls.query()
-        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
-        q = q.filter(cls.ui_section == 'hooks')
-        return q.all()
-
-    @classmethod
-    def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
-
-    @classmethod
-    def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
-        new_ui.ui_active = True
-        new_ui.ui_key = key
-        new_ui.ui_value = val
-
-        Session().add(new_ui)
-
-    def __repr__(self):
-        return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
-                                    self.ui_key, self.ui_value)
-
-
-class User(Base, BaseModel):
-    __tablename__ = 'users'
-    __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
-        Index('u_username_idx', 'username'),
-        Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    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)
-    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)
-    _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)
-    _user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
-
-    user_log = relationship('UserLog')
-    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relationship('Repository')
-    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-    followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
-
-    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
-
-    group_member = relationship('UserGroupMember', cascade='all')
-
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
-    # comments created by this user
-    user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
-    user_emails = relationship('UserEmailMap', cascade='all')
-    #extra API keys
-    user_api_keys = relationship('UserApiKeys', cascade='all')
-
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-    @property
-    def firstname(self):
-        # alias for future
-        return self.name
-
-    @property
-    def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
-        return [self.email] + [x.email for x in other]
-
-    @property
-    def api_keys(self):
-        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
-        return [self.api_key] + [x.api_key for x in other]
-
-    @property
-    def ip_addresses(self):
-        ret = UserIpMap.query().filter(UserIpMap.user == self).all()
-        return [x.ip_addr for x in ret]
-
-    @property
-    def username_and_name(self):
-        return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
-
-    @property
-    def full_name(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def full_name_or_username(self):
-        return ('%s %s' % (self.firstname, self.lastname)
-                if (self.firstname and self.lastname) else self.username)
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
-
-    @property
-    def short_contact(self):
-        return '%s %s' % (self.firstname, self.lastname)
-
-    @property
-    def is_admin(self):
-        return self.admin
-
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(user_id=self.user_id, api_key=self.api_key,
-                        username=self.username)
-
-    @hybrid_property
-    def user_data(self):
-        if not self._user_data:
-            return {}
-
-        try:
-            return json.loads(self._user_data)
-        except TypeError:
-            return {}
-
-    @user_data.setter
-    def user_data(self, val):
-        try:
-            self._user_data = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.user_id, self.username)
-
-    @classmethod
-    def get_by_username(cls, username, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
-        else:
-            q = cls.query().filter(cls.username == username)
-
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(username)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get_by_api_key(cls, api_key, cache=False, fallback=True):
-        q = cls.query().filter(cls.api_key == api_key)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_api_key_%s" % api_key))
-        res = q.scalar()
-
-        if fallback and not res:
-            #fallback to additional keys
-            _res = UserApiKeys.query()\
-                .filter(UserApiKeys.api_key == api_key)\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time()))\
-                .first()
-            if _res:
-                res = _res.user
-        return res
-
-    @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
-
-        if cache:
-            q = q.options(FromCache("sql_cache_short",
-                                    "get_email_key_%s" % email))
-
-        ret = q.scalar()
-        if ret is None:
-            q = UserEmailMap.query()
-            # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
-            q = q.options(joinedload(UserEmailMap.user))
-            if cache:
-                q = q.options(FromCache("sql_cache_short",
-                                        "get_email_map_key_%s" % email))
-            ret = getattr(q.scalar(), 'user', None)
-
-        return ret
-
-    @classmethod
-    def get_from_cs_author(cls, author):
-        """
-        Tries to get User objects out of commit author string
-
-        :param author:
-        """
-        from kallithea.lib.helpers import email, author_name
-        # Valid email in the attribute passed, see if they're in the system
-        _email = email(author)
-        if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
-            if user:
-                return user
-        # Maybe we can match by username?
-        _author = author_name(author)
-        user = cls.get_by_username(_author, case_insensitive=True)
-        if user:
-            return user
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-        self.last_login = datetime.datetime.now()
-        Session().add(self)
-        log.debug('updated user %s lastlogin', self.username)
-
-    @classmethod
-    def get_first_admin(cls):
-        user = User.query().filter(User.admin == True).first()
-        if user is None:
-            raise Exception('Missing administrative account!')
-        return user
-
-    @classmethod
-    def get_default_user(cls, cache=False):
-        user = User.get_by_username(User.DEFAULT_USER, cache=cache)
-        if user is None:
-            raise Exception('Missing default account!')
-        return user
-
-    def get_api_data(self):
-        """
-        Common function for generating user related data for API
-        """
-        user = self
-        data = dict(
-            user_id=user.user_id,
-            username=user.username,
-            firstname=user.name,
-            lastname=user.lastname,
-            email=user.email,
-            emails=user.emails,
-            api_key=user.api_key,
-            api_keys=user.api_keys,
-            active=user.active,
-            admin=user.admin,
-            extern_type=user.extern_type,
-            extern_name=user.extern_name,
-            last_login=user.last_login,
-            ip_addresses=user.ip_addresses
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-            full_name=self.full_name,
-            full_name_or_username=self.full_name_or_username,
-            short_contact=self.short_contact,
-            full_contact=self.full_contact
-        )
-        data.update(self.get_api_data())
-        return data
-
-
-class UserApiKeys(Base, BaseModel):
-    __tablename__ = 'user_api_keys'
-    __table_args__ = (
-        Index('uak_api_key_idx', 'api_key'),
-        Index('uak_api_key_expires_idx', 'api_key', 'expires'),
-        UniqueConstraint('api_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = relationship('User', lazy='joined')
-
-    @property
-    def expired(self):
-        if self.expires == -1:
-            return False
-        return time.time() > self.expires
-
-
-class UserEmailMap(Base, BaseModel):
-    __tablename__ = 'user_email_map'
-    __table_args__ = (
-        Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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 = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    user = relationship('User', lazy='joined')
-
-    @validates('_email')
-    def validate_email(self, key, email):
-        # check if this email is not main one
-        main_email = Session().query(User).filter(User.email == email).scalar()
-        if main_email is not None:
-            raise AttributeError('email %s is present is user table' % email)
-        return email
-
-    @hybrid_property
-    def email(self):
-        return self._email
-
-    @email.setter
-    def email(self, val):
-        self._email = val.lower() if val else None
-
-
-class UserIpMap(Base, BaseModel):
-    __tablename__ = 'user_ip_map'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    __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)
-    user = relationship('User', lazy='joined')
-
-    @classmethod
-    def _get_ip_range(cls, ip_addr):
-        from kallithea.lib import ipaddr
-        net = ipaddr.IPNetwork(address=ip_addr)
-        return [str(net.network), str(net.broadcast)]
-
-    def __json__(self):
-        return dict(
-          ip_addr=self.ip_addr,
-          ip_range=self._get_ip_range(self.ip_addr)
-        )
-
-    def __unicode__(self):
-        return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
-                                            self.user_id, self.ip_addr)
-
-class UserLog(Base, BaseModel):
-    __tablename__ = 'user_logs'
-    __table_args__ = (
-        {'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)
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.repository_name,
-                                      self.action)
-
-    @property
-    def action_as_day(self):
-        return datetime.date(*self.action_date.timetuple()[:3])
-
-    user = relationship('User')
-    repository = relationship('Repository', cascade='')
-
-
-class UserGroup(Base, BaseModel):
-    __tablename__ = 'users_groups'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    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)
-    _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
-
-    members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
-    users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
-    users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
-    user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
-
-    user = relationship('User')
-
-    @hybrid_property
-    def group_data(self):
-        if not self._group_data:
-            return {}
-
-        try:
-            return json.loads(self._group_data)
-        except TypeError:
-            return {}
-
-    @group_data.setter
-    def group_data(self, val):
-        try:
-            self._group_data = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
-                                      self.users_group_id,
-                                      self.users_group_name)
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False,
-                          case_insensitive=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
-        else:
-            q = cls.query().filter(cls.users_group_name == group_name)
-        if cache:
-            q = q.options(FromCache(
-                            "sql_cache_short",
-                            "get_user_%s" % _hash_key(group_name)
-                          )
-            )
-        return q.scalar()
-
-    @classmethod
-    def get(cls, user_group_id, cache=False):
-        user_group = cls.query()
-        if cache:
-            user_group = user_group.options(FromCache("sql_cache_short",
-                                    "get_users_group_%s" % user_group_id))
-        return user_group.get(user_group_id)
-
-    def get_api_data(self, with_members=True):
-        user_group = self
-
-        data = dict(
-            users_group_id=user_group.users_group_id,
-            group_name=user_group.users_group_name,
-            group_description=user_group.user_group_description,
-            active=user_group.users_group_active,
-            owner=user_group.user.username,
-        )
-        if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
-
-        return data
-
-
-class UserGroupMember(Base, BaseModel):
-    __tablename__ = 'users_groups_members'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User', lazy='joined')
-    users_group = relationship('UserGroup')
-
-    def __init__(self, gr_id='', u_id=''):
-        self.users_group_id = gr_id
-        self.user_id = u_id
-
-
-class RepositoryField(Base, BaseModel):
-    __tablename__ = 'repositories_fields'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    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(256), nullable=False, unique=None)
-    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-
-    repository = relationship('Repository')
-
-    @property
-    def field_key_prefixed(self):
-        return 'ex_%s' % self.field_key
-
-    @classmethod
-    def un_prefix_key(cls, key):
-        if key.startswith(cls.PREFIX):
-            return key[len(cls.PREFIX):]
-        return key
-
-    @classmethod
-    def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
-                .filter(cls.field_key == key).scalar()
-        return row
-
-
-class Repository(Base, BaseModel):
-    __tablename__ = 'repositories'
-    __table_args__ = (
-        UniqueConstraint('repo_name'),
-        Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
-    DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
-
-    STATE_CREATED = 'repo_state_created'
-    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)
-    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)
-    _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)
-    _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)
-
-    user = relationship('User')
-    fork = relationship('Repository', remote_side=repo_id)
-    group = relationship('RepoGroup')
-    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
-    stats = relationship('Statistics', cascade='all', uselist=False)
-
-    followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
-                             cascade='all')
-    extra_fields = relationship('RepositoryField',
-                                cascade="all, delete, delete-orphan")
-
-    logs = relationship('UserLog')
-    comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
-
-    pull_requests_org = relationship('PullRequest',
-                    primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    pull_requests_other = relationship('PullRequest',
-                    primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
-                    cascade="all, delete, delete-orphan")
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
-                                   safe_unicode(self.repo_name))
-
-    @hybrid_property
-    def landing_rev(self):
-        # always should return [rev_type, rev]
-        if self._landing_revision:
-            _rev_info = self._landing_revision.split(':')
-            if len(_rev_info) < 2:
-                _rev_info.insert(0, 'rev')
-            return [_rev_info[0], _rev_info[1]]
-        return [None, None]
-
-    @landing_rev.setter
-    def landing_rev(self, val):
-        if ':' not in val:
-            raise ValueError('value must be delimited with `:` and consist '
-                             'of <rev_type>:<rev>, got %s instead' % val)
-        self._landing_revision = val
-
-    @hybrid_property
-    def locked(self):
-        # always should return [user_id, timelocked]
-        if self._locked:
-            _lock_info = self._locked.split(':')
-            return int(_lock_info[0]), _lock_info[1]
-        return [None, None]
-
-    @locked.setter
-    def locked(self, val):
-        if val and isinstance(val, (list, tuple)):
-            self._locked = ':'.join(map(str, val))
-        else:
-            self._locked = None
-
-    @hybrid_property
-    def changeset_cache(self):
-        from kallithea.lib.vcs.backends.base import EmptyChangeset
-        dummy = EmptyChangeset().__json__()
-        if not self._changeset_cache:
-            return dummy
-        try:
-            return json.loads(self._changeset_cache)
-        except TypeError:
-            return dummy
-
-    @changeset_cache.setter
-    def changeset_cache(self, val):
-        try:
-            self._changeset_cache = json.dumps(val)
-        except Exception:
-            log.error(traceback.format_exc())
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def normalize_repo_name(cls, repo_name):
-        """
-        Normalizes os specific repo_name to the format internally stored inside
-        dabatabase using URL_SEP
-
-        :param cls:
-        :param repo_name:
-        """
-        return cls.url_sep().join(repo_name.split(os.sep))
-
-    @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
-                .options(joinedload(Repository.group))
-        return q.scalar()
-
-    @classmethod
-    def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
-        repo_name = cls.normalize_repo_name(repo_name)
-        return cls.get_by_repo_name(repo_name.strip(URL_SEP))
-
-    @classmethod
-    def get_repo_forks(cls, repo_id):
-        return cls.query().filter(Repository.fork_id == repo_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all repos are stored
-
-        :param cls:
-        """
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == cls.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def forks(self):
-        """
-        Return forks of this repo
-        """
-        return Repository.get_repo_forks(self.repo_id)
-
-    @property
-    def parent(self):
-        """
-        Returns fork parent
-        """
-        return self.fork
-
-    @property
-    def just_name(self):
-        return self.repo_name.split(Repository.url_sep())[-1]
-
-    @property
-    def groups_with_parents(self):
-        groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
-        return groups
-
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
-    @LazyProperty
-    def repo_path(self):
-        """
-        Returns base full path for that repository means where it actually
-        exists on a filesystem
-        """
-        q = Session().query(Ui).filter(Ui.ui_key ==
-                                              Repository.url_sep())
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return q.one().ui_value
-
-    @property
-    def repo_full_path(self):
-        p = [self.repo_path]
-        # we need to split the name by / since this is how we store the
-        # names in the database, but that eventually needs to be converted
-        # into a valid system path
-        p += self.repo_name.split(Repository.url_sep())
-        return os.path.join(*map(safe_unicode, p))
-
-    @property
-    def cache_keys(self):
-        """
-        Returns associated cache keys for that repo
-        """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
-            .all()
-
-    def get_new_name(self, repo_name):
-        """
-        returns new full repository name based on assigned group and new new
-
-        :param group_name:
-        """
-        path_prefix = self.group.full_path_splitted if self.group else []
-        return Repository.url_sep().join(path_prefix + [repo_name])
-
-    @property
-    def _ui(self):
-        """
-        Creates an db based ui object for this repository
-        """
-        from kallithea.lib.utils import make_ui
-        return make_ui('db', clear_session=False)
-
-    @classmethod
-    def is_valid(cls, repo_name):
-        """
-        returns True if given repo name is a valid filesystem repository
-
-        :param cls:
-        :param repo_name:
-        """
-        from kallithea.lib.utils import is_valid_repo
-
-        return is_valid_repo(repo_name, cls.base_path())
-
-    def get_api_data(self):
-        """
-        Common function for generating repo api data
-
-        """
-        repo = self
-        data = dict(
-            repo_id=repo.repo_id,
-            repo_name=repo.repo_name,
-            repo_type=repo.repo_type,
-            clone_uri=repo.clone_uri,
-            private=repo.private,
-            created_on=repo.created_on,
-            description=repo.description,
-            landing_rev=repo.landing_rev,
-            owner=repo.user.username,
-            fork_of=repo.fork.repo_name if repo.fork else None,
-            enable_statistics=repo.enable_statistics,
-            enable_locking=repo.enable_locking,
-            enable_downloads=repo.enable_downloads,
-            last_changeset=repo.changeset_cache,
-            locked_by=User.get(self.locked[0]).get_api_data() \
-                if self.locked[0] else None,
-            locked_date=time_to_datetime(self.locked[1]) \
-                if self.locked[1] else None
-        )
-        rc_config = Setting.get_app_settings()
-        repository_fields = str2bool(rc_config.get('repository_fields'))
-        if repository_fields:
-            for f in self.extra_fields:
-                data[f.field_key_prefixed] = f.field_value
-
-        return data
-
-    @classmethod
-    def lock(cls, repo, user_id, lock_time=None):
-        if not lock_time:
-            lock_time = time.time()
-        repo.locked = [user_id, lock_time]
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def unlock(cls, repo):
-        repo.locked = None
-        Session().add(repo)
-        Session().commit()
-
-    @classmethod
-    def getlock(cls, repo):
-        return repo.locked
-
-    @property
-    def last_db_change(self):
-        return self.updated_on
-
-    def clone_url(self, **override):
-        import kallithea.lib.helpers as h
-        qualified_home_url = h.canonical_url('home')
-
-        uri_tmpl = None
-        if 'with_id' in override:
-            uri_tmpl = self.DEFAULT_CLONE_URI_ID
-            del override['with_id']
-
-        if 'uri_tmpl' in override:
-            uri_tmpl = override['uri_tmpl']
-            del override['uri_tmpl']
-
-        # we didn't override our tmpl from **overrides
-        if not uri_tmpl:
-            uri_tmpl = self.DEFAULT_CLONE_URI
-            try:
-                from pylons import tmpl_context as c
-                uri_tmpl = c.clone_uri_tmpl
-            except Exception:
-                # in any case if we call this outside of request context,
-                # ie, not having tmpl_context set up
-                pass
-
-        return get_clone_url(uri_tmpl=uri_tmpl,
-                             qualified_home_url=qualified_home_url,
-                             repo_name=self.repo_name,
-                             repo_id=self.repo_id, **override)
-
-    def set_state(self, state):
-        self.repo_state = state
-        Session().add(self)
-    #==========================================================================
-    # SCM PROPERTIES
-    #==========================================================================
-
-    def get_changeset(self, rev=None):
-        return get_changeset_safe(self.scm_instance, rev)
-
-    def get_landing_changeset(self):
-        """
-        Returns landing changeset, or if that doesn't exist returns the tip
-        """
-        _rev_type, _rev = self.landing_rev
-        cs = self.get_changeset(_rev)
-        if isinstance(cs, EmptyChangeset):
-            return self.get_changeset()
-        return cs
-
-    def update_changeset_cache(self, cs_cache=None):
-        """
-        Update cache of last changeset for repository, keys should be::
-
-            short_id
-            raw_id
-            revision
-            message
-            date
-            author
-
-        :param cs_cache:
-        """
-        from kallithea.lib.vcs.backends.base import BaseChangeset
-        if cs_cache is None:
-            cs_cache = EmptyChangeset()
-            # use no-cache version here
-            scm_repo = self.scm_instance_no_cache()
-            if scm_repo:
-                cs_cache = scm_repo.get_changeset()
-
-        if isinstance(cs_cache, BaseChangeset):
-            cs_cache = cs_cache.__json__()
-
-        if (cs_cache != self.changeset_cache or not self.changeset_cache):
-            _default = datetime.datetime.fromtimestamp(0)
-            last_change = cs_cache.get('date') or _default
-            log.debug('updated repo %s with new cs cache %s',
-                      self.repo_name, cs_cache)
-            self.updated_on = last_change
-            self.changeset_cache = cs_cache
-            Session().add(self)
-            Session().commit()
-        else:
-            log.debug('Skipping repo:%s already with latest changes',
-                      self.repo_name)
-
-    @property
-    def tip(self):
-        return self.get_changeset('tip')
-
-    @property
-    def author(self):
-        return self.tip.author
-
-    @property
-    def last_change(self):
-        return self.scm_instance.last_change
-
-    def get_comments(self, revisions=None):
-        """
-        Returns comments for this repository grouped by revisions
-
-        :param revisions: filter query by revisions only
-        """
-        cmts = ChangesetComment.query()\
-            .filter(ChangesetComment.repo == self)
-        if revisions:
-            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
-        grouped = collections.defaultdict(list)
-        for cmt in cmts.all():
-            grouped[cmt.revision].append(cmt)
-        return grouped
-
-    def statuses(self, revisions=None):
-        """
-        Returns statuses for this repository
-
-        :param revisions: list of revisions to get statuses for
-        """
-
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)
-        if revisions:
-            statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
-        grouped = {}
-
-        #maybe we have open new pullrequest without a status ?
-        stat = ChangesetStatus.STATUS_UNDER_REVIEW
-        status_lbl = ChangesetStatus.get_status_lbl(stat)
-        for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
-            for rev in pr.revisions:
-                pr_id = pr.pull_request_id
-                pr_repo = pr.other_repo.repo_name
-                grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
-
-        for stat in statuses.all():
-            pr_id = pr_repo = None
-            if stat.pull_request:
-                pr_id = stat.pull_request.pull_request_id
-                pr_repo = stat.pull_request.other_repo.repo_name
-            grouped[stat.revision] = [str(stat.status), stat.status_lbl,
-                                      pr_id, pr_repo]
-        return grouped
-
-    def _repo_size(self):
-        from kallithea.lib import helpers as h
-        log.debug('calculating repository size...')
-        return h.format_byte_size(self.scm_instance.size)
-
-    #==========================================================================
-    # SCM CACHE INSTANCE
-    #==========================================================================
-
-    def set_invalidate(self):
-        """
-        Mark caches of this repo as invalid.
-        """
-        CacheInvalidation.set_invalidate(self.repo_name)
-
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
-
-    @property
-    def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
-
-    def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
-        rn = self.repo_name
-
-        valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
-        if not valid:
-            log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
-        else:
-            log.debug('Getting obj for %s from cache', rn)
-        return _c(rn)
-
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-        try:
-            alias = get_scm(repo_full_path)[0]
-            log.debug('Creating instance of %s repository from %s',
-                      alias, repo_full_path)
-            backend = get_backend(alias)
-        except VCSError:
-            log.error(traceback.format_exc())
-            log.error('Perhaps this repository is in db and not in '
-                      'filesystem run rescan repositories with '
-                      '"destroy old data " option from admin panel')
-            return
-
-        if alias == 'hg':
-
-            repo = backend(safe_str(repo_full_path), create=False,
-                           baseui=self._ui)
-        else:
-            repo = backend(repo_full_path, create=False)
-
-        return repo
-
-    def __json__(self):
-        return dict(landing_rev = self.landing_rev)
-
-class RepoGroup(Base, BaseModel):
-    __tablename__ = 'groups'
-    __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        CheckConstraint('group_id != group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    __mapper_args__ = {'order_by': 'group_name'}
-
-    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)
-
-    repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
-    users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
-    parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
-
-    def __init__(self, group_name='', parent_group=None):
-        self.group_name = group_name
-        self.parent_group = parent_group
-
-    def __unicode__(self):
-        return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
-                                      self.group_name)
-
-    @classmethod
-    def _generate_choice(cls, repo_group):
-        from webhelpers.html import literal as _literal
-        _name = lambda k: _literal(cls.SEP.join(k))
-        return repo_group.group_id, _name(repo_group.full_path_splitted)
-
-    @classmethod
-    def groups_choices(cls, groups=None, show_empty_group=True):
-        if not groups:
-            groups = cls.query().all()
-
-        repo_groups = []
-        if show_empty_group:
-            repo_groups = [('-1', u'-- %s --' % _('top level'))]
-
-        repo_groups.extend([cls._generate_choice(x) for x in groups])
-
-        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(cls.SEP)[0])
-        return repo_groups
-
-    @classmethod
-    def url_sep(cls):
-        return URL_SEP
-
-    @classmethod
-    def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
-        if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
-        else:
-            gr = cls.query()\
-                .filter(cls.group_name == group_name)
-        if cache:
-            gr = gr.options(FromCache(
-                            "sql_cache_short",
-                            "get_group_%s" % _hash_key(group_name)
-                            )
-            )
-        return gr.scalar()
-
-    @property
-    def parents(self):
-        parents_recursion_limit = 5
-        groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error('group nested more than %s',
-                          parents_recursion_limit)
-                break
-
-            groups.insert(0, gr)
-        return groups
-
-    @property
-    def children(self):
-        return RepoGroup.query().filter(RepoGroup.parent_group == self)
-
-    @property
-    def name(self):
-        return self.group_name.split(RepoGroup.url_sep())[-1]
-
-    @property
-    def full_path(self):
-        return self.group_name
-
-    @property
-    def full_path_splitted(self):
-        return self.group_name.split(RepoGroup.url_sep())
-
-    @property
-    def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
-
-    @property
-    def repositories_recursive_count(self):
-        cnt = self.repositories.count()
-
-        def children_count(group):
-            cnt = 0
-            for child in group.children:
-                cnt += child.repositories.count()
-                cnt += children_count(child)
-            return cnt
-
-        return cnt + children_count(self)
-
-    def _recursive_objects(self, include_repos=True):
-        all_ = []
-
-        def _get_members(root_gr):
-            if include_repos:
-                for r in root_gr.repositories:
-                    all_.append(r)
-            childs = root_gr.children.all()
-            if childs:
-                for gr in childs:
-                    all_.append(gr)
-                    _get_members(gr)
-
-        _get_members(self)
-        return [self] + all_
-
-    def recursive_groups_and_repos(self):
-        """
-        Recursive return all groups, with repositories in those groups
-        """
-        return self._recursive_objects()
-
-    def recursive_groups(self):
-        """
-        Returns all children groups for this group including children of children
-        """
-        return self._recursive_objects(include_repos=False)
-
-    def get_new_name(self, group_name):
-        """
-        returns new full group name based on parent and new name
-
-        :param group_name:
-        """
-        path_prefix = (self.parent_group.full_path_splitted if
-                       self.parent_group else [])
-        return RepoGroup.url_sep().join(path_prefix + [group_name])
-
-    def get_api_data(self):
-        """
-        Common function for generating api data
-
-        """
-        group = self
-        data = dict(
-            group_id=group.group_id,
-            group_name=group.group_name,
-            group_description=group.group_description,
-            parent_group=group.parent_group.group_name if group.parent_group else None,
-            repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
-        )
-        return data
-
-
-class Permission(Base, BaseModel):
-    __tablename__ = 'permissions'
-    __table_args__ = (
-        Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    PERMS = [
-        ('hg.admin', _('Kallithea Administrator')),
-
-        ('repository.none', _('Repository no access')),
-        ('repository.read', _('Repository read access')),
-        ('repository.write', _('Repository write access')),
-        ('repository.admin', _('Repository admin access')),
-
-        ('group.none', _('Repository group no access')),
-        ('group.read', _('Repository group read access')),
-        ('group.write', _('Repository group write access')),
-        ('group.admin', _('Repository group admin access')),
-
-        ('usergroup.none', _('User group no access')),
-        ('usergroup.read', _('User group read access')),
-        ('usergroup.write', _('User group write access')),
-        ('usergroup.admin', _('User group admin access')),
-
-        ('hg.repogroup.create.false', _('Repository Group creation disabled')),
-        ('hg.repogroup.create.true', _('Repository Group creation enabled')),
-
-        ('hg.usergroup.create.false', _('User Group creation disabled')),
-        ('hg.usergroup.create.true', _('User Group creation enabled')),
-
-        ('hg.create.none', _('Repository creation disabled')),
-        ('hg.create.repository', _('Repository creation enabled')),
-        ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
-        ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
-
-        ('hg.fork.none', _('Repository forking disabled')),
-        ('hg.fork.repository', _('Repository forking enabled')),
-
-        ('hg.register.none', _('Registration disabled')),
-        ('hg.register.manual_activate', _('User Registration with manual account activation')),
-        ('hg.register.auto_activate', _('User Registration with automatic account activation')),
-
-        ('hg.extern_activate.manual', _('Manual activation of external account')),
-        ('hg.extern_activate.auto', _('Automatic activation of external account')),
-
-    ]
-
-    #definition of system default permissions for DEFAULT user
-    DEFAULT_USER_PERMISSIONS = [
-        'repository.read',
-        'group.read',
-        'usergroup.read',
-        'hg.create.repository',
-        'hg.create.write_on_repogroup.true',
-        'hg.fork.repository',
-        'hg.register.manual_activate',
-        'hg.extern_activate.auto',
-    ]
-
-    # defines which permissions are more important higher the more important
-    # Weight defines which permissions are more important.
-    # The higher number the more important.
-    PERM_WEIGHTS = {
-        'repository.none': 0,
-        'repository.read': 1,
-        'repository.write': 3,
-        'repository.admin': 4,
-
-        'group.none': 0,
-        'group.read': 1,
-        'group.write': 3,
-        'group.admin': 4,
-
-        'usergroup.none': 0,
-        'usergroup.read': 1,
-        'usergroup.write': 3,
-        'usergroup.admin': 4,
-        'hg.repogroup.create.false': 0,
-        'hg.repogroup.create.true': 1,
-
-        'hg.usergroup.create.false': 0,
-        'hg.usergroup.create.true': 1,
-
-        'hg.fork.none': 0,
-        'hg.fork.repository': 1,
-        'hg.create.none': 0,
-        '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)
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__, self.permission_id, self.permission_name
-        )
-
-    @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.permission_name == key).scalar()
-
-    @classmethod
-    def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserRepoGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-    @classmethod
-    def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
-         .filter(UserUserGroupToPerm.user_id == default_user_id)
-
-        return q.all()
-
-
-class UserRepoToPerm(Base, BaseModel):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'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)
-
-    user = relationship('User')
-    repository = relationship('Repository')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository, permission):
-        n = cls()
-        n.user = user
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.repository)
-
-
-class UserUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'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 = relationship('User')
-    user_group = relationship('UserGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, user_group, permission):
-        n = cls()
-        n.user = user
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.user_group)
-
-
-class UserToPerm(Base, BaseModel):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'permission_id'),
-        {'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 = relationship('User')
-    permission = relationship('Permission', lazy='joined')
-
-    def __unicode__(self):
-        return u'<%s => %s >' % (self.user, self.permission)
-
-
-class UserGroupRepoToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_to_perm'
-    __table_args__ = (
-        UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    repository = relationship('Repository')
-
-    @classmethod
-    def create(cls, users_group, repository, permission):
-        n = cls()
-        n.users_group = users_group
-        n.repository = repository
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
-
-
-class UserGroupUserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_group_user_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        CheckConstraint('target_user_group_id != user_group_id'),
-        {'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)
-
-    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')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, target_user_group, user_group, permission):
-        n = cls()
-        n.target_user_group = target_user_group
-        n.user_group = user_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-    def __unicode__(self):
-        return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
-
-
-class UserGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'permission_id',),
-        {'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 = relationship('UserGroup')
-    permission = relationship('Permission')
-
-
-class UserRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'user_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-
-    user = relationship('User')
-    group = relationship('RepoGroup')
-    permission = relationship('Permission')
-
-    @classmethod
-    def create(cls, user, repository_group, permission):
-        n = cls()
-        n.user = user
-        n.group = repository_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-
-class UserGroupRepoGroupToPerm(Base, BaseModel):
-    __tablename__ = 'users_group_repo_group_to_perm'
-    __table_args__ = (
-        UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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 = relationship('UserGroup')
-    permission = relationship('Permission')
-    group = relationship('RepoGroup')
-
-    @classmethod
-    def create(cls, user_group, repository_group, permission):
-        n = cls()
-        n.users_group = user_group
-        n.group = repository_group
-        n.permission = permission
-        Session().add(n)
-        return n
-
-
-class Statistics(Base, BaseModel):
-    __tablename__ = 'statistics'
-    __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'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
-
-    repository = relationship('Repository', single_parent=True)
-
-
-class UserFollowing(Base, BaseModel):
-    __tablename__ = 'user_followings'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
-
-    user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
-
-    @classmethod
-    def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (
-        UniqueConstraint('cache_key'),
-        Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         '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_key as created by _get_cache_key
-    cache_key = Column("cache_key", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    # 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)
-
-    def __init__(self, cache_key, repo_name=''):
-        self.cache_key = cache_key
-        self.cache_args = repo_name
-        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)
-
-    def _cache_key_partition(self):
-        prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
-        return prefix, repo_name, suffix
-
-    def get_prefix(self):
-        """
-        get prefix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[0]
-
-    def get_suffix(self):
-        """
-        get suffix that might have been used in _get_cache_key to
-        generate self.cache_key. Only used for informational purposes
-        in repo_edit.html.
-        """
-        # prefix, repo_name, suffix
-        return self._cache_key_partition()[2]
-
-    @classmethod
-    def clear_cache(cls):
-        """
-        Delete all cache keys from database.
-        Should only be run when all instances are down and all entries thus stale.
-        """
-        cls.query().delete()
-        Session().commit()
-
-    @classmethod
-    def _get_cache_key(cls, key):
-        """
-        Wrapper for generating a unique cache key for this instance and "key".
-        key must / will start with a repo_name which will be stored in .cache_args .
-        """
-        import kallithea
-        prefix = kallithea.CONFIG.get('instance_id', '')
-        return "%s%s" % (prefix, key)
-
-    @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
-        """
-        Mark all caches of a repo as invalid in the database.
-        """
-        inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
-        log.debug('for repo %s got %s invalidation objects',
-                  safe_str(repo_name), inv_objs)
-        try:
-            for inv_obj in inv_objs:
-                log.debug('marking %s key for invalidation based on repo_name=%s',
-                          inv_obj, safe_str(repo_name))
-                if delete:
-                    Session().delete(inv_obj)
-                else:
-                    inv_obj.cache_active = False
-                    Session().add(inv_obj)
-            Session().commit()
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-
-    @classmethod
-    def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
-        """
-        Mark this cache key as active and currently cached.
-        Return True if the existing cache registration still was valid.
-        Return False to indicate that it had been invalidated and caches should be refreshed.
-        """
-
-        key = (repo_name + '_' + kind) if kind else repo_name
-        cache_key = cls._get_cache_key(key)
-
-        if valid_cache_keys and cache_key in valid_cache_keys:
-            return True
-
-        try:
-            inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
-            if not inv_obj:
-                inv_obj = CacheInvalidation(cache_key, repo_name)
-            was_valid = inv_obj.cache_active
-            inv_obj.cache_active = True
-            Session().add(inv_obj)
-            Session().commit()
-            return was_valid
-        except Exception:
-            log.error(traceback.format_exc())
-            Session().rollback()
-            return False
-
-    @classmethod
-    def get_valid_cache_keys(cls):
-        """
-        Return opaque object with information of which caches still are valid
-        and can be used without checking for invalidation.
-        """
-        return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
-
-
-class ChangesetComment(Base, BaseModel):
-    __tablename__ = 'changeset_comments'
-    __table_args__ = (
-        Index('cc_revision_idx', 'revision'),
-        {'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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
-
-class ChangesetStatus(Base, BaseModel):
-    __tablename__ = 'changeset_statuses'
-    __table_args__ = (
-        Index('cs_revision_idx', 'revision'),
-        Index('cs_version_idx', 'version'),
-        UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
-    STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
-    STATUS_UNDER_REVIEW = 'under_review'
-
-    STATUSES = [
-        (STATUS_NOT_REVIEWED, _("Not Reviewed")),  # (no icon) and default
-        (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (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)
-
-    author = relationship('User', lazy='joined')
-    repo = relationship('Repository')
-    comment = relationship('ChangesetComment', lazy='joined')
-    pull_request = relationship('PullRequest', lazy='joined')
-
-    def __unicode__(self):
-        return u"<%s('%s:%s')>" % (
-            self.__class__.__name__,
-            self.status, self.author
-        )
-
-    @classmethod
-    def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
-
-    @property
-    def status_lbl(self):
-        return ChangesetStatus.get_status_lbl(self.status)
-
-
-class PullRequest(Base, BaseModel):
-    __tablename__ = 'pull_requests'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    # values for .status
-    STATUS_NEW = u'new'
-    STATUS_OPEN = u'open'
-    STATUS_CLOSED = u'closed'
-
-    pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
-    title = Column('title', Unicode(256), nullable=True)
-    description = Column('description', UnicodeText(10240), nullable=True)
-    status = Column('status', Unicode(256), 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)
-    _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(256), nullable=False)
-    other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
-    other_ref = Column('other_ref', Unicode(256), nullable=False)
-
-    @hybrid_property
-    def revisions(self):
-        return self._revisions.split(':')
-
-    @revisions.setter
-    def revisions(self, val):
-        self._revisions = ':'.join(val)
-
-    @property
-    def org_ref_parts(self):
-        return self.org_ref.split(':')
-
-    @property
-    def other_ref_parts(self):
-        return self.other_ref.split(':')
-
-    author = relationship('User', lazy='joined')
-    reviewers = relationship('PullRequestReviewers',
-                             cascade="all, delete, delete-orphan")
-    org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
-    other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
-                             cascade="all, delete, delete-orphan")
-
-    def is_closed(self):
-        return self.status == self.STATUS_CLOSED
-
-    @property
-    def last_review_status(self):
-        return self.statuses[-1].status if self.statuses else ''
-
-    def __json__(self):
-        return dict(
-            revisions=self.revisions
-        )
-
-
-class PullRequestReviewers(Base, BaseModel):
-    __tablename__ = 'pull_request_reviewers'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    def __init__(self, user=None, pull_request=None):
-        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)
-
-    user = relationship('User')
-    pull_request = relationship('PullRequest')
-
-
-class Notification(Base, BaseModel):
-    __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    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)
-    type_ = Column('type', Unicode(256))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', lazy='joined',
-                                          cascade="all, delete, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()\
-                .filter(UserNotification.notification == self)\
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            u.notifications.append(assoc)
-        Session().add(notification)
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
-    __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, '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 = relationship('User', lazy="joined")
-    notification = relationship('Notification', lazy="joined",
-                                order_by=lambda: Notification.created_on.desc(),)
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
-    __tablename__ = 'gists'
-    __table_args__ = (
-        Index('g_gist_access_id_idx', 'gist_access_id'),
-        Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    GIST_PUBLIC = u'public'
-    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_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)
-
-    owner = relationship('User')
-
-    def __repr__(self):
-        return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
-
-    @classmethod
-    def get_or_404(cls, id_):
-        res = cls.query().filter(cls.gist_access_id == id_).scalar()
-        if not res:
-            raise HTTPNotFound
-        return res
-
-    @classmethod
-    def get_by_access_id(cls, gist_access_id):
-        return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
-
-    def gist_url(self):
-        import kallithea
-        alias_url = kallithea.CONFIG.get('gist_alias_url')
-        if alias_url:
-            return alias_url.replace('{gistid}', self.gist_access_id)
-
-        import kallithea.lib.helpers as h
-        return h.canonical_url('gist', gist_id=self.gist_access_id)
-
-    @classmethod
-    def base_path(cls):
-        """
-        Returns base path when all gists are stored
-
-        :param cls:
-        """
-        from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
-            .filter(Ui.ui_key == URL_SEP)
-        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
-        return os.path.join(q.one().ui_value, GIST_STORE_LOC)
-
-    def get_api_data(self):
-        """
-        Common function for generating gist related data for API
-        """
-        gist = self
-        data = dict(
-            gist_id=gist.gist_id,
-            type=gist.gist_type,
-            access_id=gist.gist_access_id,
-            description=gist.gist_description,
-            url=gist.gist_url(),
-            expires=gist.gist_expires,
-            created_on=gist.created_on,
-        )
-        return data
-
-    def __json__(self):
-        data = dict(
-        )
-        data.update(self.get_api_data())
-        return data
-    ## SCM functions
-
-    @property
-    def scm_instance(self):
-        from kallithea.lib.vcs import get_repo
-        base_path = self.base_path()
-        return get_repo(os.path.join(*map(safe_str,
-                                          [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'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)
--- a/kallithea/lib/dbmigrate/versions/001_initial_release.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-#==============================================================================
-# DB INITIAL MODEL
-#==============================================================================
-import logging
-import datetime
-
-from sqlalchemy import *
-from sqlalchemy.exc import DatabaseError
-from sqlalchemy.orm import relation
-from sqlalchemy.orm.session import Session
-from kallithea.model.meta import Base
-
-from kallithea.lib.dbmigrate.migrate import *
-
-from kallithea import DB_PREFIX
-
-log = logging.getLogger(__name__)
-
-class Setting(Base):
-    __tablename__ = DB_PREFIX + 'settings'
-    __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
-    app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __init__(self, k, v):
-        self.app_settings_name = k
-        self.app_settings_value = v
-
-    def __repr__(self):
-        return "<Setting('%s:%s')>" % (self.app_settings_name,
-                                                self.app_settings_value)
-
-class Ui(Base):
-    __tablename__ = DB_PREFIX + 'ui'
-    __table_args__ = {'useexisting':True}
-    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
-
-
-class User(Base):
-    __tablename__ = 'users'
-    __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
-    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
-    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
-    name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
-    is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
-
-    user_log = relation('UserLog', cascade='all')
-    user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
-
-    repositories = relation('Repository')
-    user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
-
-    @property
-    def full_contact(self):
-        return '%s %s <%s>' % (self.name, self.lastname, self.email)
-
-    def __repr__(self):
-        return "<User('id:%s:%s')>" % (self.user_id, self.username)
-
-    def update_lastlogin(self):
-        """Update user lastlogin"""
-
-        try:
-            session = Session.object_session(self)
-            self.last_login = datetime.datetime.now()
-            session.add(self)
-            session.commit()
-            log.debug('updated user %s lastlogin', self.username)
-        except (DatabaseError,):
-            session.rollback()
-
-
-class UserLog(Base):
-    __tablename__ = 'user_logs'
-    __table_args__ = {'useexisting':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(u'users.user_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
-    repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
-
-    user = relation('User')
-    repository = relation('Repository')
-
-class Repository(Base):
-    __tablename__ = 'repositories'
-    __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
-    repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
-    repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
-    user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
-    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
-    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
-    description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    fork_id = Column("fork_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None)
-
-    user = relation('User')
-    fork = relation('Repository', remote_side=repo_id)
-    repo_to_perm = relation('UserRepoToPerm', cascade='all')
-    stats = relation('Statistics', cascade='all', uselist=False)
-
-    repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
-
-
-    def __repr__(self):
-        return "<Repository('%s:%s')>" % (self.repo_id, self.repo_name)
-
-class Permission(Base):
-    __tablename__ = 'permissions'
-    __table_args__ = {'useexisting':True}
-    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-
-    def __repr__(self):
-        return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
-
-class UserRepoToPerm(Base):
-    __tablename__ = 'repo_to_perm'
-    __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
-    repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
-
-    user = relation('User')
-    permission = relation('Permission')
-    repository = relation('Repository')
-
-class UserToPerm(Base):
-    __tablename__ = 'user_to_perm'
-    __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':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(u'users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column("permission_id", Integer(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
-
-    user = relation('User')
-    permission = relation('Permission')
-
-class Statistics(Base):
-    __tablename__ = 'statistics'
-    __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
-    stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    repository_id = Column("repository_id", Integer(), ForeignKey(u'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(), nullable=False)#JSON data
-    commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
-    languages = Column("languages", LargeBinary(), nullable=False)#JSON data
-
-    repository = relation('Repository', single_parent=True)
-
-class UserFollowing(Base):
-    __tablename__ = 'user_followings'
-    __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
-                      UniqueConstraint('user_id', 'follows_user_id')
-                      , {'useexisting':True})
-
-    user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    user_id = Column("user_id", Integer(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column("follows_user_id", Integer(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None)
-
-    user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
-
-    follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relation('Repository')
-
-
-class CacheInvalidation(Base):
-    __tablename__ = 'cache_invalidation'
-    __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
-    cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
-    cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
-
-
-    def __init__(self, cache_key, cache_args=''):
-        self.cache_key = cache_key
-        self.cache_args = cache_args
-        self.cache_active = False
-
-    def __repr__(self):
-        return "<CacheInvalidation('%s:%s')>" % (self.cache_id, self.cache_key)
-
-
-def upgrade(migrate_engine):
-    # Upgrade operations go here. Don't create your own engine; bind migrate_engine
-    # to your metadata
-    Base.metadata.create_all(bind=migrate_engine, checkfirst=False)
-
-def downgrade(migrate_engine):
-    # Operations to reverse the above upgrade go here.
-    Base.metadata.drop_all(bind=migrate_engine, checkfirst=False)
--- a/kallithea/lib/dbmigrate/versions/002_version_1_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """ Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-
-    #==========================================================================
-    # Upgrade of `users` table
-    #==========================================================================
-    tblname = 'users'
-    tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
-                    autoload_with=migrate_engine)
-
-    #ADD is_ldap column
-    is_ldap = Column("is_ldap", Boolean(), nullable=True,
-                     unique=None, default=False)
-    is_ldap.create(tbl, populate_default=True)
-    is_ldap.alter(nullable=False)
-
-    #==========================================================================
-    # Upgrade of `user_logs` table
-    #==========================================================================
-
-    tblname = 'users'
-    tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
-                    autoload_with=migrate_engine)
-
-    #ADD revision column
-    revision = Column('revision', TEXT(length=None, convert_unicode=False,
-                                       assert_unicode=None),
-                      nullable=True, unique=None, default=None)
-    revision.create(tbl)
-
-    #==========================================================================
-    # Upgrade of `repositories` table
-    #==========================================================================
-    tblname = 'repositories'
-    tbl = Table(tblname, MetaData(bind=migrate_engine), autoload=True,
-                    autoload_with=migrate_engine)
-
-    #ADD repo_type column#
-    repo_type = Column("repo_type", String(length=None, convert_unicode=False,
-                                           assert_unicode=None),
-                       nullable=True, unique=False, default='hg')
-
-    repo_type.create(tbl, populate_default=True)
-    #repo_type.alter(nullable=False)
-
-    #ADD statistics column#
-    enable_statistics = Column("statistics", Boolean(), nullable=True,
-                               unique=None, default=True)
-    enable_statistics.create(tbl)
-
-    #==========================================================================
-    # Add table `user_followings`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_1_0 import UserFollowing
-    UserFollowing().__table__.create()
-
-    #==========================================================================
-    # Add table `cache_invalidation`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_1_0 import CacheInvalidation
-    CacheInvalidation().__table__.create()
-
-    return
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/003_version_1_2_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """ Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-
-    #==========================================================================
-    # Add table `groups``
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import Group as Group
-    Group().__table__.create()
-
-    #==========================================================================
-    # Add table `group_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm
-    UserRepoGroupToPerm().__table__.create()
-
-    #==========================================================================
-    # Add table `users_groups`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroup
-    UserGroup().__table__.create()
-
-    #==========================================================================
-    # Add table `users_groups_members`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupMember
-    UserGroupMember().__table__.create()
-
-    #==========================================================================
-    # Add table `users_group_repo_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupRepoToPerm
-    UserGroupRepoToPerm().__table__.create()
-
-    #==========================================================================
-    # Add table `users_group_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserGroupToPerm
-    UserGroupToPerm().__table__.create()
-
-    #==========================================================================
-    # Upgrade of `users` table
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import User
-
-    #add column
-    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    ldap_dn.create(User().__table__)
-
-    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
-    api_key.create(User().__table__)
-
-    #remove old column
-    is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
-    is_ldap.drop(User().__table__)
-
-    #==========================================================================
-    # Upgrade of `repositories` table
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import Repository
-
-    #ADD clone_uri column#
-
-    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False,
-                                           assert_unicode=None),
-                        nullable=True, unique=False, default=None)
-
-    clone_uri.create(Repository().__table__)
-
-    #ADD downloads column#
-    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    enable_downloads.create(Repository().__table__)
-
-    #ADD column created_on
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
-                        unique=None, default=datetime.datetime.now)
-    created_on.create(Repository().__table__)
-
-    #ADD group_id column#
-    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'),
-                  nullable=True, unique=False, default=None)
-
-    group_id.create(Repository().__table__)
-
-    #==========================================================================
-    # Upgrade of `user_followings` table
-    #==========================================================================
-
-    from kallithea.lib.dbmigrate.schema.db_1_2_0 import UserFollowing
-
-    follows_from = Column('follows_from', DateTime(timezone=False),
-                          nullable=True, unique=None,
-                          default=datetime.datetime.now)
-    follows_from.create(UserFollowing().__table__)
-
-    return
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/004_version_1_3_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """ Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    #==========================================================================
-    # Add table `users_group_repo_group_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroupRepoGroupToPerm
-    UserGroupRepoGroupToPerm().__table__.create()
-
-    #==========================================================================
-    # Add table `changeset_comments`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
-    ChangesetComment().__table__.create()
-
-    #==========================================================================
-    # Add table `notifications`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import Notification
-    Notification().__table__.create()
-
-    #==========================================================================
-    # Add table `user_to_notification`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserNotification
-    UserNotification().__table__.create()
-
-    #==========================================================================
-    # Add unique to table `users_group_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroupToPerm
-    tbl = UserGroupToPerm().__table__
-    cons = UniqueConstraint('users_group_id', 'permission_id', table=tbl)
-    cons.create()
-
-    #==========================================================================
-    # Fix unique constrain on table `user_logs`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserLog
-    tbl = UserLog().__table__
-    col = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'),
-                 nullable=False, unique=None, default=None)
-    col.alter(nullable=True, table=tbl)
-
-    #==========================================================================
-    # Rename table `group_to_perm` to `user_repo_group_to_perm`
-    #==========================================================================
-    tbl = Table('group_to_perm', MetaData(bind=migrate_engine), autoload=True,
-                    autoload_with=migrate_engine)
-    tbl.rename('user_repo_group_to_perm')
-
-    return
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/005_version_1_3_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """ Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-
-    #==========================================================================
-    # Change unique constraints of table `repo_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserRepoToPerm
-    tbl = UserRepoToPerm().__table__
-    new_cons = UniqueConstraint('user_id', 'repository_id', 'permission_id', table=tbl)
-    new_cons.create()
-    old_cons = None
-    if migrate_engine.name in ['mysql']:
-        old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl, name="user_id")
-    elif migrate_engine.name in ['postgresql']:
-        old_cons = UniqueConstraint('user_id', 'repository_id', table=tbl)
-    else:
-        # sqlite doesn't support dropping constraints...
-        print """Please manually drop UniqueConstraint('user_id', 'repository_id')"""
-
-    if old_cons:
-        try:
-            old_cons.drop()
-        except Exception as e:
-            # we don't care if this fails really... better to pass migration than
-            # leave this in intermidiate state
-            print 'Failed to remove Unique for user_id, repository_id reason %s' % e
-
-
-    #==========================================================================
-    # fix uniques of table `user_repo_group_to_perm`
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserRepoGroupToPerm
-    tbl = UserRepoGroupToPerm().__table__
-    new_cons = UniqueConstraint('group_id', 'permission_id', 'user_id', table=tbl)
-    new_cons.create()
-    old_cons = None
-
-    # fix uniqueConstraints
-    if migrate_engine.name in ['mysql']:
-        #mysql is givinig troubles here...
-        old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name="group_id")
-    elif migrate_engine.name in ['postgresql']:
-        old_cons = UniqueConstraint('group_id', 'permission_id', table=tbl, name='group_to_perm_group_id_permission_id_key')
-    else:
-        # sqlite doesn't support dropping constraints...
-        print """Please manually drop UniqueConstraint('group_id', 'permission_id')"""
-
-    if old_cons:
-        try:
-            old_cons.drop()
-        except Exception as e:
-            # we don't care if this fails really... better to pass migration than
-            # leave this in intermidiate state
-            print 'Failed to remove Unique for user_id, repository_id reason %s' % e
-
-    return
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/006_version_1_4_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-
-    #==========================================================================
-    # USEREMAILMAP
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import UserEmailMap
-    tbl = UserEmailMap.__table__
-    tbl.create()
-    #==========================================================================
-    # PULL REQUEST
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import PullRequest
-    tbl = PullRequest.__table__
-    tbl.create()
-
-    #==========================================================================
-    # PULL REQUEST REVIEWERS
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import PullRequestReviewers
-    tbl = PullRequestReviewers.__table__
-    tbl.create()
-
-    #==========================================================================
-    # CHANGESET STATUS
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import ChangesetStatus
-    tbl = ChangesetStatus.__table__
-    tbl.create()
-
-    _reset_base(migrate_engine)
-
-    #==========================================================================
-    # USERS TABLE
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import User
-    tbl = User.__table__
-
-    # change column name -> firstname
-    col = User.__table__.columns.name
-    col.alter(index=Index('u_username_idx', 'username'))
-    col.alter(index=Index('u_email_idx', 'email'))
-    col.alter(name="firstname", table=tbl)
-
-    # add inherit_default_permission column
-    inherit_default_permissions = Column("inherit_default_permissions",
-                                         Boolean(), nullable=True, unique=None,
-                                         default=True)
-    inherit_default_permissions.create(table=tbl)
-    inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
-
-    #==========================================================================
-    # USERS GROUP TABLE
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import UserGroup
-    tbl = UserGroup.__table__
-    # add inherit_default_permission column
-    gr_inherit_default_permissions = Column(
-                                    "users_group_inherit_default_permissions",
-                                    Boolean(), nullable=True, unique=None,
-                                    default=True)
-    gr_inherit_default_permissions.create(table=tbl)
-    gr_inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
-
-    #==========================================================================
-    # REPOSITORIES
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import Repository
-    tbl = Repository.__table__
-
-    # add enable locking column
-    enable_locking = Column("enable_locking", Boolean(), nullable=True,
-                            unique=None, default=False)
-    enable_locking.create(table=tbl)
-    enable_locking.alter(nullable=False, default=False, table=tbl)
-
-    # add locked column
-    _locked = Column("locked", String(255), nullable=True, unique=False,
-                     default=None)
-    _locked.create(table=tbl)
-
-    #add langing revision column
-    landing_rev = Column("landing_revision", String(255), nullable=True,
-                         unique=False, default='tip')
-    landing_rev.create(table=tbl)
-    landing_rev.alter(nullable=False, default='tip', table=tbl)
-
-    #==========================================================================
-    # GROUPS
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import RepoGroup
-    tbl = RepoGroup.__table__
-
-    # add enable locking column
-    enable_locking = Column("enable_locking", Boolean(), nullable=True,
-                            unique=None, default=False)
-    enable_locking.create(table=tbl)
-    enable_locking.alter(nullable=False, default=False)
-
-    #==========================================================================
-    # CACHE INVALIDATION
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import CacheInvalidation
-    tbl = CacheInvalidation.__table__
-
-    # add INDEX for cache keys
-    col = CacheInvalidation.__table__.columns.cache_key
-    col.alter(index=Index('key_idx', 'cache_key'))
-
-    #==========================================================================
-    # NOTIFICATION
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import Notification
-    tbl = Notification.__table__
-
-    # add index for notification type
-    col = Notification.__table__.columns.type
-    col.alter(index=Index('notification_type_idx', 'type'),)
-
-    #==========================================================================
-    # CHANGESET_COMMENTS
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
-
-    tbl = ChangesetComment.__table__
-    col = ChangesetComment.__table__.columns.revision
-
-    # add index for revisions
-    col.alter(index=Index('cc_revision_idx', 'revision'),)
-
-    # add hl_lines column
-    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
-    hl_lines.create(table=tbl)
-
-    # add created_on column
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
-                        default=datetime.datetime.now)
-    created_on.create(table=tbl)
-    created_on.alter(nullable=False, default=datetime.datetime.now)
-
-    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False,
-                         default=datetime.datetime.now)
-    modified_at.alter(type=DateTime(timezone=False), table=tbl)
-
-    # add FK to pull_request
-    pull_request_id = Column("pull_request_id", Integer(),
-                             ForeignKey('pull_requests.pull_request_id'),
-                             nullable=True)
-    pull_request_id.create(table=tbl)
-    _reset_base(migrate_engine)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/007_version_1_4_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-
-    #==========================================================================
-    # CHANGESET_COMMENTS
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import ChangesetComment
-    tbl_name = ChangesetComment.__tablename__
-    tbl = Table(tbl_name,
-                MetaData(bind=migrate_engine), autoload=True,
-                autoload_with=migrate_engine)
-    col = tbl.columns.revision
-
-    # remove nullability from revision field
-    col.alter(nullable=True)
-
-    #==========================================================================
-    # REPOSITORY
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_4_0 import Repository
-    tbl = Repository.__table__
-    updated_on = Column('updated_on', DateTime(timezone=False),
-                        nullable=True, unique=None)
-    # create created on column for future lightweight main page
-    updated_on.create(table=tbl)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/008_version_1_5_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-import logging
-
-from sqlalchemy import *
-from sqlalchemy.orm import joinedload
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_5_0
-    #==========================================================================
-    # USER LOGS
-    #==========================================================================
-
-    tbl = db_1_5_0.UserLog.__table__
-    username = Column("username", String(255, convert_unicode=False,
-                                         assert_unicode=None), nullable=True,
-                      unique=None, default=None)
-    # create username column
-    username.create(table=tbl)
-
-    _Session = meta.Session()
-    ## after adding that column fix all usernames
-    users_log = _Session.query(db_1_5_0.UserLog)\
-            .options(joinedload(db_1_5_0.UserLog.user))\
-            .options(joinedload(db_1_5_0.UserLog.repository)).all()
-
-    for entry in users_log:
-        entry.username = entry.user.username
-        _Session.add(entry)
-    _Session.commit()
-
-    #alter username to not null
-    tbl_name = db_1_5_0.UserLog.__tablename__
-    tbl = Table(tbl_name,
-                MetaData(bind=migrate_engine), autoload=True,
-                autoload_with=migrate_engine)
-    col = tbl.columns.username
-
-    # remove nullability from revision field
-    col.alter(nullable=False)
-
-    # issue fixups
-    fixups(db_1_5_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    # ** create default permissions ** #
-    #=====================================
-    for p in models.Permission.PERMS:
-        if not models.Permission.get_by_key(p[0]):
-            new_perm = models.Permission()
-            new_perm.permission_name = p[0]
-            new_perm.permission_longname = p[0]  #translation err with p[1]
-            print 'Creating new permission %s' % p[0]
-            _SESSION().add(new_perm)
-
-    _SESSION().commit()
-
-    # ** populate default permissions ** #
-    #=====================================
-
-    user = models.User.query().filter(models.User.username == 'default').scalar()
-
-    def _make_perm(perm):
-        new_perm = models.UserToPerm()
-        new_perm.user = user
-        new_perm.permission = models.Permission.get_by_key(perm)
-        return new_perm
-
-    def _get_group(perm_name):
-        return '.'.join(perm_name.split('.')[:1])
-
-    perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
-    defined_perms_groups = map(_get_group,
-                              (x.permission.permission_name for x in perms))
-    log.debug('GOT ALREADY DEFINED:%s', perms)
-    DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
-
-    # for every default permission that needs to be created, we check if
-    # it's group is already defined, if it's not we create default perm
-    for perm_name in DEFAULT_PERMS:
-        gr = _get_group(perm_name)
-        if gr not in defined_perms_groups:
-            log.debug('GR:%s not found, creating permission %s',
-                      gr, perm_name)
-            new_perm = _make_perm(perm_name)
-            _SESSION().add(new_perm)
-    _SESSION().commit()
-
-    # ** create default options ** #
-    #===============================
-    skip_existing = True
-    for k, v in [
-        ('default_repo_enable_locking',  False),
-        ('default_repo_enable_downloads', False),
-        ('default_repo_enable_statistics', False),
-        ('default_repo_private', False),
-        ('default_repo_type', 'hg')]:
-
-        if skip_existing and models.Setting.get_by_name(k) is not None:
-            log.debug('Skipping option %s', k)
-            continue
-        setting = models.Setting(k, v)
-        _SESSION().add(setting)
-
-    _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/009_version_1_5_1.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    pass
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/010_version_1_5_2.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_5_2
-    #==========================================================================
-    # USER LOGS
-    #==========================================================================
-    tbl = db_1_5_2.UserIpMap.__table__
-    tbl.create()
-
-    #==========================================================================
-    # REPOSITORIES
-    #==========================================================================
-    tbl = db_1_5_2.Repository.__table__
-    changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True)
-    # create username column
-    changeset_cache.create(table=tbl)
-
-    # issue fixups
-    fixups(db_1_5_2, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Upgrading repositories Caches')
-    repositories = models.Repository.getAll()
-    for repo in repositories:
-        print repo
-        repo.update_changeset_cache()
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/011_version_1_6_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_6_0
-
-    #==========================================================================
-    # USER LOGS
-    #==========================================================================
-    tbl = db_1_6_0.RepositoryField.__table__
-    tbl.create()
-
-    # issue fixups
-    fixups(db_1_6_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Upgrading repositories Caches')
-    repositories = models.Repository.getAll()
-    for repo in repositories:
-        print repo
-        repo.update_changeset_cache()
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/012_version_1_7_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_7_0
-
-    #==========================================================================
-    # UserUserGroupToPerm
-    #==========================================================================
-    tbl = db_1_7_0.UserUserGroupToPerm.__table__
-    tbl.create()
-
-    #==========================================================================
-    # UserGroupUserGroupToPerm
-    #==========================================================================
-    tbl = db_1_7_0.UserGroupUserGroupToPerm.__table__
-    tbl.create()
-
-    #==========================================================================
-    # Gist
-    #==========================================================================
-    tbl = db_1_7_0.Gist.__table__
-    tbl.create()
-
-    #==========================================================================
-    # UserGroup
-    #==========================================================================
-    tbl = db_1_7_0.UserGroup.__table__
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
-                     nullable=True, unique=False, default=None)
-    # create username column
-    user_id.create(table=tbl)
-
-    #==========================================================================
-    # RepoGroup
-    #==========================================================================
-    tbl = db_1_7_0.RepoGroup.__table__
-    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
-                     nullable=True, unique=False, default=None)
-    # create username column
-    user_id.create(table=tbl)
-
-    # issue fixups
-    fixups(db_1_7_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    # ** create default permissions ** #
-    #=====================================
-    for p in models.Permission.PERMS:
-        if not models.Permission.get_by_key(p[0]):
-            new_perm = models.Permission()
-            new_perm.permission_name = p[0]
-            new_perm.permission_longname = p[0]  #translation err with p[1]
-            _SESSION().add(new_perm)
-
-    _SESSION().commit()
-
-    # ** populate default permissions ** #
-    #=====================================
-
-    user = models.User.query().filter(models.User.username == 'default').scalar()
-
-    def _make_perm(perm):
-        new_perm = models.UserToPerm()
-        new_perm.user = user
-        new_perm.permission = models.Permission.get_by_key(perm)
-        return new_perm
-
-    def _get_group(perm_name):
-        return '.'.join(perm_name.split('.')[:1])
-
-    perms = models.UserToPerm.query().filter(models.UserToPerm.user == user).all()
-    defined_perms_groups = map(_get_group,
-                              (x.permission.permission_name for x in perms))
-    log.debug('GOT ALREADY DEFINED:%s', perms)
-    DEFAULT_PERMS = models.Permission.DEFAULT_USER_PERMISSIONS
-
-    # for every default permission that needs to be created, we check if
-    # it's group is already defined, if it's not we create default perm
-    for perm_name in DEFAULT_PERMS:
-        gr = _get_group(perm_name)
-        if gr not in defined_perms_groups:
-            log.debug('GR:%s not found, creating permission %s',
-                      gr, perm_name)
-            new_perm = _make_perm(perm_name)
-            _SESSION().add(new_perm)
-    _SESSION().commit()
-
-    #fix all usergroups
-
-    def _create_default_perms(user_group):
-        # create default permission
-        default_perm = 'usergroup.read'
-        def_user = models.User.get_default_user()
-        for p in def_user.user_perms:
-            if p.permission.permission_name.startswith('usergroup.'):
-                default_perm = p.permission.permission_name
-                break
-
-        user_group_to_perm = models.UserUserGroupToPerm()
-        user_group_to_perm.permission = models.Permission.get_by_key(default_perm)
-
-        user_group_to_perm.user_group = user_group
-        user_group_to_perm.user_id = def_user.user_id
-        return user_group_to_perm
-
-    for ug in models.UserGroup.get_all():
-        perm_obj = _create_default_perms(ug)
-        _SESSION().add(perm_obj)
-    _SESSION().commit()
-
-    adm = models.User.get_first_admin()
-    # fix owners of UserGroup
-    for ug in _SESSION().query(models.UserGroup).all():
-        ug.user_id = adm.user_id
-        _SESSION().add(ug)
-    _SESSION().commit()
-
-    # fix owners of RepoGroup
-    for ug in _SESSION().query(models.RepoGroup).all():
-        ug.user_id = adm.user_id
-        _SESSION().add(ug)
-    _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/013_version_1_7_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-
-
-    #==========================================================================
-    # UserGroup
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_7_0 import UserGroup
-    tbl = UserGroup.__table__
-    user_id = tbl.columns.user_id
-    user_id.alter(nullable=False)
-
-    #==========================================================================
-    # RepoGroup
-    #==========================================================================
-    from kallithea.lib.dbmigrate.schema.db_1_7_0 import RepoGroup
-    tbl = RepoGroup.__table__
-    user_id = tbl.columns.user_id
-    user_id.alter(nullable=False)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
--- a/kallithea/lib/dbmigrate/versions/014_version_1_7_1.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_7_0
-
-    #==========================================================================
-    # Gist
-    #==========================================================================
-    tbl = db_1_7_0.Gist.__table__
-    user_id = tbl.columns.gist_expires
-    user_id.alter(type=Float(53))
-
-    # issue fixups
-    fixups(db_1_7_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    # fix nullable columns on last_update
-    for r in models.Repository().get_all():
-        if r.updated_on is None:
-            r.updated_on = datetime.datetime.fromtimestamp(0)
-            _SESSION().add(r)
-    _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/015_version_1_8_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_1_8_0
-    tbl = db_1_8_0.Setting.__table__
-    app_settings_type = Column("app_settings_type",
-                               String(255, convert_unicode=False, assert_unicode=None),
-                               nullable=True, unique=None, default=None)
-    app_settings_type.create(table=tbl)
-
-    # issue fixups
-    fixups(db_1_8_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Fixing default options now...')
-
-    settings = [
-        #general
-        ('realm', '', 'unicode'),
-        ('title', '', 'unicode'),
-        ('ga_code', '', 'unicode'),
-        ('show_public_icon', False, 'bool'),
-        ('show_private_icon', True, 'bool'),
-        ('stylify_metatags', True, 'bool'),
-
-        # defaults
-        ('default_repo_enable_locking',  False, 'bool'),
-        ('default_repo_enable_downloads', False, 'bool'),
-        ('default_repo_enable_statistics', False, 'bool'),
-        ('default_repo_private', False, 'bool'),
-        ('default_repo_type', 'hg', 'unicode'),
-
-        #other
-        ('dashboard_items', 100, 'int'),
-        ('show_version', True, 'bool')
-    ]
-
-    for name, default, type_ in settings:
-        setting = models.Setting.get_by_name(name)
-        if not setting:
-            # if we don't have this option create it
-            setting = models.Setting(name, default, type_)
-
-        # fix certain key to new defaults
-        if name in ['title', 'show_public_icon']:
-            # change title if it's only the default
-            if name == 'title' and setting.app_settings_value == 'Kallithea':
-                setting.app_settings_value = default
-            else:
-                setting.app_settings_value = default
-
-        setting._app_settings_type = type_
-        _SESSION().add(setting)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/016_version_2_0_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea import EXTERN_TYPE_INTERNAL
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_0
-    tbl = db_2_0_0.User.__table__
-
-    extern_type = Column("extern_type",
-                         String(255, convert_unicode=False, assert_unicode=None),
-                         nullable=True, unique=None, default=None)
-    extern_type.create(table=tbl)
-
-    extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None),
-                         nullable=True, unique=None, default=None)
-    extern_name.create(table=tbl)
-
-    created_on = Column('created_on', DateTime(timezone=False),
-                        nullable=True, default=datetime.datetime.now)
-    created_on.create(table=tbl)
-
-    # issue fixups
-    fixups(db_2_0_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Fixing default created on')
-
-    for usr in models.User.get_all():
-        usr.created_on = datetime.datetime.now()
-        _SESSION().add(usr)
-        _SESSION().commit()
-
-    notify('Migrating LDAP attribute to extern')
-    for usr in models.User.get_all():
-        ldap_dn = usr.ldap_dn
-        if ldap_dn:
-            usr.extern_name = ldap_dn
-            usr.extern_type = 'ldap'
-        else:
-            usr.extern_name = EXTERN_TYPE_INTERNAL
-            usr.extern_type = EXTERN_TYPE_INTERNAL
-        _SESSION().add(usr)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/017_version_2_0_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_0
-    tbl = db_2_0_0.UserGroup.__table__
-
-    user_group_description = Column("user_group_description",
-                                    String(10000, convert_unicode=False,
-                                           assert_unicode=None), nullable=True,
-                                    unique=None, default=None)
-    user_group_description.create(table=tbl)
-
-    created_on = Column('created_on', DateTime(timezone=False),
-                        nullable=True, default=datetime.datetime.now)
-    created_on.create(table=tbl)
-
-    # issue fixups
-    fixups(db_2_0_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Fixing default created on')
-
-    for gr in models.UserGroup.get_all():
-        gr.created_on = datetime.datetime.now()
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/018_version_2_0_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-from kallithea.lib.utils2 import str2bool
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_0
-
-    # issue fixups
-    fixups(db_2_0_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Fixing default auth modules')
-    plugins = 'kallithea.lib.auth_modules.auth_internal'
-    opts = []
-    ldap_enabled = str2bool(getattr(
-        models.Setting.get_by_name('ldap_active'),
-        'app_settings_value', False))
-    if ldap_enabled:
-        plugins += ',kallithea.lib.auth_modules.auth_ldap'
-        opts.append(('auth_ldap_enabled', 'True', 'bool'))
-
-    opts.append(('auth_plugins', plugins, 'list'),)
-    opts.append(('auth_internal_enabled', 'True', 'bool'))
-
-    for name, default, type_ in opts:
-        setting = models.Setting.get_by_name(name)
-        if not setting:
-            # if we don't have this option create it
-            setting = models.Setting(name, default, type_)
-
-        _SESSION().add(setting)
-        _SESSION().commit()
-
-    #copy over the LDAP settings
-    old_ldap = [('ldap_active', 'false', 'bool'), ('ldap_host', '', 'unicode'),
-                ('ldap_port', '389', 'int'), ('ldap_tls_kind', 'PLAIN', 'unicode'),
-                ('ldap_tls_reqcert', '', 'unicode'), ('ldap_dn_user', '', 'unicode'),
-                ('ldap_dn_pass', '', 'unicode'), ('ldap_base_dn', '', 'unicode'),
-                ('ldap_filter', '', 'unicode'), ('ldap_search_scope', '', 'unicode'),
-                ('ldap_attr_login', '', 'unicode'), ('ldap_attr_firstname', '', 'unicode'),
-                ('ldap_attr_lastname', '', 'unicode'), ('ldap_attr_email', '', 'unicode')]
-    for k, v, t in old_ldap:
-        old_setting = models.Setting.get_by_name(k)
-        name = 'auth_%s' % k
-        setting = models.Setting.get_by_name(name)
-        if setting is None:
-            # if we don't have this option create it
-            if old_setting is not None:
-                v = old_setting.app_settings_value
-            setting = models.Setting(name, v, t)
-
-        _SESSION().add(setting)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/019_version_2_0_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_0
-    tbl = db_2_0_0.Setting.__table__
-    settings_value = tbl.columns.app_settings_value
-    settings_value.alter(type=String(4096))
-
-    # issue fixups
-    fixups(db_2_0_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    return
--- a/kallithea/lib/dbmigrate/versions/020_version_2_0_1.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea import EXTERN_TYPE_INTERNAL
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_1
-
-    # issue fixups
-    fixups(db_2_0_1, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    #fix all empty extern type users to default 'internal'
-    for usr in models.User.query().all():
-        if not usr.extern_name:
-            usr.extern_name = EXTERN_TYPE_INTERNAL
-            usr.extern_type = EXTERN_TYPE_INTERNAL
-            _SESSION().add(usr)
-            _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/021_version_2_0_2.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-import os
-import logging
-import datetime
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_1
-    tbl = db_2_0_1.RepoGroup.__table__
-
-    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
-                        default=datetime.datetime.now)
-    created_on.create(table=tbl)
-
-    #fix null values on certain columns when upgrading from older releases
-    tbl = db_2_0_1.UserLog.__table__
-    col = tbl.columns.user_id
-    col.alter(nullable=True)
-
-    tbl = db_2_0_1.UserFollowing.__table__
-    col = tbl.columns.follows_repository_id
-    col.alter(nullable=True)
-
-    tbl = db_2_0_1.UserFollowing.__table__
-    col = tbl.columns.follows_user_id
-    col.alter(nullable=True)
-
-    # issue fixups
-    fixups(db_2_0_1, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Fixing default created on for repo groups')
-
-    for gr in models.RepoGroup.get_all():
-        gr.created_on = datetime.datetime.now()
-        _SESSION().add(gr)
-        _SESSION().commit()
-
-    repo_store_path = models.Ui.get_repos_location()
-    _store = os.path.join(repo_store_path, '.cache', 'largefiles')
-    notify('Setting largefiles usercache')
-    print _store
-
-    if not models.Ui.get_by_key('usercache'):
-        largefiles = models.Ui()
-        largefiles.ui_section = 'largefiles'
-        largefiles.ui_key = 'usercache'
-        largefiles.ui_value = _store
-        _SESSION().add(largefiles)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/022_version_2_0_2.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_0_2
-
-    # issue fixups
-    fixups(db_2_0_2, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('fixing new schema for landing_rev')
-
-    for repo in models.Repository.get_all():
-        print u'repo %s old landing rev is: %s' % (repo, repo.landing_rev)
-        _rev = repo.landing_rev[1]
-        _rev_type = 'rev'  # default
-
-        if _rev in ['default', 'master']:
-            _rev_type = 'branch'
-        elif _rev in ['tip']:
-            _rev_type = 'rev'
-        else:
-            try:
-                scm = repo.scm_instance
-                if scm:
-                    known_branches = scm.branches.keys()
-                    known_bookmarks = scm.bookmarks.keys()
-                    if _rev in known_branches:
-                        _rev_type = 'branch'
-                    elif _rev in known_bookmarks:
-                        _rev_type = 'book'
-            except Exception as e:
-                print e
-                print 'continue...'
-                #we don't want any error to break the process
-                pass
-
-        _new_landing_rev = '%s:%s' % (_rev_type, _rev)
-        print u'setting to %s' % _new_landing_rev
-        repo.landing_rev = _new_landing_rev
-        _SESSION().add(repo)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/023_version_2_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_1_0
-
-    tbl = db_2_1_0.UserApiKeys.__table__
-    tbl.create()
-
-    # issue fixups
-    fixups(db_2_1_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    pass
--- a/kallithea/lib/dbmigrate/versions/024_version_2_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-from kallithea.lib.utils2 import str2bool
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_1_0
-
-    # issue fixups
-    fixups(db_2_1_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    from pylons import config
-
-    notify('migrating options from .ini file')
-    use_gravatar = str2bool(config.get('use_gravatar'))
-    print('Setting gravatar use to: %s' % use_gravatar)
-    sett = models.Setting.create_or_update('use_gravatar',
-                                                    use_gravatar, 'bool')
-    _SESSION().add(sett)
-    _SESSION.commit()
-    #set the new format of gravatar URL
-    gravatar_url = models.User.DEFAULT_GRAVATAR_URL
-    if config.get('alternative_gravatar_url'):
-        gravatar_url = config.get('alternative_gravatar_url')
-
-    print('Setting gravatar url to:%s' % gravatar_url)
-    sett = models.Setting.create_or_update('gravatar_url',
-                                                    gravatar_url, 'unicode')
-    _SESSION().add(sett)
-    _SESSION.commit()
-
-    #now create new changed value of clone_url
-    clone_uri_tmpl = models.Repository.DEFAULT_CLONE_URI
-    print('settings new clone url template to %s' % clone_uri_tmpl)
-
-    sett = models.Setting.create_or_update('clone_uri_tmpl',
-                                                    clone_uri_tmpl, 'unicode')
-    _SESSION().add(sett)
-    _SESSION.commit()
--- a/kallithea/lib/dbmigrate/versions/025_version_2_1_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_1_0
-
-    # issue fixups
-    fixups(db_2_1_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Creating upgrade URL')
-    sett = models.Setting.create_or_update('update_url',
-                            models.Setting.DEFAULT_UPDATE_URL, 'unicode')
-    _SESSION().add(sett)
-    _SESSION.commit()
--- a/kallithea/lib/dbmigrate/versions/026_version_2_2_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_0
-
-    tbl = db_2_2_0.User.__table__
-
-    user_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data
-    user_data.create(table=tbl)
-
-    # issue fixups
-    fixups(db_2_2_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    pass
--- a/kallithea/lib/dbmigrate/versions/027_version_2_2_0.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_0
-
-    # issue fixups
-    fixups(db_2_2_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    # ** create default permissions ** #
-    #=====================================
-    for p in models.Permission.PERMS:
-        if not models.Permission.get_by_key(p[0]):
-            new_perm = models.Permission()
-            new_perm.permission_name = p[0]
-            new_perm.permission_longname = p[0]  #translation err with p[1]
-            print 'Creating new permission %s' % p[0]
-            _SESSION().add(new_perm)
-
-    _SESSION().commit()
-
-    # ** set default create_on_write to active
-    user = models.User.get_default_user()
-    _def = 'hg.create.write_on_repogroup.true'
-    new = models.UserToPerm()
-    new.user = user
-    new.permission = models.Permission.get_by_key(_def)
-    print 'Setting default to %s' % _def
-    _SESSION().add(new)
-    _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/028_version_2_2_3.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_0
-
-    tbl = db_2_2_0.UserGroup.__table__
-
-    user_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
-    user_data.create(table=tbl)
-
-    # issue fixups
-    fixups(db_2_2_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    pass
--- a/kallithea/lib/dbmigrate/versions/029_version_2_2_3.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_0
-
-    # issue fixups
-    fixups(db_2_2_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Adding grid items options now...')
-
-    settings = [
-        ('admin_grid_items', 25, 'int'),  # old hardcoded value was 25
-    ]
-
-    for name, default, type_ in settings:
-        setting = models.Setting.get_by_name(name)
-        if not setting:
-            # if we don't have this option create it
-            setting = models.Setting(name, default, type_)
-        setting._app_settings_type = type_
-        _SESSION().add(setting)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/030_version_2_2_3.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_0
-
-    tbl = db_2_2_0.Repository.__table__
-
-    repo_state = Column("repo_state", String(255), nullable=True)
-    repo_state.create(table=tbl)
-
-    # issue fixups
-    fixups(db_2_2_0, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    pass
--- a/kallithea/lib/dbmigrate/versions/031_version_2_2_3.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-import logging
-
-from sqlalchemy import *
-
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-from kallithea.lib.dbmigrate.versions import _reset_base, notify
-
-log = logging.getLogger(__name__)
-
-
-def upgrade(migrate_engine):
-    """
-    Upgrade operations go here.
-    Don't create your own engine; bind migrate_engine to your metadata
-    """
-    _reset_base(migrate_engine)
-    from kallithea.lib.dbmigrate.schema import db_2_2_3
-
-    # issue fixups
-    fixups(db_2_2_3, meta.Session)
-
-
-def downgrade(migrate_engine):
-    meta = MetaData()
-    meta.bind = migrate_engine
-
-
-def fixups(models, _SESSION):
-    notify('Creating repository states')
-    for repo in models.Repository.get_all():
-        _state = models.Repository.STATE_CREATED
-        print 'setting repo %s state to "%s"' % (repo, _state)
-        repo.repo_state = _state
-        _SESSION().add(repo)
-        _SESSION().commit()
--- a/kallithea/lib/dbmigrate/versions/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +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.lib.dbmigrate.versions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Package containing new versions of database models
-
-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 11, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-from sqlalchemy import *
-from sqlalchemy.ext.declarative import declarative_base
-from kallithea.lib.dbmigrate.migrate import *
-from kallithea.lib.dbmigrate.migrate.changeset import *
-
-from kallithea.model import meta
-
-
-def notify(msg, caps=True):
-    """
-    Notification for migrations messages
-    """
-    ml = len(msg) + (4 * 2)
-    formatted_msg = ('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml))
-    if caps:
-        formatted_msg = formatted_msg.upper()
-    print(formatted_msg)
-
-
-def _reset_base(migrate_engine):
-    ## RESET COMPLETLY THE metadata for sqlalchemy to use previous declared Base
-    Base = declarative_base()
-    Base.metadata.clear()
-    Base.metadata = MetaData()
-    Base.metadata.bind = migrate_engine
-
-    # new session and base
-    #meta.Session = scoped_session(sessionmaker(expire_on_commit=True,))
-    #meta.Session.configure(bind=migrate_engine)
-    meta.Base = Base
-
-    notify('SQLA BASE RESET !')
--- a/kallithea/lib/diffs.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/diffs.py	Sun Mar 31 21:28:56 2019 +0200
@@ -29,79 +29,208 @@
 import difflib
 import logging
 
-from itertools import tee, imap
+from tg.i18n import ugettext as _
 
-from pylons.i18n.translation import _
-
+from kallithea.lib import helpers as h
 from kallithea.lib.vcs.exceptions import VCSError
 from kallithea.lib.vcs.nodes import FileNode, SubModuleNode
 from kallithea.lib.vcs.backends.base import EmptyChangeset
-from kallithea.lib.helpers import escape
 from kallithea.lib.utils2 import safe_unicode
 
 log = logging.getLogger(__name__)
 
 
-def wrap_to_table(str_):
-    return '''<table class="code-difftable">
+def _safe_id(idstring):
+    """Make a string safe for including in an id attribute.
+
+    The HTML spec says that id attributes 'must begin with
+    a letter ([A-Za-z]) and may be followed by any number
+    of letters, digits ([0-9]), hyphens ("-"), underscores
+    ("_"), colons (":"), and periods (".")'. These regexps
+    are slightly over-zealous, in that they remove colons
+    and periods unnecessarily.
+
+    Whitespace is transformed into underscores, and then
+    anything which is not a hyphen or a character that
+    matches \w (alphanumerics and underscore) is removed.
+
+    """
+    # Transform all whitespace to underscore
+    idstring = re.sub(r'\s', "_", idstring)
+    # Remove everything that is not a hyphen or a member of \w
+    idstring = re.sub(r'(?!-)\W', "", idstring).lower()
+    return idstring
+
+
+def as_html(table_class='code-difftable', line_class='line',
+            old_lineno_class='lineno old', new_lineno_class='lineno new',
+            no_lineno_class='lineno',
+            code_class='code', enable_comments=False, parsed_lines=None):
+    """
+    Return given diff as html table with customized css classes
+    """
+    def _link_to_if(condition, label, url):
+        """
+        Generates a link if condition is meet or just the label if not.
+        """
+
+        if condition:
+            return '''<a href="%(url)s" data-pseudo-content="%(label)s"></a>''' % {
+                'url': url,
+                'label': label
+            }
+        else:
+            return label
+
+    _html_empty = True
+    _html = []
+    _html.append('''<table class="%(table_class)s">\n''' % {
+        'table_class': table_class
+    })
+
+    for diff in parsed_lines:
+        for line in diff['chunks']:
+            _html_empty = False
+            for change in line:
+                _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
+                    'lc': line_class,
+                    'action': change['action']
+                })
+                anchor_old_id = ''
+                anchor_new_id = ''
+                anchor_old = "%(filename)s_o%(oldline_no)s" % {
+                    'filename': _safe_id(diff['filename']),
+                    'oldline_no': change['old_lineno']
+                }
+                anchor_new = "%(filename)s_n%(oldline_no)s" % {
+                    'filename': _safe_id(diff['filename']),
+                    'oldline_no': change['new_lineno']
+                }
+                cond_old = (change['old_lineno'] != '...' and
+                            change['old_lineno'])
+                cond_new = (change['new_lineno'] != '...' and
+                            change['new_lineno'])
+                no_lineno = (change['old_lineno'] == '...' and
+                             change['new_lineno'] == '...')
+                if cond_old:
+                    anchor_old_id = 'id="%s"' % anchor_old
+                if cond_new:
+                    anchor_new_id = 'id="%s"' % anchor_new
+                ###########################################################
+                # OLD LINE NUMBER
+                ###########################################################
+                _html.append('''\t<td %(a_id)s class="%(olc)s" %(colspan)s>''' % {
+                    'a_id': anchor_old_id,
+                    'olc': no_lineno_class if no_lineno else old_lineno_class,
+                    'colspan': 'colspan="2"' if no_lineno else ''
+                })
+
+                _html.append('''%(link)s''' % {
+                    'link': _link_to_if(not no_lineno, change['old_lineno'],
+                                        '#%s' % anchor_old)
+                })
+                _html.append('''</td>\n''')
+                ###########################################################
+                # NEW LINE NUMBER
+                ###########################################################
+
+                if not no_lineno:
+                    _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
+                        'a_id': anchor_new_id,
+                        'nlc': new_lineno_class
+                    })
+
+                    _html.append('''%(link)s''' % {
+                        'link': _link_to_if(True, change['new_lineno'],
+                                            '#%s' % anchor_new)
+                    })
+                    _html.append('''</td>\n''')
+                ###########################################################
+                # CODE
+                ###########################################################
+                comments = '' if enable_comments else 'no-comment'
+                _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
+                    'cc': code_class,
+                    'inc': comments
+                })
+                _html.append('''\n\t\t<div class="add-bubble"><div>&nbsp;</div></div><pre>%(code)s</pre>\n''' % {
+                    'code': change['line']
+                })
+
+                _html.append('''\t</td>''')
+                _html.append('''\n</tr>\n''')
+    _html.append('''</table>''')
+    if _html_empty:
+        return None
+    return ''.join(_html)
+
+
+def wrap_to_table(html):
+    """Given a string with html, return it wrapped in a table, similar to what
+    DiffProcessor returns."""
+    return '''\
+              <table class="code-difftable">
                 <tr class="line no-comment">
                 <td class="lineno new"></td>
                 <td class="code no-comment"><pre>%s</pre></td>
                 </tr>
-              </table>''' % str_
+              </table>''' % html
 
 
-def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
+def wrapped_diff(filenode_old, filenode_new, diff_limit=None,
                 ignore_whitespace=True, line_context=3,
                 enable_comments=False):
     """
-    returns a wrapped diff into a table, checks for cut_off_limit and presents
-    proper message
+    Returns a file diff wrapped into a table.
+    Checks for diff_limit and presents a message if the diff is too big.
     """
-
     if filenode_old is None:
         filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
 
+    op = None
+    a_path = filenode_old.path # default, might be overriden by actual rename in diff
     if filenode_old.is_binary or filenode_new.is_binary:
-        diff = wrap_to_table(_('Binary file'))
+        html_diff = wrap_to_table(_('Binary file'))
         stats = (0, 0)
-        size = 0
 
-    elif cut_off_limit != -1 and (cut_off_limit is None or
-    (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
+    elif diff_limit != -1 and (
+            diff_limit is None or
+            (filenode_old.size < diff_limit and filenode_new.size < diff_limit)):
 
-        f_gitdiff = get_gitdiff(filenode_old, filenode_new,
+        raw_diff = get_gitdiff(filenode_old, filenode_new,
                                 ignore_whitespace=ignore_whitespace,
                                 context=line_context)
-        diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
+        diff_processor = DiffProcessor(raw_diff)
+        if diff_processor.parsed: # there should be exactly one element, for the specified file
+            f = diff_processor.parsed[0]
+            op = f['operation']
+            a_path = f['old_filename']
 
-        diff = diff_processor.as_html(enable_comments=enable_comments)
+        html_diff = as_html(parsed_lines=diff_processor.parsed, enable_comments=enable_comments)
         stats = diff_processor.stat()
-        size = len(diff or '')
+
     else:
-        diff = wrap_to_table(_('Changeset was too big and was cut off, use '
+        html_diff = wrap_to_table(_('Changeset was too big and was cut off, use '
                                'diff menu to display this diff'))
         stats = (0, 0)
-        size = 0
-    if not diff:
+
+    if not html_diff:
         submodules = filter(lambda o: isinstance(o, SubModuleNode),
                             [filenode_new, filenode_old])
         if submodules:
-            diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
+            html_diff = wrap_to_table(h.escape('Submodule %r' % submodules[0]))
         else:
-            diff = wrap_to_table(_('No changes detected'))
+            html_diff = wrap_to_table(_('No changes detected'))
 
     cs1 = filenode_old.changeset.raw_id
     cs2 = filenode_new.changeset.raw_id
 
-    return size, cs1, cs2, diff, stats
+    return cs1, cs2, a_path, html_diff, stats, op
 
 
 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
     """
     Returns git style diff between given ``filenode_old`` and ``filenode_new``.
-
-    :param ignore_whitespace: ignore whitespaces in diff
     """
     # make sure we pass in default context
     context = context or 3
@@ -119,10 +248,23 @@
     old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
     new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
 
-    vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
-                                ignore_whitespace, context)
+    vcs_gitdiff = get_diff(repo, old_raw_id, new_raw_id, filenode_new.path,
+                           ignore_whitespace, context)
     return vcs_gitdiff
 
+
+def get_diff(scm_instance, rev1, rev2, path=None, ignore_whitespace=False, context=3):
+    """
+    A thin wrapper around vcs lib get_diff.
+    """
+    try:
+        return scm_instance.get_diff(rev1, rev2, path=path,
+                                     ignore_whitespace=ignore_whitespace, context=context)
+    except MemoryError:
+        h.flash('MemoryError: Diff is too big', category='error')
+        return ''
+
+
 NEW_FILENODE = 1
 DEL_FILENODE = 2
 MOD_FILENODE = 3
@@ -132,78 +274,18 @@
 BIN_FILENODE = 7
 
 
-class DiffLimitExceeded(Exception):
-    pass
-
-
-class LimitedDiffContainer(object):
-
-    def __init__(self, diff_limit, cur_diff_size, diff):
-        self.diff = diff
-        self.diff_limit = diff_limit
-        self.cur_diff_size = cur_diff_size
-
-    def __iter__(self):
-        for l in self.diff:
-            yield l
-
-
 class DiffProcessor(object):
     """
     Give it a unified or git diff and it returns a list of the files that were
     mentioned in the diff together with a dict of meta information that
     can be used to render it in a HTML template.
     """
-    _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
-    _newline_marker = re.compile(r'^\\ No newline at end of file')
-    _git_header_re = re.compile(r"""
-        # has already been split on this:
-        # ^diff[ ]--git
-            [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
-        (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
-           ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
-        (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
-           ^rename[ ]from[ ](?P<rename_from>.+)\n
-           ^rename[ ]to[ ](?P<rename_to>.+)(?:\n|$))?
-        (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
-        (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
-        (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
-            \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
-        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
-        (?:^---[ ](a/(?P<a_file>.+?)|/dev/null)\t?(?:\n|$))?
-        (?:^\+\+\+[ ](b/(?P<b_file>.+?)|/dev/null)\t?(?:\n|$))?
-    """, re.VERBOSE | re.MULTILINE)
-    _hg_header_re = re.compile(r"""
-        # has already been split on this:
-        # ^diff[ ]--git
-            [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
-        (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
-           ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
-        (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
-        (?:^rename[ ]from[ ](?P<rename_from>.+)\n
-           ^rename[ ]to[ ](?P<rename_to>.+)(?:\n|$))?
-        (?:^copy[ ]from[ ](?P<copy_from>.+)\n
-           ^copy[ ]to[ ](?P<copy_to>.+)(?:\n|$))?
-        (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
-        (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
-        (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
-            \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
-        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
-        (?:^---[ ](a/(?P<a_file>.+?)|/dev/null)\t?(?:\n|$))?
-        (?:^\+\+\+[ ](b/(?P<b_file>.+?)|/dev/null)\t?(?:\n|$))?
-    """, re.VERBOSE | re.MULTILINE)
+    _diff_git_re = re.compile('^diff --git', re.MULTILINE)
 
-    # Used for inline highlighter word split, must match the substitutions in _escaper
-    _token_re = re.compile(r'()(&amp;|&lt;|&gt;|<u>\t</u>|<u class="cr"></u>| <i></i>|\W+?)')
-
-    _escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( \n| $)')
-
-
-    def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
+    def __init__(self, diff, vcs='hg', diff_limit=None, inline_diff=True):
         """
         :param diff:   a text in diff format
         :param vcs: type of version control hg or git
-        :param format: format of diff passed, `udiff` or `gitdiff`
         :param diff_limit: define the size of diff that is considered "big"
             based on that parameter cut off will be triggered, set to None
             to show full diff
@@ -212,175 +294,29 @@
             raise Exception('Diff must be a basestring got %s instead' % type(diff))
 
         self._diff = diff
-        self._format = format
         self.adds = 0
         self.removes = 0
-        # calculate diff size
-        self.diff_size = len(diff)
         self.diff_limit = diff_limit
-        self.cur_diff_size = 0
-        self.parsed = False
-        self.parsed_diff = []
+        self.limited_diff = False
         self.vcs = vcs
-
-        if format == 'gitdiff':
-            self.differ = self._highlight_line_difflib
-            self._parser = self._parse_gitdiff
-        else:
-            self.differ = self._highlight_line_udiff
-            self._parser = self._parse_udiff
-
-    def _copy_iterator(self):
-        """
-        make a fresh copy of generator, we should not iterate thru
-        an original as it's needed for repeating operations on
-        this instance of DiffProcessor
-        """
-        self.__udiff, iterator_copy = tee(self.__udiff)
-        return iterator_copy
-
-    def _escaper(self, string):
-        """
-        Escaper for diff escapes special chars and checks the diff limit
-
-        :param string:
-        """
-
-        self.cur_diff_size += len(string)
-
-        # escaper gets iterated on each .next() call and it checks if each
-        # parsed line doesn't exceed the diff limit
-        if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
-            raise DiffLimitExceeded('Diff Limit Exceeded')
-
-        def substitute(m):
-            groups = m.groups()
-            if groups[0]:
-                return '&amp;'
-            if groups[1]:
-                return '&lt;'
-            if groups[2]:
-                return '&gt;'
-            if groups[3]:
-                return '<u>\t</u>'
-            if groups[4]:
-                return '<u class="cr"></u>'
-            if groups[5]:
-                return ' <i></i>'
-            assert False
-
-        return self._escape_re.sub(substitute, safe_unicode(string))
-
-    def _line_counter(self, l):
-        """
-        Checks each line and bumps total adds/removes for this diff
-
-        :param l:
-        """
-        if l.startswith('+') and not l.startswith('+++'):
-            self.adds += 1
-        elif l.startswith('-') and not l.startswith('---'):
-            self.removes += 1
-        return safe_unicode(l)
-
-    def _highlight_line_difflib(self, line, next_):
-        """
-        Highlight inline changes in both lines.
-        """
-
-        if line['action'] == 'del':
-            old, new = line, next_
-        else:
-            old, new = next_, line
-
-        oldwords = self._token_re.split(old['line'])
-        newwords = self._token_re.split(new['line'])
-        sequence = difflib.SequenceMatcher(None, oldwords, newwords)
+        self.parsed = self._parse_gitdiff(inline_diff=inline_diff)
 
-        oldfragments, newfragments = [], []
-        for tag, i1, i2, j1, j2 in sequence.get_opcodes():
-            oldfrag = ''.join(oldwords[i1:i2])
-            newfrag = ''.join(newwords[j1:j2])
-            if tag != 'equal':
-                if oldfrag:
-                    oldfrag = '<del>%s</del>' % oldfrag
-                if newfrag:
-                    newfrag = '<ins>%s</ins>' % newfrag
-            oldfragments.append(oldfrag)
-            newfragments.append(newfrag)
-
-        old['line'] = "".join(oldfragments)
-        new['line'] = "".join(newfragments)
-
-    def _highlight_line_udiff(self, line, next_):
-        """
-        Highlight inline changes in both lines.
+    def _parse_gitdiff(self, inline_diff):
+        """Parse self._diff and return a list of dicts with meta info and chunks for each file.
+        Might set limited_diff.
+        Optionally, do an extra pass and to extra markup of one-liner changes.
         """
-        start = 0
-        limit = min(len(line['line']), len(next_['line']))
-        while start < limit and line['line'][start] == next_['line'][start]:
-            start += 1
-        end = -1
-        limit -= start
-        while -end <= limit and line['line'][end] == next_['line'][end]:
-            end -= 1
-        end += 1
-        if start or end:
-            def do(l):
-                last = end + len(l['line'])
-                if l['action'] == 'add':
-                    tag = 'ins'
-                else:
-                    tag = 'del'
-                l['line'] = '%s<%s>%s</%s>%s' % (
-                    l['line'][:start],
-                    tag,
-                    l['line'][start:last],
-                    tag,
-                    l['line'][last:]
-                )
-            do(line)
-            do(next_)
+        _files = [] # list of dicts with meta info and chunks
 
-    def _get_header(self, diff_chunk):
-        """
-        parses the diff header, and returns parts, and leftover diff
-        parts consists of 14 elements::
+        starts = [m.start() for m in self._diff_git_re.finditer(self._diff)]
+        starts.append(len(self._diff))
 
-            a_path, b_path, similarity_index, rename_from, rename_to,
-            old_mode, new_mode, new_file_mode, deleted_file_mode,
-            a_blob_id, b_blob_id, b_mode, a_file, b_file
-
-        :param diff_chunk:
-        """
+        for start, end in zip(starts, starts[1:]):
+            if self.diff_limit and end > self.diff_limit:
+                self.limited_diff = True
+                continue
 
-        match = None
-        if self.vcs == 'git':
-            match = self._git_header_re.match(diff_chunk)
-        elif self.vcs == 'hg':
-            match = self._hg_header_re.match(diff_chunk)
-        if match is None:
-            raise Exception('diff not recognized as valid %s diff' % self.vcs)
-        groups = match.groupdict()
-        rest = diff_chunk[match.end():]
-        if rest and not rest.startswith('@') and not rest.startswith('literal ') and not rest.startswith('delta '):
-            raise Exception('cannot parse diff header: %r followed by %r' % (diff_chunk[:match.end()], rest[:1000]))
-        difflines = imap(self._escaper, re.findall(r'.*\n|.+$', rest)) # don't split on \r as str.splitlines do
-        return groups, difflines
-
-    def _clean_line(self, line, command):
-        if command in ['+', '-', ' ']:
-            #only modify the line if it's actually a diff thing
-            line = line[1:]
-        return line
-
-    def _parse_gitdiff(self, inline_diff=True):
-        _files = []
-        diff_container = lambda arg: arg
-
-        ##split the diff in chunks of separate --git a/file b/file chunks
-        for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
-            head, diff = self._get_header(raw_diff)
+            head, diff_lines = _get_header(self.vcs, buffer(self._diff, start, end - start))
 
             op = None
             stats = {
@@ -391,75 +327,68 @@
             }
 
             if head['deleted_file_mode']:
-                op = 'D'
+                op = 'removed'
                 stats['binary'] = True
                 stats['ops'][DEL_FILENODE] = 'deleted file'
 
             elif head['new_file_mode']:
-                op = 'A'
+                op = 'added'
                 stats['binary'] = True
                 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
             else:  # modify operation, can be cp, rename, chmod
                 # CHMOD
                 if head['new_mode'] and head['old_mode']:
-                    op = 'M'
+                    op = 'modified'
                     stats['binary'] = True
                     stats['ops'][CHMOD_FILENODE] = ('modified file chmod %s => %s'
                                         % (head['old_mode'], head['new_mode']))
                 # RENAME
                 if (head['rename_from'] and head['rename_to']
                       and head['rename_from'] != head['rename_to']):
-                    op = 'R'
+                    op = 'renamed'
                     stats['binary'] = True
                     stats['ops'][RENAMED_FILENODE] = ('file renamed from %s to %s'
                                     % (head['rename_from'], head['rename_to']))
                 # COPY
                 if head.get('copy_from') and head.get('copy_to'):
-                    op = 'M'
+                    op = 'modified'
                     stats['binary'] = True
                     stats['ops'][COPIED_FILENODE] = ('file copied from %s to %s'
                                         % (head['copy_from'], head['copy_to']))
                 # FALL BACK: detect missed old style add or remove
                 if op is None:
                     if not head['a_file'] and head['b_file']:
-                        op = 'A'
+                        op = 'added'
                         stats['binary'] = True
                         stats['ops'][NEW_FILENODE] = 'new file'
 
                     elif head['a_file'] and not head['b_file']:
-                        op = 'D'
+                        op = 'removed'
                         stats['binary'] = True
                         stats['ops'][DEL_FILENODE] = 'deleted file'
 
                 # it's not ADD not DELETE
                 if op is None:
-                    op = 'M'
+                    op = 'modified'
                     stats['binary'] = True
                     stats['ops'][MOD_FILENODE] = 'modified file'
 
             # a real non-binary diff
             if head['a_file'] or head['b_file']:
-                try:
-                    chunks, _stats = self._parse_lines(diff)
-                    stats['binary'] = False
-                    stats['added'] = _stats[0]
-                    stats['deleted'] = _stats[1]
-                    # explicit mark that it's a modified file
-                    if op == 'M':
-                        stats['ops'][MOD_FILENODE] = 'modified file'
-
-                except DiffLimitExceeded:
-                    diff_container = lambda _diff: \
-                        LimitedDiffContainer(self.diff_limit,
-                                            self.cur_diff_size, _diff)
-                    break
+                chunks, added, deleted = _parse_lines(diff_lines)
+                stats['binary'] = False
+                stats['added'] = added
+                stats['deleted'] = deleted
+                # explicit mark that it's a modified file
+                if op == 'modified':
+                    stats['ops'][MOD_FILENODE] = 'modified file'
             else:  # Git binary patch (or empty diff)
                 # Git binary patch
                 if head['bin_patch']:
                     stats['ops'][BIN_FILENODE] = 'binary diff not shown'
                 chunks = []
 
-            if op == 'D' and chunks:
+            if op == 'removed' and chunks:
                 # a way of seeing deleted content could perhaps be nice - but
                 # not with the current UI
                 chunks = []
@@ -473,6 +402,7 @@
                   if _op not in [MOD_FILENODE]])
 
             _files.append({
+                'old_filename':     head['a_path'],
                 'filename':         head['b_path'],
                 'old_revision':     head['a_blob_id'],
                 'new_revision':     head['b_blob_id'],
@@ -482,274 +412,266 @@
             })
 
         if not inline_diff:
-            return diff_container(_files)
+            return _files
 
-        # highlight inline changes
+        # highlight inline changes when one del is followed by one add
         for diff_data in _files:
             for chunk in diff_data['chunks']:
                 lineiter = iter(chunk)
                 try:
-                    while 1:
-                        line = lineiter.next()
-                        if line['action'] not in ['unmod', 'context']:
-                            nextline = lineiter.next()
-                            if nextline['action'] in ['unmod', 'context'] or \
-                               nextline['action'] == line['action']:
-                                continue
-                            self.differ(line, nextline)
+                    peekline = lineiter.next()
+                    while True:
+                        # find a first del line
+                        while peekline['action'] != 'del':
+                            peekline = lineiter.next()
+                        delline = peekline
+                        peekline = lineiter.next()
+                        # if not followed by add, eat all following del lines
+                        if peekline['action'] != 'add':
+                            while peekline['action'] == 'del':
+                                peekline = lineiter.next()
+                            continue
+                        # found an add - make sure it is the only one
+                        addline = peekline
+                        try:
+                            peekline = lineiter.next()
+                        except StopIteration:
+                            # add was last line - ok
+                            _highlight_inline_diff(delline, addline)
+                            raise
+                        if peekline['action'] != 'add':
+                            # there was only one add line - ok
+                            _highlight_inline_diff(delline, addline)
                 except StopIteration:
                     pass
 
-        return diff_container(_files)
-
-    def _parse_udiff(self, inline_diff=True):
-        raise NotImplementedError()
-
-    def _parse_lines(self, diff):
-        """
-        Parse the diff and return data for the template.
-        """
-
-        stats = [0, 0]
-        (old_line, old_end, new_line, new_end) = (None, None, None, None)
-
-        try:
-            chunks = []
-            line = diff.next()
-
-            while True:
-                lines = []
-                chunks.append(lines)
-
-                match = self._chunk_re.match(line)
-
-                if not match:
-                    raise Exception('error parsing diff @@ line %r' % line)
-
-                gr = match.groups()
-                (old_line, old_end,
-                 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
-                old_line -= 1
-                new_line -= 1
-
-                context = len(gr) == 5
-                old_end += old_line
-                new_end += new_line
-
-                if context:
-                    # skip context only if it's first line
-                    if int(gr[0]) > 1:
-                        lines.append({
-                            'old_lineno': '...',
-                            'new_lineno': '...',
-                            'action':     'context',
-                            'line':       line,
-                        })
-
-                line = diff.next()
-
-                while old_line < old_end or new_line < new_end:
-                    if not line:
-                        raise Exception('error parsing diff - empty line at -%s+%s' % (old_line, new_line))
-
-                    affects_old = affects_new = False
-
-                    command = line[0]
-                    if command == '+':
-                        affects_new = True
-                        action = 'add'
-                        stats[0] += 1
-                    elif command == '-':
-                        affects_old = True
-                        action = 'del'
-                        stats[1] += 1
-                    elif command == ' ':
-                        affects_old = affects_new = True
-                        action = 'unmod'
-                    else:
-                        raise Exception('error parsing diff - unknown command in line %r at -%s+%s' % (line, old_line, new_line))
-
-                    if not self._newline_marker.match(line):
-                        old_line += affects_old
-                        new_line += affects_new
-                        lines.append({
-                            'old_lineno':   affects_old and old_line or '',
-                            'new_lineno':   affects_new and new_line or '',
-                            'action':       action,
-                            'line':         self._clean_line(line, command)
-                        })
-
-                    line = diff.next()
-
-                    if self._newline_marker.match(line):
-                        # we need to append to lines, since this is not
-                        # counted in the line specs of diff
-                        lines.append({
-                            'old_lineno':   '...',
-                            'new_lineno':   '...',
-                            'action':       'context',
-                            'line':         self._clean_line(line, command)
-                        })
-                        line = diff.next()
-                if old_line > old_end:
-                        raise Exception('error parsing diff - more than %s "-" lines at -%s+%s' % (old_end, old_line, new_line))
-                if new_line > new_end:
-                        raise Exception('error parsing diff - more than %s "+" lines at -%s+%s' % (new_end, old_line, new_line))
-        except StopIteration:
-            pass
-        if old_line != old_end or new_line != new_end:
-            raise Exception('diff processing broken when old %s<>%s or new %s<>%s line %r' % (old_line, old_end, new_line, new_end, line))
-
-        return chunks, stats
-
-    def _safe_id(self, idstring):
-        """Make a string safe for including in an id attribute.
-
-        The HTML spec says that id attributes 'must begin with
-        a letter ([A-Za-z]) and may be followed by any number
-        of letters, digits ([0-9]), hyphens ("-"), underscores
-        ("_"), colons (":"), and periods (".")'. These regexps
-        are slightly over-zealous, in that they remove colons
-        and periods unnecessarily.
-
-        Whitespace is transformed into underscores, and then
-        anything which is not a hyphen or a character that
-        matches \w (alphanumerics and underscore) is removed.
-
-        """
-        # Transform all whitespace to underscore
-        idstring = re.sub(r'\s', "_", idstring)
-        # Remove everything that is not a hyphen or a member of \w
-        idstring = re.sub(r'(?!-)\W', "", idstring).lower()
-        return idstring
-
-    def prepare(self, inline_diff=True):
-        """
-        Prepare the passed udiff for HTML rendering. It'l return a list
-        of dicts with diff information
-        """
-        parsed = self._parser(inline_diff=inline_diff)
-        self.parsed = True
-        self.parsed_diff = parsed
-        return parsed
-
-    def as_raw(self, diff_lines=None):
-        """
-        Returns raw string diff
-        """
-        return self._diff
-        #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
-
-    def as_html(self, table_class='code-difftable', line_class='line',
-                old_lineno_class='lineno old', new_lineno_class='lineno new',
-                no_lineno_class='lineno',
-                code_class='code', enable_comments=False, parsed_lines=None):
-        """
-        Return given diff as html table with customized css classes
-        """
-        def _link_to_if(condition, label, url):
-            """
-            Generates a link if condition is meet or just the label if not.
-            """
-
-            if condition:
-                return '''<a href="%(url)s">%(label)s</a>''' % {
-                    'url': url,
-                    'label': label
-                }
-            else:
-                return label
-        if not self.parsed:
-            self.prepare()
-
-        diff_lines = self.parsed_diff
-        if parsed_lines:
-            diff_lines = parsed_lines
-
-        _html_empty = True
-        _html = []
-        _html.append('''<table class="%(table_class)s">\n''' % {
-            'table_class': table_class
-        })
-
-        for diff in diff_lines:
-            for line in diff['chunks']:
-                _html_empty = False
-                for change in line:
-                    _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
-                        'lc': line_class,
-                        'action': change['action']
-                    })
-                    anchor_old_id = ''
-                    anchor_new_id = ''
-                    anchor_old = "%(filename)s_o%(oldline_no)s" % {
-                        'filename': self._safe_id(diff['filename']),
-                        'oldline_no': change['old_lineno']
-                    }
-                    anchor_new = "%(filename)s_n%(oldline_no)s" % {
-                        'filename': self._safe_id(diff['filename']),
-                        'oldline_no': change['new_lineno']
-                    }
-                    cond_old = (change['old_lineno'] != '...' and
-                                change['old_lineno'])
-                    cond_new = (change['new_lineno'] != '...' and
-                                change['new_lineno'])
-                    no_lineno = (change['old_lineno'] == '...' and
-                                 change['new_lineno'] == '...')
-                    if cond_old:
-                        anchor_old_id = 'id="%s"' % anchor_old
-                    if cond_new:
-                        anchor_new_id = 'id="%s"' % anchor_new
-                    ###########################################################
-                    # OLD LINE NUMBER
-                    ###########################################################
-                    _html.append('''\t<td %(a_id)s class="%(olc)s" %(colspan)s>''' % {
-                        'a_id': anchor_old_id,
-                        'olc': no_lineno_class if no_lineno else old_lineno_class,
-                        'colspan': 'colspan="2"' if no_lineno else ''
-                    })
-
-                    _html.append('''%(link)s''' % {
-                        'link': _link_to_if(True, change['old_lineno'],
-                                            '#%s' % anchor_old)
-                    })
-                    _html.append('''</td>\n''')
-                    ###########################################################
-                    # NEW LINE NUMBER
-                    ###########################################################
-
-                    if not no_lineno:
-                        _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
-                            'a_id': anchor_new_id,
-                            'nlc': new_lineno_class
-                        })
-
-                        _html.append('''%(link)s''' % {
-                            'link': _link_to_if(True, change['new_lineno'],
-                                                '#%s' % anchor_new)
-                        })
-                        _html.append('''</td>\n''')
-                    ###########################################################
-                    # CODE
-                    ###########################################################
-                    comments = '' if enable_comments else 'no-comment'
-                    _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
-                        'cc': code_class,
-                        'inc': comments
-                    })
-                    _html.append('''\n\t\t<div class="add-bubble"><div>&nbsp;</div></div><pre>%(code)s</pre>\n''' % {
-                        'code': change['line']
-                    })
-
-                    _html.append('''\t</td>''')
-                    _html.append('''\n</tr>\n''')
-        _html.append('''</table>''')
-        if _html_empty:
-            return None
-        return ''.join(_html)
+        return _files
 
     def stat(self):
         """
         Returns tuple of added, and removed lines for this instance
         """
         return self.adds, self.removes
+
+
+_escape_re = re.compile(r'(&)|(<)|(>)|(\t)|(\r)|(?<=.)( \n| $)')
+
+
+def _escaper(string):
+    """
+    Do HTML escaping/markup
+    """
+
+    def substitute(m):
+        groups = m.groups()
+        if groups[0]:
+            return '&amp;'
+        if groups[1]:
+            return '&lt;'
+        if groups[2]:
+            return '&gt;'
+        if groups[3]:
+            return '<u>\t</u>'
+        if groups[4]:
+            return '<u class="cr"></u>'
+        if groups[5]:
+            return ' <i></i>'
+        assert False
+
+    return _escape_re.sub(substitute, safe_unicode(string))
+
+
+_git_header_re = re.compile(r"""
+    ^diff[ ]--git[ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
+    (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
+       ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
+    (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
+       ^rename[ ]from[ ](?P<rename_from>.+)\n
+       ^rename[ ]to[ ](?P<rename_to>.+)(?:\n|$))?
+    (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
+    (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
+    (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
+        \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
+    (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
+    (?:^---[ ](a/(?P<a_file>.+?)|/dev/null)\t?(?:\n|$))?
+    (?:^\+\+\+[ ](b/(?P<b_file>.+?)|/dev/null)\t?(?:\n|$))?
+""", re.VERBOSE | re.MULTILINE)
+
+
+_hg_header_re = re.compile(r"""
+    ^diff[ ]--git[ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
+    (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
+       ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
+    (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
+    (?:^rename[ ]from[ ](?P<rename_from>.+)\n
+       ^rename[ ]to[ ](?P<rename_to>.+)(?:\n|$))?
+    (?:^copy[ ]from[ ](?P<copy_from>.+)\n
+       ^copy[ ]to[ ](?P<copy_to>.+)(?:\n|$))?
+    (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
+    (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
+    (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
+        \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
+    (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
+    (?:^---[ ](a/(?P<a_file>.+?)|/dev/null)\t?(?:\n|$))?
+    (?:^\+\+\+[ ](b/(?P<b_file>.+?)|/dev/null)\t?(?:\n|$))?
+""", re.VERBOSE | re.MULTILINE)
+
+
+def _get_header(vcs, diff_chunk):
+    """
+    Parses a Git diff for a single file (header and chunks) and returns a tuple with:
+
+    1. A dict with meta info:
+
+        a_path, b_path, similarity_index, rename_from, rename_to,
+        old_mode, new_mode, new_file_mode, deleted_file_mode,
+        a_blob_id, b_blob_id, b_mode, a_file, b_file
+
+    2. An iterator yielding lines with simple HTML markup.
+    """
+    match = None
+    if vcs == 'git':
+        match = _git_header_re.match(diff_chunk)
+    elif vcs == 'hg':
+        match = _hg_header_re.match(diff_chunk)
+    if match is None:
+        raise Exception('diff not recognized as valid %s diff' % vcs)
+    meta_info = match.groupdict()
+    rest = diff_chunk[match.end():]
+    if rest and not rest.startswith('@') and not rest.startswith('literal ') and not rest.startswith('delta '):
+        raise Exception('cannot parse %s diff header: %r followed by %r' % (vcs, diff_chunk[:match.end()], rest[:1000]))
+    diff_lines = (_escaper(m.group(0)) for m in re.finditer(r'.*\n|.+$', rest)) # don't split on \r as str.splitlines do
+    return meta_info, diff_lines
+
+
+_chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
+_newline_marker = re.compile(r'^\\ No newline at end of file')
+
+
+def _parse_lines(diff_lines):
+    """
+    Given an iterator of diff body lines, parse them and return a dict per
+    line and added/removed totals.
+    """
+    added = deleted = 0
+    old_line = old_end = new_line = new_end = None
+
+    try:
+        chunks = []
+        line = diff_lines.next()
+
+        while True:
+            lines = []
+            chunks.append(lines)
+
+            match = _chunk_re.match(line)
+
+            if not match:
+                raise Exception('error parsing diff @@ line %r' % line)
+
+            gr = match.groups()
+            (old_line, old_end,
+             new_line, new_end) = [int(x or 1) for x in gr[:-1]]
+            old_line -= 1
+            new_line -= 1
+
+            context = len(gr) == 5
+            old_end += old_line
+            new_end += new_line
+
+            if context:
+                # skip context only if it's first line
+                if int(gr[0]) > 1:
+                    lines.append({
+                        'old_lineno': '...',
+                        'new_lineno': '...',
+                        'action':     'context',
+                        'line':       line,
+                    })
+
+            line = diff_lines.next()
+
+            while old_line < old_end or new_line < new_end:
+                if not line:
+                    raise Exception('error parsing diff - empty line at -%s+%s' % (old_line, new_line))
+
+                affects_old = affects_new = False
+
+                command = line[0]
+                if command == '+':
+                    affects_new = True
+                    action = 'add'
+                    added += 1
+                elif command == '-':
+                    affects_old = True
+                    action = 'del'
+                    deleted += 1
+                elif command == ' ':
+                    affects_old = affects_new = True
+                    action = 'unmod'
+                else:
+                    raise Exception('error parsing diff - unknown command in line %r at -%s+%s' % (line, old_line, new_line))
+
+                if not _newline_marker.match(line):
+                    old_line += affects_old
+                    new_line += affects_new
+                    lines.append({
+                        'old_lineno':   affects_old and old_line or '',
+                        'new_lineno':   affects_new and new_line or '',
+                        'action':       action,
+                        'line':         line[1:],
+                    })
+
+                line = diff_lines.next()
+
+                if _newline_marker.match(line):
+                    # we need to append to lines, since this is not
+                    # counted in the line specs of diff
+                    lines.append({
+                        'old_lineno':   '...',
+                        'new_lineno':   '...',
+                        'action':       'context',
+                        'line':         line,
+                    })
+                    line = diff_lines.next()
+            if old_line > old_end:
+                raise Exception('error parsing diff - more than %s "-" lines at -%s+%s' % (old_end, old_line, new_line))
+            if new_line > new_end:
+                raise Exception('error parsing diff - more than %s "+" lines at -%s+%s' % (new_end, old_line, new_line))
+    except StopIteration:
+        pass
+    if old_line != old_end or new_line != new_end:
+        raise Exception('diff processing broken when old %s<>%s or new %s<>%s line %r' % (old_line, old_end, new_line, new_end, line))
+
+    return chunks, added, deleted
+
+# Used for inline highlighter word split, must match the substitutions in _escaper
+_token_re = re.compile(r'()(&amp;|&lt;|&gt;|<u>\t</u>|<u class="cr"></u>| <i></i>|\W+?)')
+
+
+def _highlight_inline_diff(old, new):
+    """
+    Highlight simple add/remove in two lines given as info dicts. They are
+    modified in place and given markup with <del>/<ins>.
+    """
+    assert old['action'] == 'del'
+    assert new['action'] == 'add'
+
+    oldwords = _token_re.split(old['line'])
+    newwords = _token_re.split(new['line'])
+    sequence = difflib.SequenceMatcher(None, oldwords, newwords)
+
+    oldfragments, newfragments = [], []
+    for tag, i1, i2, j1, j2 in sequence.get_opcodes():
+        oldfrag = ''.join(oldwords[i1:i2])
+        newfrag = ''.join(newwords[j1:j2])
+        if tag != 'equal':
+            if oldfrag:
+                oldfrag = '<del>%s</del>' % oldfrag
+            if newfrag:
+                newfrag = '<ins>%s</ins>' % newfrag
+        oldfragments.append(oldfrag)
+        newfragments.append(newfrag)
+
+    old['line'] = "".join(oldfragments)
+    new['line'] = "".join(newfragments)
--- a/kallithea/lib/exceptions.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/exceptions.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,6 +27,8 @@
 
 from webob.exc import HTTPClientError
 
+from kallithea.lib.utils2 import safe_str
+
 
 class LdapUsernameError(Exception):
     pass
@@ -57,10 +59,6 @@
     pass
 
 
-class StatusChangeOnClosedPullRequestError(Exception):
-    pass
-
-
 class AttachedForksError(Exception):
     pass
 
@@ -86,8 +84,8 @@
         from kallithea.lib.utils2 import safe_int
         _code = CONFIG.get('lock_ret_code')
         self.code = safe_int(_code, self.code)
-        self.title = self.explanation = ('Repository `%s` locked by '
-                                         'user `%s`' % (reponame, username))
+        self.title = self.explanation = safe_str(
+            'Repository `%s` locked by user `%s`' % (reponame, username))
         super(HTTPLockedRC, self).__init__(*args, **kwargs)
 
 
@@ -99,10 +97,6 @@
     pass
 
 
-class UserInvalidException(Exception):
-    pass
-
-
 class RepositoryCreationError(Exception):
     pass
 
--- a/kallithea/lib/ext_json.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/ext_json.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,7 @@
 """
 Extended JSON encoder for json
 
-json.org do not specify how date time can be represented - monkeypatch it to do something.
+json.org does not specify how date time can be represented - monkeypatch it to do something.
 """
 
 import datetime
@@ -22,6 +22,7 @@
     return (value.tzinfo is not None
             and value.tzinfo.utcoffset(value) is not None)
 
+
 def _obj_dump(obj):
     """
     Custom function for dumping objects to JSON, if obj has __json__ attribute
--- a/kallithea/lib/graphmod.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/graphmod.py	Sun Mar 31 21:28:56 2019 +0200
@@ -19,11 +19,17 @@
 
 nullrev = -1
 
+
 def _first_known_ancestors(parentrev_func, minrev, knownrevs, head):
     """
-    Walk DAG defined by parentrev_func.
-    Return most immediate ancestors of head that are in knownrevs.
-    minrev is lower bound on knownrevs.
+    Return the apparent parents of the head revision in a filtered DAG.
+    This is like calling plain parentrev_func, except that if the parent has
+    been filtered (isn't in knownrevs), recurse on its parents.
+    For ancestors that are unknown in knownrevs, the revision number is negated.
+    (This means that a Mercurial revision can have more than 2 "parents".)
+
+    parentrev_func defines the full DAG.
+    knownrevs contains the subset we care about. minrev is lower bound on knownrevs.
     """
     pending = set([head])
     seen = set()
@@ -40,6 +46,7 @@
             seen.add(r)
     return ancestors
 
+
 def graph_data(repo, revs):
     """Return a DAG with colored edge information for revs
 
@@ -56,7 +63,9 @@
     dag = _dagwalker(repo, revs)
     return list(_colored(repo, dag))
 
+
 def _dagwalker(repo, revs):
+    """Iterate over revs, yielding revs (highest first) and parents to show in the graph."""
     if not revs:
         return
 
@@ -66,7 +75,7 @@
         def parentrev_func(rev):
             return [x.revision for x in repo[rev].parents]
 
-    minrev = revs[-1] # assuming sorted reverse
+    minrev = revs[-1] # assuming sorted with highest revision numbers first
     knownrevs = set(revs)
     acache = {}
     for rev in revs:
@@ -96,15 +105,19 @@
         parents.
     """
     branch_cache = {}
+
     def branch(rev):
+        """Return branch for rev, using cache for efficiency.
+        For Mercurial, always return the named branch name (which may be 'default').
+        For Git, return a branch name for branch heads, otherwise None."""
         if rev not in branch_cache:
             branch_cache[rev] = repo[rev].branch
         return branch_cache[rev]
 
-    row = []
-    colors = {}
-    obs = {}
-    newcolor = 1
+    row = []  # the ancestor revision that each column is tracking
+    colors = {}  # color number for revisions - set by descendants
+    obs = {}  # obsolete flag for revisions - set by descendants
+    newcolor = 1  # the next available color
 
     for (rev, dagparents) in dag:
 
@@ -121,24 +134,26 @@
         # Add unknown parents to nextrow
         tmprow = row[:]
         tmprow[col:col + 1] = reversed(addparents) # highest revs first (to the right), dead ends last (to the left)
-        # Stop looking for non-existing ancestors
+        # Filter old fake parents but keep new ones
         nextrow = []
         for r in tmprow:
-            if r > nullrev or r in dagparents:
+            if r >= 0 or r in dagparents:
                 nextrow.append(r)
             else:
                 colors.pop(r)
                 obs.pop(r)
 
-        # Set colors for the parents
+        # Let color and obs for this rev be "inherited" by the first "parent"
         color = colors.pop(rev)
         if addparents:
             b = branch(rev)
+            searching = True
             for p in reversed(addparents):
-                obs[p] = int(repo[p].obsolete)
-                if b and branch(abs(p)) == b:
+                obs[p] = int(repo[p].obsolete) if p >= 0 else 0
+                if searching and branch(abs(p)) in [b, None]:
+                    # This is the first parent on the same branch - inherit the color
                     colors[p] = color
-                    b = None
+                    searching = False # make sure we don't give the color away twice
                 else:
                     colors[p] = newcolor
                     newcolor += 1
@@ -155,5 +170,9 @@
         # Yield and move on
         closing = int(repo[rev].closesbranch)
         obsolete = int(repo[rev].obsolete)
-        yield ((col, color), edges, closing, obsolete)
+        bumped = int(repo[rev].bumped)
+        divergent = int(repo[rev].divergent)
+        extinct = int(repo[rev].extinct)
+        unstable = int(repo[rev].unstable)
+        yield ((col, color), edges, closing, obsolete, bumped, divergent, extinct, unstable)
         row = nextrow
--- a/kallithea/lib/helpers.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/helpers.py	Sun Mar 31 21:28:56 2019 +0200
@@ -20,49 +20,34 @@
 import hashlib
 import json
 import StringIO
-import math
 import logging
 import re
 import urlparse
 import textwrap
 
+from beaker.cache import cache_region
 from pygments.formatters.html import HtmlFormatter
 from pygments import highlight as code_highlight
-from pylons import url
-from pylons.i18n.translation import _, ungettext
+from tg.i18n import ugettext as _
 
 from webhelpers.html import literal, HTML, escape
-from webhelpers.html.tools import *
-from webhelpers.html.builder import make_tag
-from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
-    end_form, file, hidden, image, javascript_link, link_to, \
-    link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
-    submit, text, password, textarea, title, ul, xml_declaration, radio, \
-    form as insecure_form
-from webhelpers.html.tools import auto_link, button_to, highlight, \
-    js_obfuscate, mail_to, strip_links, strip_tags, tag_re
-from webhelpers.number import format_byte_size, format_bit_size
+from webhelpers.html.tags import checkbox, end_form, hidden, link_to, \
+    select, submit, text, password, textarea, radio, form as insecure_form
+from webhelpers.number import format_byte_size
 from webhelpers.pylonslib import Flash as _Flash
 from webhelpers.pylonslib.secure_form import secure_form, authentication_token
-from webhelpers.text import chop_at, collapse, convert_accented_entities, \
-    convert_misc_entities, lchop, plural, rchop, remove_formatting, \
-    replace_whitespace, urlify, truncate, wrap_paragraphs
-from webhelpers.date import time_ago_in_words
-from webhelpers.paginate import Page as _Page
+from webhelpers.text import chop_at, truncate, wrap_paragraphs
 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
     convert_boolean_attrs, NotGiven, _make_safe_id_component
 
+from kallithea.config.routing import url
 from kallithea.lib.annotate import annotate_highlight
-from kallithea.lib.utils import repo_name_slug, get_custom_lexer
+from kallithea.lib.pygmentsutils import get_custom_lexer
 from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str, \
-    get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
-    safe_int
-from kallithea.lib.markup_renderer import MarkupRenderer, url_re
+    time_to_datetime, AttributeDict, safe_int, MENTIONS_REGEX
+from kallithea.lib.markup_renderer import url_re
 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
-from kallithea.config.conf import DATE_FORMAT, DATETIME_FORMAT
-from kallithea.model.changeset_status import ChangesetStatusModel
-from kallithea.model.db import URL_SEP, Permission
 
 log = logging.getLogger(__name__)
 
@@ -73,12 +58,13 @@
     from kallithea import CONFIG
     try:
         parts = CONFIG.get('canonical_url', '').split('://', 1)
-        kargs['host'] = parts[1].split('/', 1)[0]
+        kargs['host'] = parts[1]
         kargs['protocol'] = parts[0]
     except IndexError:
         kargs['qualified'] = True
     return url(*args, **kargs)
 
+
 def canonical_hostname():
     '''Return canonical hostname of system'''
     from kallithea import CONFIG
@@ -89,6 +75,7 @@
         parts = url('home', qualified=True).split('://', 1)
         return parts[1].split('/', 1)[0]
 
+
 def html_escape(s):
     """Return string with all html escaped.
     This is also safe for javascript in html but not necessarily correct.
@@ -98,7 +85,7 @@
         .replace(">", "&gt;")
         .replace("<", "&lt;")
         .replace('"', "&quot;")
-        .replace("'", "&apos;")
+        .replace("'", "&apos;") # Note: this is HTML5 not HTML4 and might not work in mails
         )
 
 def js(value):
@@ -131,8 +118,27 @@
         .replace('>', r'\x3e')
     )
 
-def shorter(s, size=20):
-    postfix = '...'
+
+def jshtml(val):
+    """HTML escapes a string value, then converts the resulting string
+    to its corresponding JavaScript representation (see `js`).
+
+    This is used when a plain-text string (possibly containing special
+    HTML characters) will be used by a script in an HTML context (e.g.
+    element.innerHTML or jQuery's 'html' method).
+
+    If in doubt, err on the side of using `jshtml` over `js`, since it's
+    better to escape too much than too little.
+    """
+    return js(escape(val))
+
+
+def shorter(s, size=20, firstline=False, postfix='...'):
+    """Truncate s to size, including the postfix string if truncating.
+    If firstline, truncate at newline.
+    """
+    if firstline:
+        s = s.split('\n', 1)[0].rstrip()
     if len(s) > size:
         return s[:size - len(postfix)] + postfix
     return s
@@ -147,6 +153,7 @@
     convert_boolean_attrs(attrs, ["disabled"])
     return HTML.input(**attrs)
 
+
 reset = _reset
 safeid = _make_safe_id_component
 
@@ -163,22 +170,6 @@
     return 'C-%s-%s' % (short_id(raw_id), hashlib.md5(safe_str(path)).hexdigest()[:12])
 
 
-class _GetError(object):
-    """Get error from form_errors, and represent it as span wrapped error
-    message
-
-    :param field_name: field to fetch errors for
-    :param form_errors: form errors dict
-    """
-
-    def __call__(self, field_name, form_errors):
-        tmpl = """<span class="error_msg">%s</span>"""
-        if form_errors and field_name in form_errors:
-            return literal(tmpl % form_errors.get(field_name))
-
-get_error = _GetError()
-
-
 class _FilesBreadCrumbs(object):
 
     def __call__(self, repo_name, rev, paths):
@@ -203,6 +194,7 @@
 
         return literal('/'.join(url_l))
 
+
 files_breadcrumbs = _FilesBreadCrumbs()
 
 
@@ -217,7 +209,7 @@
     def _wrap_code(self, source):
         for cnt, it in enumerate(source):
             i, t = it
-            t = '<div id="L%s">%s</div>' % (cnt + 1, t)
+            t = '<span id="L%s">%s</span>' % (cnt + 1, t)
             yield i, t
 
     def _wrap_tablelinenos(self, inner):
@@ -271,20 +263,21 @@
         # some configurations seem to mess up the formatting...
         if nocls:
             yield 0, ('<table class="%stable">' % self.cssclass +
-                      '<tr><td><div class="linenodiv" '
-                      'style="background-color: #f0f0f0; padding-right: 10px">'
-                      '<pre style="line-height: 125%">' +
-                      ls + '</pre></div></td><td id="hlcode" class="code">')
+                      '<tr><td><div class="linenodiv">'
+                      '<pre>' + ls + '</pre></div></td>'
+                      '<td id="hlcode" class="code">')
         else:
             yield 0, ('<table class="%stable">' % self.cssclass +
-                      '<tr><td class="linenos"><div class="linenodiv"><pre>' +
-                      ls + '</pre></div></td><td id="hlcode" class="code">')
+                      '<tr><td class="linenos"><div class="linenodiv">'
+                      '<pre>' + ls + '</pre></div></td>'
+                      '<td id="hlcode" class="code">')
         yield 0, dummyoutfile.getvalue()
         yield 0, '</td></tr></table>'
 
 
 _whitespace_re = re.compile(r'(\t)|( )(?=\n|</div>)')
 
+
 def _markup_whitespace(m):
     groups = m.groups()
     if groups[0]:
@@ -292,9 +285,11 @@
     if groups[1]:
         return ' <i></i>'
 
+
 def markup_whitespace(s):
     return _whitespace_re.sub(_markup_whitespace, s)
 
+
 def pygmentize(filenode, **kwargs):
     """
     pygmentize function using pygments
@@ -369,9 +364,9 @@
             author = escape(changeset.author)
             date = changeset.date
             message = escape(changeset.message)
-            tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
-                            " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
-                            "</b> %s<br/></div>") % (author, date, message)
+            tooltip_html = ("<b>Author:</b> %s<br/>"
+                            "<b>Date:</b> %s</b><br/>"
+                            "<b>Message:</b> %s") % (author, date, message)
 
             lnk_format = show_id(changeset)
             uri = link_to(
@@ -379,8 +374,8 @@
                     url('changeset_home', repo_name=repo_name,
                         revision=changeset.raw_id),
                     style=get_color_string(changeset.raw_id),
-                    class_='tooltip safe-html-title',
-                    title=tooltip_html
+                    **{'data-toggle': 'popover',
+                       'data-content': tooltip_html}
                   )
 
             uri += '\n'
@@ -390,10 +385,6 @@
     return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs)))
 
 
-def is_following_repo(repo_name, user_id):
-    from kallithea.model.scm import ScmModel
-    return ScmModel().is_following_repo(repo_name, user_id)
-
 class _Message(object):
     """A message returned by ``Flash.pop_messages()``.
 
@@ -416,6 +407,7 @@
     def __html__(self):
         return escape(safe_unicode(self.message))
 
+
 class Flash(_Flash):
 
     def __call__(self, message, category=None, ignore_duplicate=False, logf=None):
@@ -442,11 +434,12 @@
 
         The return value is a list of ``Message`` objects.
         """
-        from pylons import session
+        from tg import session
         messages = session.pop(self.session_key, [])
         session.save()
         return [_Message(*m) for m in messages]
 
+
 flash = Flash()
 
 #==============================================================================
@@ -454,9 +447,8 @@
 #==============================================================================
 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, PullRequest
 
-age = lambda  x, y=False: _age(x, y)
+age = lambda x, y=False: _age(x, y)
 capitalize = lambda x: x.capitalize()
 email = author_email
 short_id = lambda x: x[:12]
@@ -483,7 +475,7 @@
 
 def fmt_date(date):
     if date:
-        return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf8')
+        return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf-8')
 
     return ""
 
@@ -508,20 +500,19 @@
     return _type == 'hg'
 
 
+@cache_region('long_term', 'user_or_none')
 def user_or_none(author):
+    """Try to match email part of VCS committer string with a local user - or return None"""
+    from kallithea.model.db import User
     email = author_email(author)
     if email:
-        user = User.get_by_email(email, case_insensitive=True, cache=True)
-        if user is not None:
-            return user
-
-    user = User.get_by_username(author_name(author), case_insensitive=True, cache=True)
-    if user is not None:
-        return user
-
+        return User.get_by_email(email, cache=True) # cache will only use sql_cache_short
     return None
 
+
 def email_or_none(author):
+    """Try to match email part of VCS committer string with a local user.
+    Return primary email of user, email part of the specified author name, or None."""
     if not author:
         return None
     user = user_or_none(author)
@@ -536,9 +527,11 @@
     # No valid email, not a valid user in the system, none!
     return None
 
+
 def person(author, show_attr="username"):
     """Find the user identified by 'author', return one of the users attributes,
     default to the username attribute, None if there is no user"""
+    from kallithea.model.db import User
     # attr to return from fetched user
     person_getter = lambda usr: getattr(usr, show_attr)
 
@@ -555,10 +548,11 @@
 
 
 def person_by_id(id_, show_attr="username"):
+    from kallithea.model.db import User
     # attr to return from fetched user
     person_getter = lambda usr: getattr(usr, show_attr)
 
-    #maybe it's an ID ?
+    # maybe it's an ID ?
     if str(id_).isdigit() or isinstance(id_, int):
         id_ = int(id_)
         user = User.get(id_)
@@ -567,29 +561,6 @@
     return id_
 
 
-def desc_stylize(value):
-    """
-    converts tags from value into html equivalent
-
-    :param value:
-    """
-    if not value:
-        return ''
-
-    value = re.sub(r'\[see\ \=&gt;\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
-                   '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
-    value = re.sub(r'\[license\ \=&gt;\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
-                   '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
-    value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=&gt;\ *([a-zA-Z0-9\-\/]*)\]',
-                   '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
-    value = re.sub(r'\[(lang|language)\ \=&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
-                   '<div class="metatag" tag="lang">\\2</div>', value)
-    value = re.sub(r'\[([a-z]+)\]',
-                  '<div class="metatag" tag="\\1">\\1</div>', value)
-
-    return value
-
-
 def boolicon(value):
     """Returns boolean value of a value, represented as small html image of true/false
     icons
@@ -653,9 +624,9 @@
                 lbl = rev[:12]
                 title_ = _('Changeset %s not found') % lbl
             if parse_cs:
-                return link_to(lbl, url_, title=title_, class_='tooltip')
-            return link_to(lbl, url_, raw_id=rev.raw_id, repo_name=repo_name,
-                           class_='lazy-cs' if lazy_cs else '')
+                return link_to(lbl, url_, title=title_, **{'data-toggle': 'tooltip'})
+            return link_to(lbl, url_, class_='lazy-cs' if lazy_cs else '',
+                           **{'data-raw_id': rev.raw_id, 'data-repo_name': repo_name})
 
         def _get_op(rev_txt):
             _op = None
@@ -698,7 +669,7 @@
         _rev = '%s...%s' % (_name1, _name2)
 
         compare_view = (
-            ' <div class="compare_view tooltip" title="%s">'
+            ' <div class="compare_view" data-toggle="tooltip" title="%s">'
             '<a href="%s">%s</a> </div>' % (
                 _('Show all combined changesets %s->%s') % (
                     revs_ids[0][:12], revs_ids[-1][:12]
@@ -762,6 +733,7 @@
         return group_name
 
     def get_pull_request():
+        from kallithea.model.db import PullRequest
         pull_request_id = action_params
         nice_id = PullRequest.make_nice_id(pull_request_id)
 
@@ -833,9 +805,9 @@
     if feed:
         action = action_str[0].replace('[', '').replace(']', '')
     else:
-        action = action_str[0]\
-            .replace('[', '<span class="journal_highlight">')\
-            .replace(']', '</span>')
+        action = action_str[0] \
+            .replace('[', '<b>') \
+            .replace(']', '</b>')
 
     action_params_func = lambda: ""
 
@@ -850,9 +822,9 @@
         if len(x) > 1:
             action, action_params = x
 
-        tmpl = """<i class="%s" alt="%s"></i>"""
         ico = action_map.get(action, ['', '', ''])[2]
-        return literal(tmpl % (ico, action))
+        html = """<i class="%s"></i>""" % ico
+        return literal(html)
 
     # returned callbacks we need to call to get
     return [lambda: literal(action), action_params_func, action_parser_icon]
@@ -862,15 +834,34 @@
 #==============================================================================
 # PERMS
 #==============================================================================
-from kallithea.lib.auth import HasPermissionAny, HasPermissionAll, \
-HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
-HasRepoGroupPermissionAny
+from kallithea.lib.auth import HasPermissionAny, \
+    HasRepoPermissionLevel, HasRepoGroupPermissionLevel
 
 
 #==============================================================================
 # GRAVATAR URL
 #==============================================================================
-def gravatar(email_address, cls='', size=30, ssl_enabled=True):
+def gravatar_div(email_address, cls='', size=30, **div_attributes):
+    """Return an html literal with a span around a gravatar if they are enabled.
+    Extra keyword parameters starting with 'div_' will get the prefix removed
+    and '_' changed to '-' and be used as attributes on the div. The default
+    class is 'gravatar'.
+    """
+    from tg import tmpl_context as c
+    if not c.visual.use_gravatar:
+        return ''
+    if 'div_class' not in div_attributes:
+        div_attributes['div_class'] = "gravatar"
+    attributes = []
+    for k, v in sorted(div_attributes.items()):
+        assert k.startswith('div_'), k
+        attributes.append(' %s="%s"' % (k[4:].replace('_', '-'), escape(v)))
+    return literal("""<span%s>%s</span>""" %
+                   (''.join(attributes),
+                    gravatar(email_address, cls=cls, size=size)))
+
+
+def gravatar(email_address, cls='', size=30):
     """return html element of the gravatar
 
     This method will return an <img> with the resolution double the size (for
@@ -878,268 +869,49 @@
     empty then we fallback to using an icon.
 
     """
-    src = gravatar_url(email_address, size*2, ssl_enabled)
+    from tg import tmpl_context as c
+    if not c.visual.use_gravatar:
+        return ''
 
-    #  here it makes sense to use style="width: ..." (instead of, say, a
-    # stylesheet) because we using this to generate a high-res (retina) size
-    tmpl = '<img alt="" class="{cls}" style="width: {size}px; height: {size}px" src="{src}"/>'
+    src = gravatar_url(email_address, size * 2)
 
-    # if src is empty then there was no gravatar, so we use a font icon
-    if not src:
-        tmpl = """<i class="icon-user {cls}" style="font-size: {size}px;"></i>"""
-
-    tmpl = tmpl.format(cls=cls, size=size, src=src)
-    return literal(tmpl)
+    if src:
+        # here it makes sense to use style="width: ..." (instead of, say, a
+        # stylesheet) because we using this to generate a high-res (retina) size
+        html = ('<i class="icon-gravatar {cls}"'
+                ' style="font-size: {size}px;background-size: {size}px;background-image: url(\'{src}\')"'
+                '></i>').format(cls=cls, size=size, src=src)
 
-def gravatar_url(email_address, size=30, ssl_enabled=True):
-    # doh, we need to re-import those to mock it later
-    from pylons import url
-    from pylons import tmpl_context as c
+    else:
+        # if src is empty then there was no gravatar, so we use a font icon
+        html = ("""<i class="icon-user {cls}" style="font-size: {size}px;"></i>"""
+            .format(cls=cls, size=size, src=src))
+
+    return literal(html)
 
-    _def = 'anonymous@kallithea-scm.org'  # default gravatar
-    _use_gravatar = c.visual.use_gravatar
-    _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
 
-    email_address = email_address or _def
-
-    if not _use_gravatar or not email_address or email_address == _def:
+def gravatar_url(email_address, size=30, default=''):
+    # doh, we need to re-import those to mock it later
+    from kallithea.config.routing import url
+    from kallithea.model.db import User
+    from tg import tmpl_context as c
+    if not c.visual.use_gravatar:
         return ""
 
-    if _use_gravatar:
-        _md5 = lambda s: hashlib.md5(s).hexdigest()
-
-        tmpl = _gravatar_url
-        parsed_url = urlparse.urlparse(url.current(qualified=True))
-        tmpl = tmpl.replace('{email}', email_address)\
-                   .replace('{md5email}', _md5(safe_str(email_address).lower())) \
-                   .replace('{netloc}', parsed_url.netloc)\
-                   .replace('{scheme}', parsed_url.scheme)\
-                   .replace('{size}', safe_str(size))
-        return tmpl
-
-class Page(_Page):
-    """
-    Custom pager to match rendering style with YUI paginator
-    """
-
-    def _get_pos(self, cur_page, max_page, items):
-        edge = (items / 2) + 1
-        if (cur_page <= edge):
-            radius = max(items / 2, items - cur_page)
-        elif (max_page - cur_page) < edge:
-            radius = (items - 1) - (max_page - cur_page)
-        else:
-            radius = items / 2
-
-        left = max(1, (cur_page - (radius)))
-        right = min(max_page, cur_page + (radius))
-        return left, cur_page, right
-
-    def _range(self, regexp_match):
-        """
-        Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
-
-        Arguments:
-
-        regexp_match
-            A "re" (regular expressions) match object containing the
-            radius of linked pages around the current page in
-            regexp_match.group(1) as a string
-
-        This function is supposed to be called as a callable in
-        re.sub.
-
-        """
-        radius = int(regexp_match.group(1))
-
-        # Compute the first and last page number within the radius
-        # e.g. '1 .. 5 6 [7] 8 9 .. 12'
-        # -> leftmost_page  = 5
-        # -> rightmost_page = 9
-        leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
-                                                            self.last_page,
-                                                            (radius * 2) + 1)
-        nav_items = []
+    _def = 'anonymous@kallithea-scm.org'  # default gravatar
+    email_address = email_address or _def
 
-        # Create a link to the first page (unless we are on the first page
-        # or there would be no need to insert '..' spacers)
-        if self.page != self.first_page and self.first_page < leftmost_page:
-            nav_items.append(self._pagerlink(self.first_page, self.first_page))
-
-        # Insert dots if there are pages between the first page
-        # and the currently displayed page range
-        if leftmost_page - self.first_page > 1:
-            # Wrap in a SPAN tag if nolink_attr is set
-            text_ = '..'
-            if self.dotdot_attr:
-                text_ = HTML.span(c=text_, **self.dotdot_attr)
-            nav_items.append(text_)
-
-        for thispage in xrange(leftmost_page, rightmost_page + 1):
-            # Highlight the current page number and do not use a link
-            text_ = str(thispage)
-            if thispage == self.page:
-                # Wrap in a SPAN tag if nolink_attr is set
-                if self.curpage_attr:
-                    text_ = HTML.span(c=text_, **self.curpage_attr)
-                nav_items.append(text_)
-            # Otherwise create just a link to that page
-            else:
-                nav_items.append(self._pagerlink(thispage, text_))
-
-        # Insert dots if there are pages between the displayed
-        # page numbers and the end of the page range
-        if self.last_page - rightmost_page > 1:
-            text_ = '..'
-            # Wrap in a SPAN tag if nolink_attr is set
-            if self.dotdot_attr:
-                text_ = HTML.span(c=text_, **self.dotdot_attr)
-            nav_items.append(text_)
-
-        # Create a link to the very last page (unless we are on the last
-        # page or there would be no need to insert '..' spacers)
-        if self.page != self.last_page and rightmost_page < self.last_page:
-            nav_items.append(self._pagerlink(self.last_page, self.last_page))
-
-        #_page_link = url.current()
-        #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
-        #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
-        return self.separator.join(nav_items)
-
-    def pager(self, format='~2~', page_param='page', partial_param='partial',
-        show_if_single_page=False, separator=' ', onclick=None,
-        symbol_first='<<', symbol_last='>>',
-        symbol_previous='<', symbol_next='>',
-        link_attr={'class': 'pager_link', 'rel': 'prerender'},
-        curpage_attr={'class': 'pager_curpage'},
-        dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
-
-        self.curpage_attr = curpage_attr
-        self.separator = separator
-        self.pager_kwargs = kwargs
-        self.page_param = page_param
-        self.partial_param = partial_param
-        self.onclick = onclick
-        self.link_attr = link_attr
-        self.dotdot_attr = dotdot_attr
+    if email_address == _def:
+        return default
 
-        # Don't show navigator if there is no more than one page
-        if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
-            return ''
-
-        from string import Template
-        # Replace ~...~ in token format by range of pages
-        result = re.sub(r'~(\d+)~', self._range, format)
-
-        # Interpolate '%' variables
-        result = Template(result).safe_substitute({
-            'first_page': self.first_page,
-            'last_page': self.last_page,
-            'page': self.page,
-            'page_count': self.page_count,
-            'items_per_page': self.items_per_page,
-            'first_item': self.first_item,
-            'last_item': self.last_item,
-            'item_count': self.item_count,
-            'link_first': self.page > self.first_page and \
-                    self._pagerlink(self.first_page, symbol_first) or '',
-            'link_last': self.page < self.last_page and \
-                    self._pagerlink(self.last_page, symbol_last) or '',
-            'link_previous': self.previous_page and \
-                    self._pagerlink(self.previous_page, symbol_previous) \
-                    or HTML.span(symbol_previous, class_="yui-pg-previous"),
-            'link_next': self.next_page and \
-                    self._pagerlink(self.next_page, symbol_next) \
-                    or HTML.span(symbol_next, class_="yui-pg-next")
-        })
-
-        return literal(result)
-
-
-#==============================================================================
-# REPO PAGER, PAGER FOR REPOSITORY
-#==============================================================================
-class RepoPage(Page):
-
-    def __init__(self, collection, page=1, items_per_page=20,
-                 item_count=None, url=None, **kwargs):
-
-        """Create a "RepoPage" instance. special pager for paging
-        repository
-        """
-        self._url_generator = url
-
-        # Safe the kwargs class-wide so they can be used in the pager() method
-        self.kwargs = kwargs
-
-        # Save a reference to the collection
-        self.original_collection = collection
-
-        self.collection = collection
-
-        # The self.page is the number of the current page.
-        # The first page has the number 1!
-        try:
-            self.page = int(page)  # make it int() if we get it as a string
-        except (ValueError, TypeError):
-            self.page = 1
-
-        self.items_per_page = items_per_page
-
-        # Unless the user tells us how many items the collections has
-        # we calculate that ourselves.
-        if item_count is not None:
-            self.item_count = item_count
-        else:
-            self.item_count = len(self.collection)
-
-        # Compute the number of the first and last available page
-        if self.item_count > 0:
-            self.first_page = 1
-            self.page_count = int(math.ceil(float(self.item_count) /
-                                            self.items_per_page))
-            self.last_page = self.first_page + self.page_count - 1
-
-            # Make sure that the requested page number is the range of
-            # valid pages
-            if self.page > self.last_page:
-                self.page = self.last_page
-            elif self.page < self.first_page:
-                self.page = self.first_page
-
-            # Note: the number of items on this page can be less than
-            #       items_per_page if the last page is not full
-            self.first_item = max(0, (self.item_count) - (self.page *
-                                                          items_per_page))
-            self.last_item = ((self.item_count - 1) - items_per_page *
-                              (self.page - 1))
-
-            self.items = list(self.collection[self.first_item:self.last_item + 1])
-
-            # Links to previous and next page
-            if self.page > self.first_page:
-                self.previous_page = self.page - 1
-            else:
-                self.previous_page = None
-
-            if self.page < self.last_page:
-                self.next_page = self.page + 1
-            else:
-                self.next_page = None
-
-        # No items available
-        else:
-            self.first_page = None
-            self.page_count = 0
-            self.last_page = None
-            self.first_item = None
-            self.last_item = None
-            self.previous_page = None
-            self.next_page = None
-            self.items = []
-
-        # This is a subclass of the 'list' type. Initialise the list now.
-        list.__init__(self, reversed(self.items))
+    parsed_url = urlparse.urlparse(url.current(qualified=True))
+    url = (c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL ) \
+               .replace('{email}', email_address) \
+               .replace('{md5email}', hashlib.md5(safe_str(email_address).lower()).hexdigest()) \
+               .replace('{netloc}', parsed_url.netloc) \
+               .replace('{scheme}', parsed_url.scheme) \
+               .replace('{size}', safe_str(size))
+    return url
 
 
 def changed_tooltip(nodes):
@@ -1160,27 +932,6 @@
         return ': ' + _('No files')
 
 
-def repo_link(groups_and_repos):
-    """
-    Makes a breadcrumbs link to repo within a group
-    joins &raquo; on each group to create a fancy link
-
-    ex::
-        group >> subgroup >> repo
-
-    :param groups_and_repos:
-    :param last_url:
-    """
-    groups, just_name, repo_name = groups_and_repos
-    last_url = url('summary_home', repo_name=repo_name)
-    last_link = link_to(just_name, last_url)
-
-    def make_link(group):
-        return link_to(group.name,
-                       url('repos_group_home', group_name=group.group_name))
-    return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
-
-
 def fancy_file_stats(stats):
     """
     Displays a fancy two colored bar for number of added/deleted
@@ -1191,31 +942,11 @@
     from kallithea.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
         MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
 
-    def cgen(l_type, a_v, d_v):
-        mapping = {'tr': 'top-right-rounded-corner-mid',
-                   'tl': 'top-left-rounded-corner-mid',
-                   'br': 'bottom-right-rounded-corner-mid',
-                   'bl': 'bottom-left-rounded-corner-mid'}
-        map_getter = lambda x: mapping[x]
-
-        if l_type == 'a' and d_v:
-            #case when added and deleted are present
-            return ' '.join(map(map_getter, ['tl', 'bl']))
-
-        if l_type == 'a' and not d_v:
-            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
-
-        if l_type == 'd' and a_v:
-            return ' '.join(map(map_getter, ['tr', 'br']))
-
-        if l_type == 'd' and not a_v:
-            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
-
     a, d = stats['added'], stats['deleted']
     width = 100
 
     if stats['binary']:
-        #binary mode
+        # binary mode
         lbl = ''
         bin_op = 1
 
@@ -1235,15 +966,15 @@
             lbl += _('rename')
             bin_op = RENAMED_FILENODE
 
-        #chmod can go with other operations
+        # chmod can go with other operations
         if CHMOD_FILENODE in stats['ops']:
             _org_lbl = _('chmod')
             lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
 
         #import ipdb;ipdb.set_trace()
-        b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
+        b_d = '<div class="bin bin%s progress-bar" style="width:100%%">%s</div>' % (bin_op, lbl)
         b_a = '<div class="bin bin1" style="width:0%"></div>'
-        return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
+        return literal('<div style="width:%spx" class="progress">%s%s</div>' % (width, b_a, b_d))
 
     t = stats['added'] + stats['deleted']
     unit = float(width) / (t or 1)
@@ -1254,7 +985,7 @@
     p_sum = a_p + d_p
 
     if p_sum > width:
-        #adjust the percentage to be == 100% since we adjusted to 9
+        # adjust the percentage to be == 100% since we adjusted to 9
         if a_p > d_p:
             a_p = a_p - (p_sum - width)
         else:
@@ -1263,166 +994,219 @@
     a_v = a if a > 0 else ''
     d_v = d if d > 0 else ''
 
-    d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
-        cgen('a', a_v, d_v), a_p, a_v
+    d_a = '<div class="added progress-bar" style="width:%s%%">%s</div>' % (
+        a_p, a_v
+    )
+    d_d = '<div class="deleted progress-bar" style="width:%s%%">%s</div>' % (
+        d_p, d_v
     )
-    d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
-        cgen('d', a_v, d_v), d_p, d_v
-    )
-    return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
+    return literal('<div class="progress" style="width:%spx">%s%s</div>' % (width, d_a, d_d))
+
+
+_URLIFY_RE = re.compile(r'''
+# URL markup
+(?P<url>%s) |
+# @mention markup
+(?P<mention>%s) |
+# Changeset hash markup
+(?<!\w|[-_])
+  (?P<hash>[0-9a-f]{12,40})
+(?!\w|[-_]) |
+# Markup of *bold text*
+(?:
+  (?:^|(?<=\s))
+  (?P<bold> [*] (?!\s) [^*\n]* (?<!\s) [*] )
+  (?![*\w])
+) |
+# "Stylize" markup
+\[see\ \=&gt;\ *(?P<seen>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
+\[license\ \=&gt;\ *(?P<license>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
+\[(?P<tagtype>requires|recommends|conflicts|base)\ \=&gt;\ *(?P<tagvalue>[a-zA-Z0-9\-\/]*)\] |
+\[(?:lang|language)\ \=&gt;\ *(?P<lang>[a-zA-Z\-\/\#\+]*)\] |
+\[(?P<tag>[a-z]+)\]
+''' % (url_re.pattern, MENTIONS_REGEX.pattern),
+    re.VERBOSE | re.MULTILINE | re.IGNORECASE)
 
 
-def _urlify_text(s):
-    """
-    Extract urls from text and make html links out of them
+def urlify_text(s, repo_name=None, link_=None, truncate=None, stylize=False, truncatef=truncate):
     """
-    def url_func(match_obj):
-        url_full = match_obj.group(1)
-        return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
-    return url_re.sub(url_func, s)
+    Parses given text message and make literal html with markup.
+    The text will be truncated to the specified length.
+    Hashes are turned into changeset links to specified repository.
+    URLs links to what they say.
+    Issues are linked to given issue-server.
+    If link_ is provided, all text not already linking somewhere will link there.
+    """
 
-def urlify_text(s, truncate=None, stylize=False, truncatef=truncate):
-    """
-    Extract urls from text and make literal html links out of them
-    """
-    if truncate is not None:
-        s = truncatef(s, truncate)
+    def _replace(match_obj):
+        url = match_obj.group('url')
+        if url is not None:
+            return '<a href="%(url)s">%(url)s</a>' % {'url': url}
+        mention = match_obj.group('mention')
+        if mention is not None:
+            return '<b>%s</b>' % mention
+        hash_ = match_obj.group('hash')
+        if hash_ is not None and repo_name is not None:
+            from kallithea.config.routing import url  # doh, we need to re-import url to mock it later
+            return '<a class="changeset_hash" href="%(url)s">%(hash)s</a>' % {
+                 'url': url('changeset_home', repo_name=repo_name, revision=hash_),
+                 'hash': hash_,
+                }
+        bold = match_obj.group('bold')
+        if bold is not None:
+            return '<b>*%s*</b>' % _urlify(bold[1:-1])
+        if stylize:
+            seen = match_obj.group('seen')
+            if seen:
+                return '<div class="label label-meta" data-tag="see">see =&gt; %s</div>' % seen
+            license = match_obj.group('license')
+            if license:
+                return '<div class="label label-meta" data-tag="license"><a href="http:\/\/www.opensource.org/licenses/%s">%s</a></div>' % (license, license)
+            tagtype = match_obj.group('tagtype')
+            if tagtype:
+                tagvalue = match_obj.group('tagvalue')
+                return '<div class="label label-meta" data-tag="%s">%s =&gt; <a href="/%s">%s</a></div>' % (tagtype, tagtype, tagvalue, tagvalue)
+            lang = match_obj.group('lang')
+            if lang:
+                return '<div class="label label-meta" data-tag="lang">%s</div>' % lang
+            tag = match_obj.group('tag')
+            if tag:
+                return '<div class="label label-meta" data-tag="%s">%s</div>' % (tag, tag)
+        return match_obj.group(0)
+
+    def _urlify(s):
+        """
+        Extract urls from text and make html links out of them
+        """
+        return _URLIFY_RE.sub(_replace, s)
+
+    if truncate is None:
+        s = s.rstrip()
+    else:
+        s = truncatef(s, truncate, whole_word=True)
     s = html_escape(s)
-    if stylize:
-        s = desc_stylize(s)
-    s = _urlify_text(s)
+    s = _urlify(s)
+    if repo_name is not None:
+        s = urlify_issues(s, repo_name)
+    if link_ is not None:
+        # make href around everything that isn't a href already
+        s = linkify_others(s, link_)
+    s = s.replace('\r\n', '<br/>').replace('\n', '<br/>')
+    # Turn HTML5 into more valid HTML4 as required by some mail readers.
+    # (This is not done in one step in html_escape, because character codes like
+    # &#123; risk to be seen as an issue reference due to the presence of '#'.)
+    s = s.replace("&apos;", "&#39;")
     return literal(s)
 
-def urlify_changesets(text_, repository):
-    """
-    Extract revision ids from changeset and make link from them
-
-    :param text_:
-    :param repository: repo name to build the URL with
-    """
-    from pylons import url  # doh, we need to re-import url to mock it later
-
-    def url_func(match_obj):
-        rev = match_obj.group(0)
-        return '<a class="revision-link" href="%(url)s">%(rev)s</a>' % {
-         'url': url('changeset_home', repo_name=repository, revision=rev),
-         'rev': rev,
-        }
-
-    return re.sub(r'(?:^|(?<=[\s(),]))([0-9a-fA-F]{12,40})(?=$|\s|[.,:()])', url_func, text_)
 
 def linkify_others(t, l):
+    """Add a default link to html with links.
+    HTML doesn't allow nesting of links, so the outer link must be broken up
+    in pieces and give space for other links.
+    """
     urls = re.compile(r'(\<a.*?\<\/a\>)',)
     links = []
     for e in urls.split(t):
-        if not urls.match(e):
+        if e.strip() and not urls.match(e):
             links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
         else:
             links.append(e)
 
     return ''.join(links)
 
-def urlify_commit(text_, repository, link_=None):
-    """
-    Parses given text message and makes proper links.
-    issues are linked to given issue-server, and rest is a changeset link
-    if link_ is given, in other case it's a plain text
 
-    :param text_:
-    :param repository:
-    :param link_: changeset link
-    """
-    newtext = html_escape(text_)
-
-    # urlify changesets - extract revisions and make link out of them
-    newtext = urlify_changesets(newtext, repository)
-
-    # extract http/https links and make them real urls
-    newtext = _urlify_text(newtext)
-
-    newtext = urlify_issues(newtext, repository, link_)
-
-    return literal(newtext)
-
-def urlify_issues(newtext, repository, link_=None):
-    from kallithea import CONFIG as conf
-
-    # allow multiple issue servers to be used
-    valid_indices = [
-        x.group(1)
-        for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
-        if x and 'issue_server_link%s' % x.group(1) in conf
-        and 'issue_prefix%s' % x.group(1) in conf
-    ]
-
-    if valid_indices:
-        log.debug('found issue server suffixes `%s` during valuation of: %s',
-                  ','.join(valid_indices), newtext)
-
-    for pattern_index in valid_indices:
-        ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
-        ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
-        ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
-
-        log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s',
-                  pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
-                     ISSUE_PREFIX)
-
-        URL_PAT = re.compile(ISSUE_PATTERN)
-
-        def url_func(match_obj):
-            pref = ''
-            if match_obj.group().startswith(' '):
-                pref = ' '
-
-            issue_id = ''.join(match_obj.groups())
-            issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
-            if repository:
-                issue_url = issue_url.replace('{repo}', repository)
-                repo_name = repository.split(URL_SEP)[-1]
-                issue_url = issue_url.replace('{repo_name}', repo_name)
-
-            return (
-                '%(pref)s<a class="%(cls)s" href="%(url)s">'
-                '%(issue-prefix)s%(id-repr)s'
-                '</a>'
-                ) % {
-                 'pref': pref,
-                 'cls': 'issue-tracker-link',
-                 'url': issue_url,
-                 'id-repr': issue_id,
-                 'issue-prefix': ISSUE_PREFIX,
-                 'serv': ISSUE_SERVER_LNK,
-                }
-        newtext = URL_PAT.sub(url_func, newtext)
-        log.debug('processed prefix:`%s` => %s', pattern_index, newtext)
-
-    # if we actually did something above
-    if link_:
-        # wrap not links into final link => link_
-        newtext = linkify_others(newtext, link_)
-    return newtext
+# Global variable that will hold the actual urlify_issues function body.
+# Will be set on first use when the global configuration has been read.
+_urlify_issues_f = None
 
 
-def rst(source):
-    return literal('<div class="rst-block">%s</div>' %
-                   MarkupRenderer.rst(source))
+def urlify_issues(newtext, repo_name):
+    """Urlify issue references according to .ini configuration"""
+    global _urlify_issues_f
+    if _urlify_issues_f is None:
+        from kallithea import CONFIG
+        from kallithea.model.db import URL_SEP
+        assert CONFIG['sqlalchemy.url'] # make sure config has been loaded
+
+        # Build chain of urlify functions, starting with not doing any transformation
+        tmp_urlify_issues_f = lambda s: s
+
+        issue_pat_re = re.compile(r'issue_pat(.*)')
+        for k in CONFIG.keys():
+            # Find all issue_pat* settings that also have corresponding server_link and prefix configuration
+            m = issue_pat_re.match(k)
+            if m is None:
+                continue
+            suffix = m.group(1)
+            issue_pat = CONFIG.get(k)
+            issue_server_link = CONFIG.get('issue_server_link%s' % suffix)
+            issue_sub = CONFIG.get('issue_sub%s' % suffix)
+            if not issue_pat or not issue_server_link or issue_sub is None: # issue_sub can be empty but should be present
+                log.error('skipping incomplete issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_sub)
+                continue
+
+            # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound
+            try:
+                issue_re = re.compile(issue_pat)
+            except re.error as e:
+                log.error('skipping invalid issue pattern %r: %r -> %r %r. Error: %s', suffix, issue_pat, issue_server_link, issue_sub, str(e))
+                continue
+
+            log.debug('issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_sub)
+
+            def issues_replace(match_obj,
+                               issue_server_link=issue_server_link, issue_sub=issue_sub):
+                try:
+                    issue_url = match_obj.expand(issue_server_link)
+                except (IndexError, re.error) as e:
+                    log.error('invalid issue_url setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
+                    issue_url = issue_server_link
+                issue_url = issue_url.replace('{repo}', repo_name)
+                issue_url = issue_url.replace('{repo_name}', repo_name.split(URL_SEP)[-1])
+                # if issue_sub is empty use the matched issue reference verbatim
+                if not issue_sub:
+                    issue_text = match_obj.group()
+                else:
+                    try:
+                        issue_text = match_obj.expand(issue_sub)
+                    except (IndexError, re.error) as e:
+                        log.error('invalid issue_sub setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
+                        issue_text = match_obj.group()
+
+                return (
+                    '<a class="issue-tracker-link" href="%(url)s">'
+                    '%(text)s'
+                    '</a>'
+                    ) % {
+                     'url': issue_url,
+                     'text': issue_text,
+                    }
+            tmp_urlify_issues_f = (lambda s,
+                                          issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f:
+                                   issue_re.sub(issues_replace, chain_f(s)))
+
+        # Set tmp function globally - atomically
+        _urlify_issues_f = tmp_urlify_issues_f
+
+    return _urlify_issues_f(newtext)
 
 
-def rst_w_mentions(source):
+def render_w_mentions(source, repo_name=None):
     """
-    Wrapped rst renderer with @mention highlighting
+    Render plain text with revision hashes and issue references urlified
+    and with @mention highlighting.
+    """
+    s = safe_unicode(source)
+    s = urlify_text(s, repo_name=repo_name)
+    return literal('<div class="formatted-fixed">%s</div>' % s)
 
-    :param source:
-    """
-    return literal('<div class="rst-block">%s</div>' %
-                   MarkupRenderer.rst_with_mentions(source))
 
 def short_ref(ref_type, ref_name):
     if ref_type == 'rev':
         return short_id(ref_name)
     return ref_name
 
+
 def link_to_ref(repo_name, ref_type, ref_name, rev=None):
     """
     Return full markup for a href to changeset_home for a changeset.
@@ -1440,15 +1224,19 @@
         l = literal('%s (%s)' % (l, link_to(short_id(rev), url('changeset_home', repo_name=repo_name, revision=rev))))
     return l
 
+
 def changeset_status(repo, revision):
+    from kallithea.model.changeset_status import ChangesetStatusModel
     return ChangesetStatusModel().get_status(repo, revision)
 
 
 def changeset_status_lbl(changeset_status):
-    return dict(ChangesetStatus.STATUSES).get(changeset_status)
+    from kallithea.model.db import ChangesetStatus
+    return ChangesetStatus.get_status_lbl(changeset_status)
 
 
 def get_permission_name(key):
+    from kallithea.model.db import Permission
     return dict(Permission.PERMS).get(key)
 
 
--- a/kallithea/lib/hooks.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/hooks.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,7 +26,6 @@
 """
 
 import os
-import sys
 import time
 import binascii
 
@@ -35,12 +34,12 @@
 from kallithea.lib.utils import action_logger
 from kallithea.lib.vcs.backends.base import EmptyChangeset
 from kallithea.lib.exceptions import HTTPLockedRC, UserCreationError
-from kallithea.lib.utils2 import safe_str, _extract_extras
-from kallithea.model.db import Repository, User
+from kallithea.lib.utils import make_ui, setup_cache_regions
+from kallithea.lib.utils2 import safe_str, safe_unicode, _extract_extras
+from kallithea.model.db import Repository, User, Ui
 
 
 def _get_scm_size(alias, root_path):
-
     if not alias.startswith('.'):
         alias += '.'
 
@@ -67,29 +66,23 @@
 
 
 def repo_size(ui, repo, hooktype=None, **kwargs):
-    """
-    Presents size of repository after push
-
-    :param ui:
-    :param repo:
-    :param hooktype:
-    """
-
+    """Presents size of repository after push"""
     size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
 
     last_cs = repo[len(repo) - 1]
 
-    msg = ('Repository size .hg:%s repo:%s total:%s\n'
+    msg = ('Repository size .hg: %s Checkout: %s Total: %s\n'
            'Last revision is now r%s:%s\n') % (
         size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
     )
-
-    sys.stdout.write(msg)
+    ui.status(msg)
 
 
-def pre_push(ui, repo, **kwargs):
-    # pre push function, currently used to ban pushing when
-    # repository is locked
+def push_lock_handling(ui, repo, **kwargs):
+    """Pre push function, currently used to ban pushing when repository is locked.
+
+    Called as Mercurial hook prechangegroup.push_lock_handling or from the Git pre-receive hook calling handle_git_pre_receive.
+    """
     ex = _extract_extras()
 
     usr = User.get_by_username(ex.username)
@@ -99,13 +92,14 @@
         # on that proper return code is server to client
         _http_ret = HTTPLockedRC(ex.repository, locked_by)
         if str(_http_ret.code).startswith('2'):
-            #2xx Codes don't raise exceptions
-            sys.stdout.write(_http_ret.title)
+            # 2xx Codes don't raise exceptions
+            ui.status(safe_str(_http_ret.title))
         else:
             raise _http_ret
 
 
-def pre_pull(ui, repo, **kwargs):
+def pull_lock_handling(ui, repo, **kwargs):
+    """Called as Mercurial hook preoutgoing.pull_lock_handling or from Kallithea before invoking Git"""
     # pre pull function ...
     ex = _extract_extras()
     if ex.locked_by[0]:
@@ -114,18 +108,16 @@
         # on that proper return code is server to client
         _http_ret = HTTPLockedRC(ex.repository, locked_by)
         if str(_http_ret.code).startswith('2'):
-            #2xx Codes don't raise exceptions
-            sys.stdout.write(_http_ret.title)
+            # 2xx Codes don't raise exceptions
+            ui.status(safe_str(_http_ret.title))
         else:
             raise _http_ret
 
 
 def log_pull_action(ui, repo, **kwargs):
-    """
-    Logs user last pull action
+    """Logs user last pull action, and also handle locking
 
-    :param ui:
-    :param repo:
+    Called as Mercurial hook outgoing.pull_logger or from Kallithea before invoking Git.
     """
     ex = _extract_extras()
 
@@ -143,23 +135,28 @@
     if ex.make_lock is not None and ex.make_lock:
         Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id)
         #msg = 'Made lock on repo `%s`' % repository
-        #sys.stdout.write(msg)
+        #ui.status(msg)
 
     if ex.locked_by[0]:
         locked_by = User.get(ex.locked_by[0]).username
         _http_ret = HTTPLockedRC(ex.repository, locked_by)
         if str(_http_ret.code).startswith('2'):
-            #2xx Codes don't raise exceptions
-            sys.stdout.write(_http_ret.title)
+            # 2xx Codes don't raise exceptions
+            ui.status(safe_str(_http_ret.title))
     return 0
 
 
 def log_push_action(ui, repo, **kwargs):
     """
-    Register that changes have been pushed.
-    Mercurial invokes this directly as a hook, git uses handle_git_receive.
+    Register that changes have been pushed - log it *and* invalidate caches.
+    Note: It is not only logging, but also the side effect invalidating cahes!
+    The function should perhaps be renamed.
+
+    Called as Mercurial hook changegroup.push_logger or from the Git
+    post-receive hook calling handle_git_post_receive ... or from scm _handle_push.
+
+    Revisions are passed in different hack-ish ways.
     """
-
     ex = _extract_extras()
 
     action_tmpl = ex.action + ':%s'
@@ -188,6 +185,9 @@
     action = action_tmpl % ','.join(revs)
     action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
 
+    from kallithea.model.scm import ScmModel
+    ScmModel().mark_for_invalidation(ex.repository)
+
     # extension hook call
     from kallithea import EXTENSIONS
     callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
@@ -198,15 +198,14 @@
 
     if ex.make_lock is not None and not ex.make_lock:
         Repository.unlock(Repository.get_by_repo_name(ex.repository))
-        msg = 'Released lock on repo `%s`\n' % ex.repository
-        sys.stdout.write(msg)
+        ui.status(safe_str('Released lock on repo `%s`\n' % ex.repository))
 
     if ex.locked_by[0]:
         locked_by = User.get(ex.locked_by[0]).username
         _http_ret = HTTPLockedRC(ex.repository, locked_by)
         if str(_http_ret.code).startswith('2'):
-            #2xx Codes don't raise exceptions
-            sys.stdout.write(_http_ret.title)
+            # 2xx Codes don't raise exceptions
+            ui.status(safe_str(_http_ret.title))
 
     return 0
 
@@ -226,7 +225,7 @@
      'created_on',
      'enable_downloads',
      'repo_id',
-     'user_id',
+     'owner_id',
      'enable_statistics',
      'clone_uri',
      'fork_id',
@@ -308,7 +307,7 @@
      'created_on',
      'enable_downloads',
      'repo_id',
-     'user_id',
+     'owner_id',
      'enable_statistics',
      'clone_uri',
      'fork_id',
@@ -366,40 +365,32 @@
     return 0
 
 
-def handle_git_pre_receive(repo_path, revs, env):
-    return handle_git_receive(repo_path, revs, env, hook_type='pre')
-
-def handle_git_post_receive(repo_path, revs, env):
-    return handle_git_receive(repo_path, revs, env, hook_type='post')
-
-def handle_git_receive(repo_path, revs, env, hook_type):
+def _hook_environment(repo_path):
     """
-    A really hacky method that is run by git post-receive hook and logs
-    an push action together with pushed revisions. It's executed by subprocess
-    thus needs all info to be able to create a on the fly pylons environment,
-    connect to database and run the logging code. Hacky as sh*t but works.
+    Create a light-weight environment for stand-alone scripts and return an UI and the
+    db repository.
 
-    :param repo_path:
-    :param revs:
-    :param env:
+    Git hooks are executed as subprocess of Git while Kallithea is waiting, and
+    they thus need enough info to be able to create an app environment and
+    connect to the database.
     """
     from paste.deploy import appconfig
     from sqlalchemy import engine_from_config
     from kallithea.config.environment import load_environment
-    from kallithea.model import init_model
-    from kallithea.model.db import Ui
-    from kallithea.lib.utils import make_ui
-    extras = _extract_extras(env)
+    from kallithea.model.base import init_model
 
-    path, ini_name = os.path.split(extras['config'])
-    conf = appconfig('config:%s' % ini_name, relative_to=path)
-    load_environment(conf.global_conf, conf.local_conf, test_env=False,
-                     test_index=False)
+    extras = _extract_extras()
+    ini_file_path = extras['config']
+    #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging
+    app_conf = appconfig('config:%s' % ini_file_path)
+    conf = load_environment(app_conf.global_conf, app_conf.local_conf)
 
-    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    setup_cache_regions(conf)
+
+    engine = engine_from_config(conf, 'sqlalchemy.')
     init_model(engine)
 
-    baseui = make_ui('db')
+    repo_path = safe_unicode(repo_path)
     # fix if it's not a bare repo
     if repo_path.endswith(os.sep + '.git'):
         repo_path = repo_path[:-5]
@@ -409,59 +400,72 @@
         raise OSError('Repository %s not found in database'
                       % (safe_str(repo_path)))
 
-    _hooks = dict(baseui.configitems('hooks')) or {}
+    baseui = make_ui('db')
+    return baseui, repo
+
 
-    if hook_type == 'pre':
-        repo = repo.scm_instance
-    else:
-        #post push shouldn't use the cached instance never
-        repo = repo.scm_instance_no_cache()
+def handle_git_pre_receive(repo_path, git_stdin_lines):
+    """Called from Git pre-receive hook"""
+    baseui, repo = _hook_environment(repo_path)
+    scm_repo = repo.scm_instance
+    push_lock_handling(baseui, scm_repo)
+    return 0
+
 
-    if hook_type == 'pre':
-        pre_push(baseui, repo)
+def handle_git_post_receive(repo_path, git_stdin_lines):
+    """Called from Git post-receive hook"""
+    baseui, repo = _hook_environment(repo_path)
 
+    # the post push hook should never use the cached instance
+    scm_repo = repo.scm_instance_no_cache()
+
+    _hooks = dict(baseui.configitems('hooks')) or {}
     # if push hook is enabled via web interface
-    elif hook_type == 'post' and _hooks.get(Ui.HOOK_PUSH):
+    if _hooks.get(Ui.HOOK_PUSH_LOG):
         rev_data = []
-        for l in revs:
-            old_rev, new_rev, ref = l.split(' ')
+        for l in git_stdin_lines:
+            old_rev, new_rev, ref = l.strip().split(' ')
             _ref_data = ref.split('/')
             if _ref_data[1] in ['tags', 'heads']:
                 rev_data.append({'old_rev': old_rev,
                                  'new_rev': new_rev,
                                  'ref': ref,
                                  'type': _ref_data[1],
-                                 'name': _ref_data[2].strip()})
+                                 'name': '/'.join(_ref_data[2:])})
 
         git_revs = []
-
         for push_ref in rev_data:
             _type = push_ref['type']
             if _type == 'heads':
                 if push_ref['old_rev'] == EmptyChangeset().raw_id:
                     # update the symbolic ref if we push new repo
-                    if repo.is_empty():
-                        repo._repo.refs.set_symbolic_ref('HEAD',
+                    if scm_repo.is_empty():
+                        scm_repo._repo.refs.set_symbolic_ref('HEAD',
                                             'refs/heads/%s' % push_ref['name'])
 
-                    cmd = ['for-each-ref', '--format=%(refname)','refs/heads/*']
-                    heads = repo.run_git_command(cmd)[0]
-                    cmd = ['log', push_ref['new_rev'],
-                           '--reverse', '--pretty=format:%H', '--not']
-                    heads = heads.replace(push_ref['ref'], '')
-                    for l in heads.splitlines():
-                        cmd.append(l.strip())
-                    git_revs += repo.run_git_command(cmd)[0].splitlines()
+                    # build exclude list without the ref
+                    cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*']
+                    stdout, stderr = scm_repo.run_git_command(cmd)
+                    ref = push_ref['ref']
+                    heads = [head for head in stdout.splitlines() if head != ref]
+                    # now list the git revs while excluding from the list
+                    cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H']
+                    cmd.append('--not')
+                    cmd.extend(heads) # empty list is ok
+                    stdout, stderr = scm_repo.run_git_command(cmd)
+                    git_revs += stdout.splitlines()
 
                 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
-                    #delete branch case
+                    # delete branch case
                     git_revs += ['delete_branch=>%s' % push_ref['name']]
                 else:
                     cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref,
                            '--reverse', '--pretty=format:%H']
-                    git_revs += repo.run_git_command(cmd)[0].splitlines()
+                    stdout, stderr = scm_repo.run_git_command(cmd)
+                    git_revs += stdout.splitlines()
 
             elif _type == 'tags':
                 git_revs += ['tag=>%s' % push_ref['name']]
 
-        log_push_action(baseui, repo, _git_revs=git_revs)
+        log_push_action(baseui, scm_repo, _git_revs=git_revs)
+    return 0
--- a/kallithea/lib/indexers/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/indexers/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,12 +28,12 @@
 import os
 import sys
 import logging
-from os.path import dirname as dn, join as jn
+from os.path import dirname
 
 # Add location of top level folder to sys.path
-sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
+sys.path.append(dirname(dirname(dirname(os.path.realpath(__file__)))))
 
-from whoosh.analysis import RegexTokenizer, LowercaseFilter
+from whoosh.analysis import RegexTokenizer, LowercaseFilter, IDTokenizer
 from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType, DATETIME
 from whoosh.formats import Characters
 from whoosh.highlight import highlight as whoosh_highlight, HtmlFormatter, ContextFragmenter
@@ -44,16 +44,51 @@
 # CUSTOM ANALYZER wordsplit + lowercase filter
 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
 
-#INDEX SCHEMA DEFINITION
+# CUSTOM ANALYZER wordsplit + lowercase filter, for emailaddr-like text
+#
+# This is useful to:
+# - avoid removing "stop words" from text
+# - search case-insensitively
+#
+EMAILADDRANALYZER = RegexTokenizer() | LowercaseFilter()
+
+# CUSTOM ANALYZER raw-string + lowercase filter
+#
+# This is useful to:
+# - avoid tokenization
+# - avoid removing "stop words" from text
+# - search case-insensitively
+#
+ICASEIDANALYZER = IDTokenizer() | LowercaseFilter()
+
+# CUSTOM ANALYZER raw-string
+#
+# This is useful to:
+# - avoid tokenization
+# - avoid removing "stop words" from text
+#
+IDANALYZER = IDTokenizer()
+
+# CUSTOM ANALYZER wordsplit + lowercase filter, for pathname-like text
+#
+# This is useful to:
+# - avoid removing "stop words" from text
+# - search case-insensitively
+#
+PATHANALYZER = RegexTokenizer() | LowercaseFilter()
+
+# INDEX SCHEMA DEFINITION
 SCHEMA = Schema(
     fileid=ID(unique=True),
-    owner=TEXT(),
-    repository=TEXT(stored=True),
-    path=TEXT(stored=True),
+    owner=TEXT(analyzer=EMAILADDRANALYZER),
+    # this field preserves case of repository name for exact matching
+    repository_rawname=TEXT(analyzer=IDANALYZER),
+    repository=TEXT(stored=True, analyzer=ICASEIDANALYZER),
+    path=TEXT(stored=True, analyzer=PATHANALYZER),
     content=FieldType(format=Characters(), analyzer=ANALYZER,
                       scorable=True, stored=True),
     modtime=STORED(),
-    extension=TEXT(stored=True)
+    extension=TEXT(stored=True, analyzer=PATHANALYZER)
 )
 
 IDX_NAME = 'HG_INDEX'
@@ -64,22 +99,25 @@
     raw_id=ID(unique=True, stored=True),
     date=NUMERIC(stored=True),
     last=BOOLEAN(),
-    owner=TEXT(),
-    repository=ID(unique=True, stored=True),
-    author=TEXT(stored=True),
+    owner=TEXT(analyzer=EMAILADDRANALYZER),
+    # this field preserves case of repository name for exact matching
+    # and unique-ness in index table
+    repository_rawname=ID(unique=True),
+    repository=ID(stored=True, analyzer=ICASEIDANALYZER),
+    author=TEXT(stored=True, analyzer=EMAILADDRANALYZER),
     message=FieldType(format=Characters(), analyzer=ANALYZER,
                       scorable=True, stored=True),
     parents=TEXT(),
-    added=TEXT(),
-    removed=TEXT(),
-    changed=TEXT(),
+    added=TEXT(analyzer=PATHANALYZER),
+    removed=TEXT(analyzer=PATHANALYZER),
+    changed=TEXT(analyzer=PATHANALYZER),
 )
 
 CHGSET_IDX_NAME = 'CHGSET_INDEX'
 
 # used only to generate queries in journal
 JOURNAL_SCHEMA = Schema(
-    username=TEXT(),
+    username=ID(),
     date=DATETIME(),
     action=TEXT(),
     repository=ID(),
@@ -140,7 +178,7 @@
         res = self.searcher.stored_fields(docid[0])
         log.debug('result: %s', res)
         if self.search_type == 'content':
-            full_repo_path = jn(self.repo_location, res['repository'])
+            full_repo_path = os.path.join(self.repo_location, res['repository'])
             f_path = res['path'].split(full_repo_path)[-1]
             f_path = f_path.lstrip(os.sep)
             content_short = self.get_short_content(res, docid[1])
@@ -149,7 +187,7 @@
                         'f_path': f_path
             })
         elif self.search_type == 'path':
-            full_repo_path = jn(self.repo_location, res['repository'])
+            full_repo_path = os.path.join(self.repo_location, res['repository'])
             f_path = res['path'].split(full_repo_path)[-1]
             f_path = f_path.lstrip(os.sep)
             res.update({'f_path': f_path})
--- a/kallithea/lib/indexers/daemon.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/indexers/daemon.py	Sun Mar 31 21:28:56 2019 +0200
@@ -34,14 +34,13 @@
 from shutil import rmtree
 from time import mktime
 
-from os.path import dirname as dn
-from os.path import join as jn
+from os.path import dirname
 
 # Add location of top level folder to sys.path
-project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
+project_path = dirname(dirname(dirname(dirname(os.path.realpath(__file__)))))
 sys.path.append(project_path)
 
-from kallithea.config.conf import INDEX_EXTENSIONS
+from kallithea.config.conf import INDEX_EXTENSIONS, INDEX_FILENAMES
 from kallithea.model.scm import ScmModel
 from kallithea.model.db import Repository
 from kallithea.lib.utils2 import safe_unicode, safe_str
@@ -64,7 +63,7 @@
     """
 
     def __init__(self, indexname=IDX_NAME, index_location=None,
-                 repo_location=None, sa=None, repo_list=None,
+                 repo_location=None, repo_list=None,
                  repo_update_list=None):
         self.indexname = indexname
 
@@ -76,11 +75,11 @@
         if not repo_location:
             raise Exception('You have to provide repositories location')
 
-        self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
+        self.repo_paths = ScmModel().repo_scan(self.repo_location)
 
-        #filter repo list
+        # filter repo list
         if repo_list:
-            #Fix non-ascii repo names to unicode
+            # Fix non-ascii repo names to unicode
             repo_list = map(safe_unicode, repo_list)
             self.filtered_repo_paths = {}
             for repo_name, repo in self.repo_paths.items():
@@ -89,7 +88,7 @@
 
             self.repo_paths = self.filtered_repo_paths
 
-        #filter update repo list
+        # filter update repo list
         self.filtered_repo_update_paths = {}
         if repo_update_list:
             self.filtered_repo_update_paths = {}
@@ -102,12 +101,12 @@
         if not os.path.isdir(self.index_location):
             os.makedirs(self.index_location)
             log.info('Cannot run incremental index since it does not '
-                     'yet exist running full build')
+                     'yet exist - running full build')
         elif not exists_in(self.index_location, IDX_NAME):
-            log.info('Running full index build as the file content '
+            log.info('Running full index build, as the file content '
                      'index does not exist')
         elif not exists_in(self.index_location, CHGSET_IDX_NAME):
-            log.info('Running full index build as the changeset '
+            log.info('Running full index build, as the changeset '
                      'index does not exist')
         else:
             self.initial = False
@@ -136,7 +135,7 @@
             cs = self._get_index_changeset(repo)
             for _topnode, _dirs, files in cs.walk('/'):
                 for f in files:
-                    index_paths_.add(jn(safe_str(repo.path), safe_str(f.path)))
+                    index_paths_.add(os.path.join(safe_str(repo.path), safe_str(f.path)))
 
         except RepositoryError:
             log.debug(traceback.format_exc())
@@ -162,6 +161,13 @@
         node = cs.get_node(node_path)
         return node
 
+    def is_indexable_node(self, node):
+        """
+        Just index the content of chosen files, skipping binary files
+        """
+        return (node.extension in INDEX_EXTENSIONS or node.name in INDEX_FILENAMES) and \
+               not node.is_binary
+
     def get_node_mtime(self, node):
         return mktime(node.last_changeset.date.timetuple())
 
@@ -173,23 +179,21 @@
         try:
             node = self.get_node(repo, path, index_rev)
         except (ChangesetError, NodeDoesNotExistError):
-            log.debug("couldn't add doc - %s did not have %r at %s", repo, path, index_rev)
+            log.debug("    >> %s - not found in %s %s", path, repo, index_rev)
             return 0, 0
 
         indexed = indexed_w_content = 0
-        # we just index the content of chosen files, and skip binary files
-        if node.extension in INDEX_EXTENSIONS and not node.is_binary:
+        if self.is_indexable_node(node):
             u_content = node.content
             if not isinstance(u_content, unicode):
-                log.warning('  >> %s Could not get this content as unicode '
-                            'replacing with empty content' % path)
+                log.warning('    >> %s - no text content', path)
                 u_content = u''
             else:
-                log.debug('    >> %s [WITH CONTENT]', path)
+                log.debug('    >> %s', path)
                 indexed_w_content += 1
 
         else:
-            log.debug('    >> %s', path)
+            log.debug('    >> %s - not indexable', path)
             # just index file name without it's content
             u_content = u''
             indexed += 1
@@ -198,6 +202,7 @@
         writer.add_document(
             fileid=p,
             owner=unicode(repo.contact),
+            repository_rawname=safe_unicode(repo_name),
             repository=safe_unicode(repo_name),
             path=p,
             content=u_content,
@@ -224,18 +229,20 @@
         if start_rev is None:
             start_rev = repo[0].raw_id
 
-        log.debug('indexing changesets in %s starting at rev: %s',
+        log.debug('Indexing changesets in %s, starting at rev %s',
                   repo_name, start_rev)
 
         indexed = 0
         cs_iter = repo.get_changesets(start=start_rev)
         total = len(cs_iter)
         for cs in cs_iter:
-            log.debug('    >> %s/%s', cs, total)
+            indexed += 1
+            log.debug('    >> %s %s/%s', cs, indexed, total)
             writer.add_document(
                 raw_id=unicode(cs.raw_id),
                 owner=unicode(repo.contact),
                 date=cs._timestamp,
+                repository_rawname=safe_unicode(repo_name),
                 repository=safe_unicode(repo_name),
                 author=cs.author,
                 message=cs.message,
@@ -245,9 +252,7 @@
                 changed=u' '.join([safe_unicode(node.path) for node in cs.changed]).lower(),
                 parents=u' '.join([cs.raw_id for cs in cs.parents]),
             )
-            indexed += 1
 
-        log.debug('indexed %d changesets for repo %s', indexed, repo_name)
         return indexed
 
     def index_files(self, file_idx_writer, repo_name, repo):
@@ -259,7 +264,7 @@
         :param repo: instance of vcs repo
         """
         i_cnt = iwc_cnt = 0
-        log.debug('building index for %s @revision:%s', repo.path,
+        log.debug('Building file index for %s @revision:%s', repo_name,
                                                 self._get_index_revision(repo))
         index_rev = self._get_index_revision(repo)
         for idx_path in self.get_paths(repo):
@@ -280,7 +285,8 @@
             try:
                 indexed_total = 0
                 repo_name = None
-                for repo_name, repo in self.repo_paths.items():
+                for repo_name, repo in sorted(self.repo_paths.items()):
+                    log.debug('Updating changeset index for repo %s', repo_name)
                     # skip indexing if there aren't any revs in the repo
                     num_of_revs = len(repo)
                     if num_of_revs < 1:
@@ -346,7 +352,7 @@
                     indexed_repo_path = fields['repository']
                     indexed_paths.add(indexed_path)
 
-                    if not indexed_repo_path in self.filtered_repo_update_paths:
+                    if indexed_repo_path not in self.filtered_repo_update_paths:
                         continue
 
                     repo = self.repo_paths[indexed_repo_path]
@@ -378,7 +384,8 @@
             # documents to be indexed
             ri_cnt_total = 0  # indexed
             riwc_cnt_total = 0  # indexed with content
-            for repo_name, repo in self.repo_paths.items():
+            for repo_name, repo in sorted(self.repo_paths.items()):
+                log.debug('Updating file index for repo %s', repo_name)
                 # skip indexing if there aren't any revisions
                 if len(repo) < 1:
                     continue
@@ -392,7 +399,6 @@
                         # that wasn't indexed before. So index it!
                         i, iwc = self.add_doc(writer, path, repo, repo_name)
                         writer_is_dirty = True
-                        log.debug('re indexing %s', path)
                         ri_cnt += i
                         ri_cnt_total += 1
                         riwc_cnt += iwc
@@ -429,7 +435,8 @@
         log.debug('BUILDING INDEX FOR EXTENSIONS %s '
                   'AND REPOS %s' % (INDEX_EXTENSIONS, self.repo_paths.keys()))
 
-        for repo_name, repo in self.repo_paths.items():
+        for repo_name, repo in sorted(self.repo_paths.items()):
+            log.debug('Updating indices for repo %s', repo_name)
             # skip indexing if there aren't any revisions
             if len(repo) < 1:
                 continue
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/inifile.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+kallithea.lib.inifile
+~~~~~~~~~~~~~~~~~~~~~
+
+Handling of .ini files, mainly creating them from Mako templates and adding
+other custom values.
+"""
+
+import logging
+import re
+import os
+
+import mako.template
+
+
+log = logging.getLogger(__name__)
+
+
+template_file = os.path.join(
+    os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
+    'kallithea/lib/paster_commands/template.ini.mako')
+
+default_variables = {
+    'database_engine': 'sqlite',
+    'http_server': 'waitress',
+    'host': '127.0.0.1',
+    'port': '5000',
+    'uuid': lambda: 'VERY-SECRET',
+}
+
+
+def expand(template, mako_variable_values, settings):
+    """Expand mako template and tweak it.
+    Not entirely stable for random templates as input, but good enough for our
+    single template.
+
+    >>> template = '''
+    ... [first-section]
+    ...
+    ... variable=${mako_variable}
+    ... variable2  =\tvalue after tab
+    ... ## This section had some whitespace and stuff
+    ...
+    ...
+    ... # ${mako_function()}
+    ... [second-section]
+    ... %if conditional_options == 'option-a':
+    ... # option a was chosen
+    ... %elif conditional_options == 'option-b':
+    ... some_variable = "never mind - option-b will not be used anyway ..."
+    ... %endif
+    ... '''
+    >>> selected_mako_conditionals = []
+    >>> mako_variable_values = {'mako_variable': 'VALUE', 'mako_function': (lambda: 'FUNCTION RESULT'),
+    ...                         'conditional_options': 'option-a'}
+    >>> settings = { # only partially used
+    ...     '[first-section]': {'variable2': 'VAL2', 'first_extra': 'EXTRA'},
+    ...     '[third-section]': {'third_extra': ' 3'},
+    ...     '[fourth-section]': {'fourth_extra': '4', 'fourth': '"four"'},
+    ... }
+    >>> print expand(template, mako_variable_values, settings)
+    <BLANKLINE>
+    [first-section]
+    <BLANKLINE>
+    variable=VALUE
+    #variable2  =    value after tab
+    variable2 = VAL2
+    <BLANKLINE>
+    first_extra = EXTRA
+    <BLANKLINE>
+    <BLANKLINE>
+    # FUNCTION RESULT
+    [second-section]
+    # option a was chosen
+    <BLANKLINE>
+    [fourth-section]
+    fourth = "four"
+    fourth_extra = 4
+    <BLANKLINE>
+    [third-section]
+    third_extra =  3
+    <BLANKLINE>
+    """
+    mako_variables = dict(default_variables)
+    mako_variables.update(mako_variable_values or {})
+    settings = dict((k, dict(v)) for k, v in settings.items()) # deep copy before mutating
+
+    ini_lines = mako.template.Template(template).render(**mako_variables)
+
+    def process_section(m):
+        """process a ini section, replacing values as necessary"""
+        sectionname, lines = m.groups()
+        if sectionname in settings:
+            section_settings = settings.pop(sectionname)
+
+            def process_line(m):
+                """process a section line and update value if necessary"""
+                key, value = m.groups()
+                line = m.group(0)
+                if key in section_settings:
+                    new_line = '%s = %s' % (key, section_settings.pop(key))
+                    if new_line != line:
+                        # keep old entry as example - comments might refer to it
+                        line = '#%s\n%s' % (line, new_line)
+                return line.rstrip()
+
+            # process lines that not are comments or empty and look like name=value
+            lines = re.sub(r'^([^#\n\s]*)[ \t]*=[ \t]*(.*)$', process_line, lines, flags=re.MULTILINE)
+            # add unused section settings
+            if section_settings:
+                lines += '\n' + ''.join('%s = %s\n' % (key, value) for key, value in sorted(section_settings.items()))
+
+        return sectionname + '\n' + lines
+
+    # process sections until comments before next section or end
+    ini_lines = re.sub(r'''^
+        (\[.*\])\n
+        # after the section name, a number of chunks with:
+        (
+            (?:
+                # a number of comments or empty lines
+                (?:[#].*\n|\n)*
+                # one or more non-empty non-comments non-section-start lines
+                (?:[^\n#[].*\n)+
+                # a number of comments - not empty lines
+                (?:[#].*\n)*
+            )*
+        )
+        ''',
+        process_section, ini_lines, flags=re.MULTILINE|re.VERBOSE) \
+        + \
+        ''.join(
+            '\n' + sectionname + '\n' + ''.join('%s = %s\n' % (key, value) for key, value in sorted(section_settings.items()))
+            for sectionname, section_settings in sorted(settings.items())
+            if section_settings)
+
+    return ini_lines
+
+
+def create(dest_file, mako_variable_values, settings):
+    """Create an ini file at dest_file"""
+    with open(template_file, 'rb') as f:
+        template = f.read().decode('utf-8')
+
+    ini_lines = expand(template, mako_variable_values, settings)
+
+    with open(dest_file, 'wb') as f:
+        f.write(ini_lines.encode('utf-8'))
--- a/kallithea/lib/ipaddr.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/ipaddr.py	Sun Mar 31 21:28:56 2019 +0200
@@ -367,9 +367,11 @@
     return _collapse_address_list_recursive(sorted(
         addrs + nets, key=_BaseNet._get_networks_key))
 
+
 # backwards compatibility
 CollapseAddrList = collapse_address_list
 
+
 # We need to distinguish between the string and packed-bytes representations
 # of an IP address.  For example, b'0::1' is the IPv4 address 48.58.58.49,
 # while '0::1' is an IPv6 address.
@@ -767,18 +769,17 @@
                 s1, s2 = s2.subnet()
             else:
                 # If we got here, there's a bug somewhere.
-                assert True == False, ('Error performing exclusion: '
-                                       's1: %s s2: %s other: %s' %
-                                       (str(s1), str(s2), str(other)))
+                assert False, ('Error performing exclusion: '
+                               's1: %s s2: %s other: %s' %
+                               (str(s1), str(s2), str(other)))
         if s1 == other:
             ret_addrs.append(s2)
         elif s2 == other:
             ret_addrs.append(s1)
         else:
             # If we got here, there's a bug somewhere.
-            assert True == False, ('Error performing exclusion: '
-                                   's1: %s s2: %s other: %s' %
-                                   (str(s1), str(s2), str(other)))
+            assert False, ('Error performing exclusion: s1: %s s2: %s other: %s'
+                           % (str(s1), str(s2), str(other)))
 
         return sorted(ret_addrs, key=_BaseNet._get_networks_key)
 
@@ -1245,7 +1246,7 @@
               '192.168.1.1'
               '192.168.1.1/255.255.255.255'
               '192.168.1.1/32'
-              are also functionaly equivalent. That is to say, failing to
+              are also functionally equivalent. That is to say, failing to
               provide a subnetmask will create an object with a mask of /32.
 
               If the mask (portion after the / in the argument) is given in
--- a/kallithea/lib/markup_renderer.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/markup_renderer.py	Sun Mar 31 21:28:56 2019 +0200
@@ -38,8 +38,9 @@
 log = logging.getLogger(__name__)
 
 
-url_re = re.compile(r'''(\bhttps?://(?:[\da-zA-Z0-9@:.-]+)'''
-                    r'''(?:[/a-zA-Z0-9_=@#~&+%.,:;?!*()-]*[/a-zA-Z0-9_=@#~])?)''')
+url_re = re.compile(r'''\bhttps?://(?:[\da-zA-Z0-9@:.-]+)'''
+                    r'''(?:[/a-zA-Z0-9_=@#~&+%.,:;?!*()-]*[/a-zA-Z0-9_=@#~])?''')
+
 
 class MarkupRenderer(object):
     RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
@@ -48,7 +49,7 @@
     RST_PAT = re.compile(r're?st', re.IGNORECASE)
     PLAIN_PAT = re.compile(r'readme', re.IGNORECASE)
 
-    def _detect_renderer(self, source, filename=None):
+    def _detect_renderer(self, source, filename):
         """
         runs detection of what renderer should be used for generating html
         from a markup language
@@ -59,16 +60,13 @@
         :param filename:
         """
 
-        if MarkupRenderer.MARKDOWN_PAT.findall(filename):
-            detected_renderer = 'markdown'
-        elif MarkupRenderer.RST_PAT.findall(filename):
-            detected_renderer = 'rst'
-        elif MarkupRenderer.PLAIN_PAT.findall(filename):
-            detected_renderer = 'rst'
-        else:
-            detected_renderer = 'plain'
-
-        return getattr(MarkupRenderer, detected_renderer)
+        if self.MARKDOWN_PAT.findall(filename):
+            return self.markdown
+        elif self.RST_PAT.findall(filename):
+            return self.rst
+        elif self.PLAIN_PAT.findall(filename):
+            return self.rst
+        return self.plain
 
     @classmethod
     def _flavored_markdown(cls, text):
@@ -81,6 +79,7 @@
 
         # Extract pre blocks.
         extractions = {}
+
         def pre_extraction_callback(matchobj):
             digest = md5(matchobj.group(0)).hexdigest()
             extractions[digest] = matchobj.group(0)
@@ -147,7 +146,7 @@
             source = newline.join(source.splitlines())
 
         def url_func(match_obj):
-            url_full = match_obj.groups()[0]
+            url_full = match_obj.group(0)
             return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
         source = url_re.sub(url_func, source)
         return '<br />' + source.replace("\n", '<br />')
@@ -175,7 +174,10 @@
         try:
             if flavored:
                 source = cls._flavored_markdown(source)
-            return markdown_mod.markdown(source, ['codehilite', 'extra'])
+            return markdown_mod.markdown(
+                source,
+                extensions=['codehilite', 'extra'],
+                extension_configs={'codehilite': {'css_class': 'code-highlight'}})
         except Exception:
             log.error(traceback.format_exc())
             if safe:
@@ -217,10 +219,9 @@
 
     @classmethod
     def rst_with_mentions(cls, source):
-        mention_pat = re.compile(MENTIONS_REGEX)
 
         def wrapp(match_obj):
             uname = match_obj.groups()[0]
             return '\ **@%(uname)s**\ ' % {'uname': uname}
-        mention_hl = mention_pat.sub(wrapp, source).strip()
+        mention_hl = MENTIONS_REGEX.sub(wrapp, source).strip()
         return cls.rst(mention_hl)
--- a/kallithea/lib/middleware/pygrack.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/middleware/pygrack.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,3 +1,30 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.pygrack
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Python implementation of git-http-backend's Smart HTTP protocol
+
+Based on original code from git_http_backend.py project.
+
+Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com>
+Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com>
+
+This file was forked by the Kallithea project in July 2014.
+"""
+
 import os
 import socket
 import logging
@@ -7,6 +34,7 @@
 
 import kallithea
 from kallithea.lib.vcs import subprocessio
+from kallithea.lib.utils2 import safe_unicode
 
 log = logging.getLogger(__name__)
 
@@ -44,7 +72,7 @@
 
     def __init__(self, repo_name, content_path, extras):
         files = set([f.lower() for f in os.listdir(content_path)])
-        if  not (self.git_folder_signature.intersection(files)
+        if not (self.git_folder_signature.intersection(files)
                 == self.git_folder_signature):
             raise OSError('%s missing git signature' % content_path)
         self.content_path = content_path
@@ -59,15 +87,17 @@
 
         :param path:
         """
-        return path.split(self.repo_name, 1)[-1].strip('/')
+        path = safe_unicode(path)
+        assert path.startswith('/' + self.repo_name + '/')
+        return path[len(self.repo_name) + 2:].strip('/')
 
-    def inforefs(self, request, environ):
+    def inforefs(self, req, environ):
         """
         WSGI Response producer for HTTP GET Git Smart
         HTTP /info/refs request.
         """
 
-        git_command = request.GET.get('service')
+        git_command = req.GET.get('service')
         if git_command not in self.commands:
             log.debug('command %s not allowed', git_command)
             return exc.HTTPMethodNotAllowed()
@@ -101,7 +131,7 @@
         resp.app_iter = out
         return resp
 
-    def backend(self, request, environ):
+    def backend(self, req, environ):
         """
         WSGI Response producer for HTTP POST Git Smart HTTP requests.
         Reads commands and data from HTTP POST's body.
@@ -109,14 +139,14 @@
         response to stdout
         """
         _git_path = kallithea.CONFIG.get('git_path', 'git')
-        git_command = self._get_fixedpath(request.path_info)
+        git_command = self._get_fixedpath(req.path_info)
         if git_command not in self.commands:
             log.debug('command %s not allowed', git_command)
             return exc.HTTPMethodNotAllowed()
 
         if 'CONTENT_LENGTH' in environ:
             inputstream = FileWrapper(environ['wsgi.input'],
-                                      request.content_length)
+                                      req.content_length)
         else:
             inputstream = environ['wsgi.input']
 
@@ -146,20 +176,20 @@
                 update_server_info(repo._repo)
 
         resp = Response()
-        resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
+        resp.content_type = 'application/x-%s-result' % git_command.encode('utf-8')
         resp.charset = None
         resp.app_iter = out
         return resp
 
     def __call__(self, environ, start_response):
-        request = Request(environ)
-        _path = self._get_fixedpath(request.path_info)
+        req = Request(environ)
+        _path = self._get_fixedpath(req.path_info)
         if _path.startswith('info/refs'):
             app = self.inforefs
-        elif [a for a in self.valid_accepts if a in request.accept]:
+        elif [a for a in self.valid_accepts if a in req.accept]:
             app = self.backend
         try:
-            resp = app(request, environ)
+            resp = app(req, environ)
         except exc.HTTPException as e:
             resp = e
             log.error(traceback.format_exc())
--- a/kallithea/lib/middleware/sessionmiddleware.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/middleware/sessionmiddleware.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,6 +26,7 @@
 from beaker.session import SessionObject
 from beaker.middleware import SessionMiddleware
 
+
 class SecureSessionMiddleware(SessionMiddleware):
     def __call__(self, environ, start_response):
         """
--- a/kallithea/lib/middleware/simplegit.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/middleware/simplegit.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,7 @@
 kallithea.lib.middleware.simplegit
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-SimpleGit middleware for handling git protocol request (push/clone etc.)
+SimpleGit middleware for handling Git protocol requests (push/clone etc.)
 It's implemented with basic auth function
 
 This file was forked by the Kallithea project in July 2014.
@@ -33,18 +33,16 @@
 import logging
 import traceback
 
-from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
     HTTPNotAcceptable
-from kallithea.model.db import User, Ui
+from kallithea.model.db import Ui
 
-from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url,\
+from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url, \
     _set_extras
-from kallithea.lib.base import BaseVCSController, WSGIResultCloseCallback
+from kallithea.lib.base import BaseVCSController, check_locking_state
 from kallithea.lib.utils import make_ui, is_valid_repo
 from kallithea.lib.exceptions import HTTPLockedRC
-from kallithea.lib.hooks import pre_pull
-from kallithea.lib import auth_modules
+from kallithea.lib.hooks import pull_lock_handling
 
 log = logging.getLogger(__name__)
 
@@ -63,15 +61,13 @@
 
 class SimpleGit(BaseVCSController):
 
+    _git_stored_op = 'pull' # most recent kind of command
+
     def _handle_request(self, environ, start_response):
         if not is_git(environ):
             return self.application(environ, start_response)
-        if not self._check_ssl(environ):
-            return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
 
         ip_addr = self._get_ip_addr(environ)
-        username = None
-        self._git_first_op = False
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
 
@@ -96,76 +92,19 @@
         action = self.__get_action(environ)
 
         #======================================================================
-        # CHECK ANONYMOUS PERMISSION
+        # CHECK PERMISSIONS
         #======================================================================
-        if action in ['pull', 'push']:
-            anonymous_user = self.__get_user('default')
-            username = anonymous_user.username
-            if anonymous_user.active:
-                # ONLY check permissions if the user is activated
-                anonymous_perm = self._check_permission(action, anonymous_user,
-                                                        repo_name, ip_addr)
-            else:
-                anonymous_perm = False
-
-            if not anonymous_user.active or not anonymous_perm:
-                if not anonymous_user.active:
-                    log.debug('Anonymous access is disabled, running '
-                              'authentication')
-
-                if not anonymous_perm:
-                    log.debug('Not enough credentials to access this '
-                              'repository as anonymous user')
-
-                username = None
-                #==============================================================
-                # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
-                # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
-                #==============================================================
+        user, response_app = self._authorize(environ, start_response, action, repo_name, ip_addr)
+        if response_app is not None:
+            return response_app(environ, start_response)
 
-                # try to auth based on environ, container auth methods
-                log.debug('Running PRE-AUTH for container based authentication')
-                pre_auth = auth_modules.authenticate('', '', environ)
-                if pre_auth is not None and pre_auth.get('username'):
-                    username = pre_auth['username']
-                log.debug('PRE-AUTH got %s as username', username)
-
-                # If not authenticated by the container, running basic auth
-                if not username:
-                    self.authenticate.realm = \
-                        safe_str(self.config['realm'])
-                    result = self.authenticate(environ)
-                    if isinstance(result, str):
-                        AUTH_TYPE.update(environ, 'basic')
-                        REMOTE_USER.update(environ, result)
-                        username = result
-                    else:
-                        return result.wsgi_application(environ, start_response)
-
-                #==============================================================
-                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
-                #==============================================================
-                try:
-                    user = self.__get_user(username)
-                    if user is None or not user.active:
-                        return HTTPForbidden()(environ, start_response)
-                    username = user.username
-                except Exception:
-                    log.error(traceback.format_exc())
-                    return HTTPInternalServerError()(environ, start_response)
-
-                #check permissions for this repository
-                perm = self._check_permission(action, user, repo_name, ip_addr)
-                if not perm:
-                    return HTTPForbidden()(environ, start_response)
-
-        # extras are injected into UI object and later available
-        # in hooks executed by kallithea
+        # extras are injected into Mercurial UI object and later available
+        # in hooks executed by Kallithea
         from kallithea import CONFIG
         server_url = get_server_url(environ)
         extras = {
             'ip': ip_addr,
-            'username': username,
+            'username': user.username,
             'action': action,
             'repository': repo_name,
             'scm': 'git',
@@ -178,18 +117,12 @@
         #===================================================================
         # GIT REQUEST HANDLING
         #===================================================================
-        repo_path = os.path.join(safe_str(self.basepath),str_repo_name)
+        repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
         log.debug('Repository path is %s', repo_path)
 
-        # CHECK LOCKING only if it's not ANONYMOUS USER
-        if username != User.DEFAULT_USER:
+        if not user.is_default_user:
             log.debug('Checking locking on repository')
-            (make_lock,
-             locked,
-             locked_by) = self._check_locking_state(
-                            environ=environ, action=action,
-                            repo=repo_name, user_id=user.user_id
-                       )
+            make_lock, locked, locked_by = check_locking_state(action, repo_name, user)
             # store the make_lock for later evaluation in hooks
             extras.update({'make_lock': make_lock,
                            'locked_by': locked_by})
@@ -197,18 +130,14 @@
         fix_PATH()
         log.debug('HOOKS extras is %s', extras)
         baseui = make_ui('db')
-        self.__inject_extras(repo_path, baseui, extras)
+        _set_extras(extras or {})
 
         try:
             self._handle_githooks(repo_name, action, baseui, environ)
             log.info('%s action on Git repo "%s" by "%s" from %s',
-                     action, str_repo_name, safe_str(username), ip_addr)
+                     action, str_repo_name, safe_str(user.username), ip_addr)
             app = self.__make_app(repo_name, repo_path, extras)
-            result = app(environ, start_response)
-            if action == 'push':
-                result = WSGIResultCloseCallback(result,
-                    lambda: self._invalidate_cache(repo_name))
-            return result
+            return app(environ, start_response)
         except HTTPLockedRC as e:
             log.debug('Locked, response %s: %s', e.code, e.title)
             return e(environ, start_response)
@@ -227,7 +156,7 @@
         from kallithea.lib.middleware.pygrack import make_wsgi_app
         app = make_wsgi_app(
             repo_root=safe_str(self.basepath),
-            repo_name=repo_name,
+            repo_name=safe_unicode(repo_name),
             extras=extras,
         )
         return app
@@ -247,12 +176,9 @@
 
         return repo_name
 
-    def __get_user(self, username):
-        return User.get_by_username(username)
-
     def __get_action(self, environ):
         """
-        Maps git request commands into a pull or push command.
+        Maps Git request commands into a pull or push command.
 
         :param environ:
         """
@@ -266,12 +192,9 @@
             }
             op = mapping[service_cmd]
             self._git_stored_op = op
-            return op
-        else:
             # try to fallback to stored variable as we don't know if the last
             # operation is pull/push
-            op = getattr(self, '_git_stored_op', 'pull')
-        return op
+        return self._git_stored_op
 
     def _handle_githooks(self, repo_name, action, baseui, environ):
         """
@@ -290,16 +213,6 @@
         _hooks = dict(baseui.configitems('hooks')) or {}
         if action == 'pull':
             # stupid git, emulate pre-pull hook !
-            pre_pull(ui=baseui, repo=_repo._repo)
-        if action == 'pull' and _hooks.get(Ui.HOOK_PULL):
+            pull_lock_handling(ui=baseui, repo=_repo._repo)
+        if action == 'pull' and _hooks.get(Ui.HOOK_PULL_LOG):
             log_pull_action(ui=baseui, repo=_repo._repo)
-
-    def __inject_extras(self, repo_path, baseui, extras={}):
-        """
-        Injects some extra params into baseui instance
-
-        :param baseui: baseui instance
-        :param extras: dict with extra params to put into baseui
-        """
-
-        _set_extras(extras)
--- a/kallithea/lib/middleware/simplehg.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/middleware/simplehg.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,8 +15,8 @@
 kallithea.lib.middleware.simplehg
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-SimpleHg middleware for handling mercurial protocol request
-(push/clone etc.). It's implemented with basic auth function
+SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.).
+It's implemented with basic auth function
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -33,18 +33,15 @@
 import traceback
 import urllib
 
-from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
-    HTTPNotAcceptable
-from kallithea.model.db import User
+    HTTPNotAcceptable, HTTPBadRequest
 
-from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url,\
+from kallithea.lib.utils2 import safe_str, safe_unicode, fix_PATH, get_server_url, \
     _set_extras
-from kallithea.lib.base import BaseVCSController, WSGIResultCloseCallback
+from kallithea.lib.base import BaseVCSController, check_locking_state
 from kallithea.lib.utils import make_ui, is_valid_repo, ui_sections
 from kallithea.lib.vcs.utils.hgcompat import RepoError, hgweb_mod
 from kallithea.lib.exceptions import HTTPLockedRC
-from kallithea.lib import auth_modules
 
 log = logging.getLogger(__name__)
 
@@ -90,11 +87,8 @@
     def _handle_request(self, environ, start_response):
         if not is_mercurial(environ):
             return self.application(environ, start_response)
-        if not self._check_ssl(environ):
-            return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
 
         ip_addr = self._get_ip_addr(environ)
-        username = None
         # skip passing error to error controller
         environ['pylons.status_code_redirect'] = True
 
@@ -102,10 +96,8 @@
         # EXTRACT REPOSITORY NAME FROM ENV
         #======================================================================
         try:
-            str_repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
-            assert isinstance(str_repo_name, str), str_repo_name
+            str_repo_name = self.__get_repository(environ)
             repo_name = safe_unicode(str_repo_name)
-            assert safe_str(repo_name) == str_repo_name, (str_repo_name, repo_name)
             log.debug('Extracted repo name is %s', repo_name)
         except Exception as e:
             log.error('error extracting repo_name: %r', e)
@@ -118,79 +110,25 @@
         #======================================================================
         # GET ACTION PULL or PUSH
         #======================================================================
-        action = self.__get_action(environ)
+        try:
+            action = self.__get_action(environ)
+        except HTTPBadRequest as e:
+            return e(environ, start_response)
 
         #======================================================================
-        # CHECK ANONYMOUS PERMISSION
+        # CHECK PERMISSIONS
         #======================================================================
-        if action in ['pull', 'push']:
-            anonymous_user = self.__get_user('default')
-            username = anonymous_user.username
-            if anonymous_user.active:
-                # ONLY check permissions if the user is activated
-                anonymous_perm = self._check_permission(action, anonymous_user,
-                                                        repo_name, ip_addr)
-            else:
-                anonymous_perm = False
-
-            if not anonymous_user.active or not anonymous_perm:
-                if not anonymous_user.active:
-                    log.debug('Anonymous access is disabled, running '
-                              'authentication')
-
-                if not anonymous_perm:
-                    log.debug('Not enough credentials to access this '
-                              'repository as anonymous user')
-
-                username = None
-                #==============================================================
-                # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
-                # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
-                #==============================================================
+        user, response_app = self._authorize(environ, start_response, action, repo_name, ip_addr)
+        if response_app is not None:
+            return response_app(environ, start_response)
 
-                # try to auth based on environ, container auth methods
-                log.debug('Running PRE-AUTH for container based authentication')
-                pre_auth = auth_modules.authenticate('', '', environ)
-                if pre_auth is not None and pre_auth.get('username'):
-                    username = pre_auth['username']
-                log.debug('PRE-AUTH got %s as username', username)
-
-                # If not authenticated by the container, running basic auth
-                if not username:
-                    self.authenticate.realm = \
-                        safe_str(self.config['realm'])
-                    result = self.authenticate(environ)
-                    if isinstance(result, str):
-                        AUTH_TYPE.update(environ, 'basic')
-                        REMOTE_USER.update(environ, result)
-                        username = result
-                    else:
-                        return result.wsgi_application(environ, start_response)
-
-                #==============================================================
-                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
-                #==============================================================
-                try:
-                    user = self.__get_user(username)
-                    if user is None or not user.active:
-                        return HTTPForbidden()(environ, start_response)
-                    username = user.username
-                except Exception:
-                    log.error(traceback.format_exc())
-                    return HTTPInternalServerError()(environ, start_response)
-
-                #check permissions for this repository
-                perm = self._check_permission(action, user, repo_name, ip_addr)
-                if not perm:
-                    return HTTPForbidden()(environ, start_response)
-
-        # extras are injected into mercurial UI object and later available
-        # in hg hooks executed by kallithea
+        # extras are injected into Mercurial UI object and later available
+        # in hooks executed by Kallithea
         from kallithea import CONFIG
         server_url = get_server_url(environ)
         extras = {
             'ip': ip_addr,
-            'username': username,
+            'username': user.username,
             'action': action,
             'repository': repo_name,
             'scm': 'hg',
@@ -205,15 +143,15 @@
         repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
         log.debug('Repository path is %s', repo_path)
 
+        # A Mercurial HTTP server will see listkeys operations (bookmarks,
+        # phases and obsolescence marker) in a different request - we don't
+        # want to check locking on those
+        if environ['QUERY_STRING'] == 'cmd=listkeys':
+            pass
         # CHECK LOCKING only if it's not ANONYMOUS USER
-        if username != User.DEFAULT_USER:
+        elif not user.is_default_user:
             log.debug('Checking locking on repository')
-            (make_lock,
-             locked,
-             locked_by) = self._check_locking_state(
-                            environ=environ, action=action,
-                            repo=repo_name, user_id=user.user_id
-                       )
+            make_lock, locked, locked_by = check_locking_state(action, repo_name, user)
             # store the make_lock for later evaluation in hooks
             extras.update({'make_lock': make_lock,
                            'locked_by': locked_by})
@@ -221,17 +159,15 @@
         fix_PATH()
         log.debug('HOOKS extras is %s', extras)
         baseui = make_ui('db')
-        self.__inject_extras(repo_path, baseui, extras)
+        self._augment_hgrc(repo_path, baseui)
+        _set_extras(extras or {})
 
         try:
             log.info('%s action on Mercurial repo "%s" by "%s" from %s',
-                     action, str_repo_name, safe_str(username), ip_addr)
+                     action, str_repo_name, safe_str(user.username), ip_addr)
+            environ['REPO_NAME'] = str_repo_name # used by hgweb_mod.hgweb
             app = self.__make_app(repo_path, baseui, extras)
-            result = app(environ, start_response)
-            if action == 'push':
-                result = WSGIResultCloseCallback(result,
-                    lambda: self._invalidate_cache(repo_name))
-            return result
+            return app(environ, start_response)
         except RepoError as e:
             if str(e).find('not found') != -1:
                 return HTTPNotFound()(environ, start_response)
@@ -251,13 +187,21 @@
         class HgWebWrapper(hgweb_mod.hgweb):
             # Work-around for Mercurial 3.6+ causing lock exceptions to be
             # thrown late
-            def _runwsgi(self, req, repo):
+            def _runwsgi(self, *args):
                 try:
-                    return super(HgWebWrapper, self)._runwsgi(req, repo)
+                    return super(HgWebWrapper, self)._runwsgi(*args)
                 except HTTPLockedRC as e:
                     log.debug('Locked, response %s: %s', e.code, e.title)
-                    req.respond(e.status, 'text/plain')
-                    return ''
+                    try:
+                        req, res, repo = args
+                        res.status = e.status
+                        res.headers['Content-Type'] = 'text/plain'
+                        res.setbodybytes('')
+                        return res.sendresponse()
+                    except ValueError: # wsgiresponse was introduced in Mercurial 4.6 (a88d68dc3ee8)
+                        req, repo = args
+                        req.respond(e.status, 'text/plain')
+                        return ''
 
         return HgWebWrapper(repo_name, name=repo_name, baseui=baseui)
 
@@ -278,14 +222,11 @@
 
         return repo_name
 
-    def __get_user(self, username):
-        return User.get_by_username(username)
-
     def __get_action(self, environ):
         """
         Maps Mercurial request commands into 'pull' or 'push'.
 
-        :param environ:
+        Raises HTTPBadRequest if the request environment doesn't look like a hg client.
         """
         mapping = {
             # 'batch' is not in this list - it is handled explicitly
@@ -333,26 +274,13 @@
                     return 'pull'
                 return mapping.get(cmd, 'push')
 
-        raise Exception('Unable to detect pull/push action !!'
-                        'Are you using non standard command or client ?')
-
-    def __inject_extras(self, repo_path, baseui, extras={}):
-        """
-        Injects some extra params into baseui instance
-
-        also overwrites global settings with those takes from local hgrc file
+        # Note: the client doesn't get the helpful error message
+        raise HTTPBadRequest('Unable to detect pull/push action! Are you using non standard command or client?')
 
-        :param baseui: baseui instance
-        :param extras: dict with extra params to put into baseui
-        """
-
+    def _augment_hgrc(self, repo_path, baseui):
+        """Augment baseui with config settings from the repo_path repo"""
         hgrc = os.path.join(repo_path, '.hg', 'hgrc')
-
-        repoui = make_ui('file', hgrc, False)
-
-        if repoui:
-            #overwrite our ui instance with the section from hgrc file
-            for section in ui_sections:
-                for k, v in repoui.configitems(section):
-                    baseui.setconfig(section, k, v)
-        _set_extras(extras)
+        repoui = make_ui('file', hgrc)
+        for section in ui_sections:
+            for k, v in repoui.configitems(section):
+                baseui.setconfig(section, k, v)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/page.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,251 @@
+# -*- 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/>.
+"""
+Custom paging classes
+"""
+import logging
+import math
+import re
+from kallithea.config.routing import url
+from webhelpers.html import literal, HTML
+from webhelpers.paginate import Page as _Page
+
+log = logging.getLogger(__name__)
+
+
+class Page(_Page):
+    """
+    Custom pager emitting Bootstrap paginators
+    """
+
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault('url', url.current)
+        _Page.__init__(self, *args, **kwargs)
+
+    def _get_pos(self, cur_page, max_page, items):
+        edge = (items / 2) + 1
+        if (cur_page <= edge):
+            radius = max(items / 2, items - cur_page)
+        elif (max_page - cur_page) < edge:
+            radius = (items - 1) - (max_page - cur_page)
+        else:
+            radius = items / 2
+
+        left = max(1, (cur_page - (radius)))
+        right = min(max_page, cur_page + (radius))
+        return left, cur_page, right
+
+    def _range(self, regexp_match):
+        """
+        Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
+
+        Arguments:
+
+        regexp_match
+            A "re" (regular expressions) match object containing the
+            radius of linked pages around the current page in
+            regexp_match.group(1) as a string
+
+        This function is supposed to be called as a callable in
+        re.sub.
+
+        """
+        radius = int(regexp_match.group(1))
+
+        # Compute the first and last page number within the radius
+        # e.g. '1 .. 5 6 [7] 8 9 .. 12'
+        # -> leftmost_page  = 5
+        # -> rightmost_page = 9
+        leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
+                                                            self.last_page,
+                                                            (radius * 2) + 1)
+        nav_items = []
+
+        # Create a link to the first page (unless we are on the first page
+        # or there would be no need to insert '..' spacers)
+        if self.page != self.first_page and self.first_page < leftmost_page:
+            nav_items.append(HTML.li(self._pagerlink(self.first_page, self.first_page)))
+
+        # Insert dots if there are pages between the first page
+        # and the currently displayed page range
+        if leftmost_page - self.first_page > 1:
+            # Wrap in a SPAN tag if nolink_attr is set
+            text_ = '..'
+            if self.dotdot_attr:
+                text_ = HTML.span(c=text_, **self.dotdot_attr)
+            nav_items.append(HTML.li(text_))
+
+        for thispage in xrange(leftmost_page, rightmost_page + 1):
+            # Highlight the current page number and do not use a link
+            text_ = str(thispage)
+            if thispage == self.page:
+                # Wrap in a SPAN tag if nolink_attr is set
+                if self.curpage_attr:
+                    text_ = HTML.li(HTML.span(c=text_), **self.curpage_attr)
+                nav_items.append(text_)
+            # Otherwise create just a link to that page
+            else:
+                nav_items.append(HTML.li(self._pagerlink(thispage, text_)))
+
+        # Insert dots if there are pages between the displayed
+        # page numbers and the end of the page range
+        if self.last_page - rightmost_page > 1:
+            text_ = '..'
+            # Wrap in a SPAN tag if nolink_attr is set
+            if self.dotdot_attr:
+                text_ = HTML.span(c=text_, **self.dotdot_attr)
+            nav_items.append(HTML.li(text_))
+
+        # Create a link to the very last page (unless we are on the last
+        # page or there would be no need to insert '..' spacers)
+        if self.page != self.last_page and rightmost_page < self.last_page:
+            nav_items.append(HTML.li(self._pagerlink(self.last_page, self.last_page)))
+
+        #_page_link = url.current()
+        #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
+        #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
+        return self.separator.join(nav_items)
+
+    def pager(self, format='<ul class="pagination">$link_previous ~2~ $link_next</ul>', page_param='page', partial_param='partial',
+        show_if_single_page=False, separator=' ', onclick=None,
+        symbol_first='<<', symbol_last='>>',
+        symbol_previous='<', symbol_next='>',
+        link_attr=None,
+        curpage_attr=None,
+        dotdot_attr=None, **kwargs):
+        self.curpage_attr = curpage_attr or {'class': 'active'}
+        self.separator = separator
+        self.pager_kwargs = kwargs
+        self.page_param = page_param
+        self.partial_param = partial_param
+        self.onclick = onclick
+        self.link_attr = link_attr or {'class': 'pager_link', 'rel': 'prerender'}
+        self.dotdot_attr = dotdot_attr or {'class': 'pager_dotdot'}
+
+        # Don't show navigator if there is no more than one page
+        if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
+            return ''
+
+        from string import Template
+        # Replace ~...~ in token format by range of pages
+        result = re.sub(r'~(\d+)~', self._range, format)
+
+        # Interpolate '%' variables
+        result = Template(result).safe_substitute({
+            'first_page': self.first_page,
+            'last_page': self.last_page,
+            'page': self.page,
+            'page_count': self.page_count,
+            'items_per_page': self.items_per_page,
+            'first_item': self.first_item,
+            'last_item': self.last_item,
+            'item_count': self.item_count,
+            'link_first': self.page > self.first_page and \
+                    self._pagerlink(self.first_page, symbol_first) or '',
+            'link_last': self.page < self.last_page and \
+                    self._pagerlink(self.last_page, symbol_last) or '',
+            'link_previous': HTML.li(self.previous_page and \
+                    self._pagerlink(self.previous_page, symbol_previous) \
+                    or HTML.a(symbol_previous)),
+            'link_next': HTML.li(self.next_page and \
+                    self._pagerlink(self.next_page, symbol_next) \
+                    or HTML.a(symbol_next))
+        })
+
+        return literal(result)
+
+
+class RepoPage(Page):
+
+    def __init__(self, collection, page=1, items_per_page=20,
+                 item_count=None, **kwargs):
+
+        """Create a "RepoPage" instance. special pager for paging
+        repository
+        """
+        # TODO: call baseclass __init__
+        self._url_generator = kwargs.pop('url', url.current)
+
+        # Safe the kwargs class-wide so they can be used in the pager() method
+        self.kwargs = kwargs
+
+        # Save a reference to the collection
+        self.original_collection = collection
+
+        self.collection = collection
+
+        # The self.page is the number of the current page.
+        # The first page has the number 1!
+        try:
+            self.page = int(page)  # make it int() if we get it as a string
+        except (ValueError, TypeError):
+            log.error("Invalid page value: %r" % page)
+            self.page = 1
+
+        self.items_per_page = items_per_page
+
+        # Unless the user tells us how many items the collections has
+        # we calculate that ourselves.
+        if item_count is not None:
+            self.item_count = item_count
+        else:
+            self.item_count = len(self.collection)
+
+        # Compute the number of the first and last available page
+        if self.item_count > 0:
+            self.first_page = 1
+            self.page_count = int(math.ceil(float(self.item_count) /
+                                            self.items_per_page))
+            self.last_page = self.first_page + self.page_count - 1
+
+            # Make sure that the requested page number is the range of
+            # valid pages
+            if self.page > self.last_page:
+                self.page = self.last_page
+            elif self.page < self.first_page:
+                self.page = self.first_page
+
+            # Note: the number of items on this page can be less than
+            #       items_per_page if the last page is not full
+            self.first_item = max(0, (self.item_count) - (self.page *
+                                                          items_per_page))
+            self.last_item = ((self.item_count - 1) - items_per_page *
+                              (self.page - 1))
+
+            self.items = list(self.collection[self.first_item:self.last_item + 1])
+
+            # Links to previous and next page
+            if self.page > self.first_page:
+                self.previous_page = self.page - 1
+            else:
+                self.previous_page = None
+
+            if self.page < self.last_page:
+                self.next_page = self.page + 1
+            else:
+                self.next_page = None
+
+        # No items available
+        else:
+            self.first_page = None
+            self.page_count = 0
+            self.last_page = None
+            self.first_item = None
+            self.last_item = None
+            self.previous_page = None
+            self.next_page = None
+            self.items = []
+
+        # This is a subclass of the 'list' type. Initialise the list now.
+        list.__init__(self, reversed(self.items))
--- a/kallithea/lib/paster_commands/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +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/>.
--- a/kallithea/lib/paster_commands/cache_keys.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +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.lib.paster_commands.cache_keys
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-cleanup-keys paster command for Kallithea
-
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: mar 27, 2013
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import logging
-
-from kallithea.model.meta import Session
-from kallithea.lib.utils import BasePasterCommand
-from kallithea.model.db import CacheInvalidation
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-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 = "Cache keys utils"
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-        _caches = CacheInvalidation.query().order_by(CacheInvalidation.cache_key).all()
-        if self.options.show:
-            for c_obj in _caches:
-                print 'key:%s active:%s' % (c_obj.cache_key, c_obj.cache_active)
-        elif self.options.cleanup:
-            for c_obj in _caches:
-                Session().delete(c_obj)
-                print 'removing key:%s' % (c_obj.cache_key)
-                Session().commit()
-        else:
-            print 'nothing done exiting...'
-        sys.exit(0)
-
-    def update_parser(self):
-        self.parser.add_option(
-            '--show',
-            action='store_true',
-            dest='show',
-            help=("show existing cache keys with together with status")
-        )
-
-        self.parser.add_option(
-            '--cleanup',
-            action="store_true",
-            dest="cleanup",
-            help="cleanup existing cache keys"
-        )
--- a/kallithea/lib/paster_commands/cleanup.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +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.lib.paster_commands.cleanup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-cleanup-repos paster command for Kallithea
-
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Jul 14, 2012
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import re
-import shutil
-import logging
-import datetime
-
-from kallithea.lib.utils import BasePasterCommand, ask_ok, REMOVED_REPO_PAT
-from kallithea.lib.utils2 import safe_str
-from kallithea.model.db import Ui
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-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 = "Cleanup deleted repos"
-
-    def _parse_older_than(self, val):
-        regex = re.compile(r'((?P<days>\d+?)d)?((?P<hours>\d+?)h)?((?P<minutes>\d+?)m)?((?P<seconds>\d+?)s)?')
-        parts = regex.match(val)
-        if not parts:
-            return
-        parts = parts.groupdict()
-        time_params = {}
-        for (name, param) in parts.iteritems():
-            if param:
-                time_params[name] = int(param)
-        return datetime.timedelta(**time_params)
-
-    def _extract_date(self, name):
-        """
-        Extract the date part from rm__<date> pattern of removed repos,
-        and convert it to datetime object
-
-        :param name:
-        """
-        date_part = name[4:19]  # 4:19 since we don't parse milisecods
-        return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-
-        repos_location = Ui.get_repos_location()
-        to_remove = []
-        for dn_, dirs, f in os.walk(safe_str(repos_location)):
-            alldirs = list(dirs)
-            del dirs[:]
-            if ('.hg' in alldirs or
-                '.git' in alldirs or
-                '.svn' in alldirs or
-                'objects' in alldirs and ('refs' in alldirs or 'packed-refs' in f)):
-                continue
-            for loc in alldirs:
-                if REMOVED_REPO_PAT.match(loc):
-                    to_remove.append([os.path.join(dn_, loc),
-                                      self._extract_date(loc)])
-                else:
-                    dirs.append(loc)
-
-        #filter older than (if present)!
-        now = datetime.datetime.now()
-        older_than = self.options.older_than
-        if older_than:
-            to_remove_filtered = []
-            older_than_date = self._parse_older_than(older_than)
-            for name, date_ in to_remove:
-                repo_age = now - date_
-                if repo_age > older_than_date:
-                    to_remove_filtered.append([name, date_])
-
-            to_remove = to_remove_filtered
-            print >> sys.stdout, 'removing %s deleted repos older than %s (%s)' \
-                % (len(to_remove), older_than, older_than_date)
-        else:
-            print >> sys.stdout, 'removing all [%s] deleted repos' \
-                % len(to_remove)
-        if self.options.dont_ask or not to_remove:
-            # don't ask just remove !
-            remove = True
-        else:
-            remove = ask_ok('the following repositories will be deleted completely:\n%s\n'
-                            'are you sure you want to remove them [y/n]?'
-                            % ', \n'.join(['%s removed on %s'
-                    % (safe_str(x[0]), safe_str(x[1])) for x in to_remove]))
-
-        if remove:
-            for path, date_ in to_remove:
-                print >> sys.stdout, 'removing repository %s' % path
-                shutil.rmtree(path)
-        else:
-            print 'nothing done exiting...'
-            sys.exit(0)
-
-    def update_parser(self):
-        self.parser.add_option(
-            '--older-than',
-            action='store',
-            dest='older_than',
-            help=("only remove repos that have been removed "
-                 "at least given time ago. "
-                 "The default is to remove all removed repositories. "
-                 "Possible suffixes: "
-                 "d (days), h (hours), m (minutes), s (seconds). "
-                 "For example --older-than=30d deletes repositories "
-                 "removed more than 30 days ago.")
-            )
-
-        self.parser.add_option(
-            '--dont-ask',
-            action="store_true",
-            dest="dont_ask",
-            help="remove repositories without asking for confirmation."
-        )
--- a/kallithea/lib/paster_commands/install_iis.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-import os
-import sys
-from paste.script.appinstall import AbstractInstallCommand
-from paste.script.command import BadCommand
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-rc_path = dn(dn(dn(os.path.realpath(__file__))))
-sys.path.append(rc_path)
-
-class Command(AbstractInstallCommand):
-    default_verbosity = 1
-    max_args = 1
-    min_args = 1
-    summary = 'Setup IIS given a config file'
-    usage = 'CONFIG_FILE'
-
-    description = '''
-    Script for installing into IIS using isapi-wsgi.
-    '''
-    parser = AbstractInstallCommand.standard_parser(
-        simulate=True, quiet=True, interactive=True)
-    parser.add_option('--virtualdir',
-                      action='store',
-                      dest='virtualdir',
-                      default='/',
-                      help='The virtual folder to install into on IIS')
-
-    def command(self):
-        config_spec = self.args[0]
-        if not config_spec.startswith('config:'):
-            config_spec = 'config:' + config_spec
-        config_file = config_spec[len('config:'):].split('#', 1)[0]
-        config_file = os.path.join(os.getcwd(), config_file)
-        try:
-            import isapi_wsgi
-        except ImportError:
-            raise BadCommand('missing requirement: isapi-wsgi not installed')
-
-        file = '''import sys
-
-if hasattr(sys, "isapidllhandle"):
-    import win32traceutil
-
-import isapi_wsgi
-import os
-
-def __ExtensionFactory__():
-    from paste.deploy import loadapp
-    from paste.script.util.logging_config import fileConfig
-    fileConfig('%(inifile)s')
-    application = loadapp('config:%(inifile)s')
-
-    def app(environ, start_response):
-        user = environ.get('REMOTE_USER', None)
-        if user is not None:
-            os.environ['REMOTE_USER'] = user
-        return application(environ, start_response)
-
-    return isapi_wsgi.ISAPIThreadPoolHandler(app)
-
-if __name__=='__main__':
-    from isapi.install import *
-    params = ISAPIParameters()
-    sm = [ScriptMapParams(Extension="*", Flags=0)]
-    vd = VirtualDirParameters(Name="%(virtualdir)s",
-                              Description = "Kallithea",
-                              ScriptMaps = sm,
-                              ScriptMapUpdate = "replace")
-    params.VirtualDirs = [vd]
-    HandleCommandLine(params)
-'''
-
-        outdata = file % {
-                'inifile': config_file.replace('\\', '\\\\'),
-                'virtualdir': self.options.virtualdir
-                }
-
-        dispatchfile = os.path.join(os.getcwd(), 'dispatch.py')
-        self.ensure_file(dispatchfile, outdata, False)
-        print 'generating', dispatchfile
-
-        print ('run \'python "%s" install\' with administrative privileges '
-            'to generate the _dispatch.dll file and install it into the '
-            'default web site') % (dispatchfile,)
--- a/kallithea/lib/paster_commands/ishell.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +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.lib.paster_commands.ishell
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-interactive shell paster command for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 4, 2013
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import logging
-
-from kallithea.lib.utils import BasePasterCommand
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-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 = "Interactive shell"
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-
-        # imports, used in ipython shell
-        import os
-        import sys
-        import time
-        import shutil
-        import datetime
-        from kallithea.model.db import *
-
-        try:
-            from IPython import embed
-            from IPython.config.loader import Config
-            cfg = Config()
-            cfg.InteractiveShellEmbed.confirm_exit = False
-            embed(config=cfg, banner1="Kallithea IShell.")
-        except ImportError:
-            print 'ipython installation required for ishell'
-            sys.exit(-1)
-
-    def update_parser(self):
-        pass
--- a/kallithea/lib/paster_commands/make_index.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +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.lib.paster_commands.make_index
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-make-index paster command for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Aug 17, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-
-import os
-import sys
-import logging
-
-from string import strip
-from kallithea.model.repo import RepoModel
-from kallithea.lib.utils import BasePasterCommand, load_rcextensions
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-rc_path = dn(dn(dn(os.path.realpath(__file__))))
-sys.path.append(rc_path)
-
-
-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 or updates full text search index"
-
-    def command(self):
-        logging.config.fileConfig(self.path_to_ini_file)
-        #get SqlAlchemy session
-        self._init_session()
-        from pylons import config
-        index_location = config['index_dir']
-        load_rcextensions(config['here'])
-
-        repo_location = self.options.repo_location \
-            if self.options.repo_location else RepoModel().repos_path
-        repo_list = map(strip, self.options.repo_list.split(',')) \
-            if self.options.repo_list else None
-
-        repo_update_list = map(strip, self.options.repo_update_list.split(',')) \
-            if self.options.repo_update_list else None
-
-        #======================================================================
-        # WHOOSH DAEMON
-        #======================================================================
-        from kallithea.lib.pidlock import LockHeld, DaemonLock
-        from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
-        try:
-            l = DaemonLock(file_=os.path.join(dn(dn(index_location)),
-                                              'make_index.lock'))
-            WhooshIndexingDaemon(index_location=index_location,
-                                 repo_location=repo_location,
-                                 repo_list=repo_list,
-                                 repo_update_list=repo_update_list)\
-                .run(full_index=self.options.full_index)
-            l.release()
-        except LockHeld:
-            sys.exit(1)
-
-    def update_parser(self):
-        self.parser.add_option('--repo-location',
-                          action='store',
-                          dest='repo_location',
-                          help="Specifies repositories location to index OPTIONAL",
-                          )
-        self.parser.add_option('--index-only',
-                          action='store',
-                          dest='repo_list',
-                          help="Specifies a comma separated list of repositories "
-                                "to build index on. If not given all repositories "
-                                "are scanned for indexing. OPTIONAL",
-                          )
-        self.parser.add_option('--update-only',
-                          action='store',
-                          dest='repo_update_list',
-                          help="Specifies a comma separated list of repositories "
-                                "to re-build index on. OPTIONAL",
-                          )
-        self.parser.add_option('-f',
-                          action='store_true',
-                          dest='full_index',
-                          help="Specifies that index should be made full i.e"
-                                " destroy old and build from scratch",
-                          default=False)
--- a/kallithea/lib/paster_commands/make_rcextensions.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +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.lib.paster_commands.make_rcextensions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-make-rcext paster command for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Mar 6, 2012
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-
-import os
-import sys
-import pkg_resources
-
-from kallithea.lib.utils import BasePasterCommand, ask_ok
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-rc_path = dn(dn(dn(os.path.realpath(__file__))))
-sys.path.append(rc_path)
-
-
-class Command(BasePasterCommand):
-
-    max_args = 1
-    min_args = 1
-
-    group_name = "Kallithea"
-    takes_config_file = -1
-    parser = BasePasterCommand.standard_parser(verbose=True)
-    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):
-        from pylons import config
-
-        here = config['here']
-        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 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/paster_commands/repo_scan.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +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.lib.paster_commands.repo_scan
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-repo-scan paster command for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Feb 9, 2013
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import logging
-
-from kallithea.model.scm import ScmModel
-from kallithea.lib.utils import BasePasterCommand, repo2db_mapper
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-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 = "Rescan default location for new repositories"
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-        rm_obsolete = self.options.delete_obsolete
-        print 'Now scanning root location for new repos ...'
-        added, removed = repo2db_mapper(ScmModel().repo_scan(),
-                                        remove_obsolete=rm_obsolete)
-        added = ', '.join(added) or '-'
-        removed = ', '.join(removed) or '-'
-        print 'Scan completed.'
-        print 'Added: %s' % added
-        print 'Removed: %s' % removed
-
-    def update_parser(self):
-        self.parser.add_option(
-            '--delete-obsolete',
-            action='store_true',
-            help="Use this flag do delete repositories that are "
-                 "present in Kallithea database but not on the filesystem",
-        )
--- a/kallithea/lib/paster_commands/setup_db.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-import os
-import sys
-from paste.script.appinstall import AbstractInstallCommand
-from paste.script.command import BadCommand
-from paste.deploy import appconfig
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-rc_path = dn(dn(dn(os.path.realpath(__file__))))
-sys.path.append(rc_path)
-
-
-class Command(AbstractInstallCommand):
-
-    default_verbosity = 1
-    max_args = 1
-    min_args = 1
-    summary = "Setup an application, given a config file"
-    usage = "CONFIG_FILE"
-    group_name = "Kallithea"
-
-    description = """\
-
-    Setup Kallithea according to its configuration file.  This is
-    the second part of a two-phase web application installation
-    process (the first phase is prepare-app).  The setup process
-    consist of things like setting up databases, creating super user
-    """
-
-    parser = AbstractInstallCommand.standard_parser(
-        simulate=True, quiet=True, interactive=True)
-    parser.add_option('--user',
-                      action='store',
-                      dest='username',
-                      default=None,
-                      help='Admin Username')
-    parser.add_option('--email',
-                      action='store',
-                      dest='email',
-                      default=None,
-                      help='Admin Email')
-    parser.add_option('--password',
-                      action='store',
-                      dest='password',
-                      default=None,
-                      help='Admin password min 6 chars')
-    parser.add_option('--repos',
-                      action='store',
-                      dest='repos_location',
-                      default=None,
-                      help='Absolute path to repositories location')
-    parser.add_option('--name',
-                      action='store',
-                      dest='section_name',
-                      default=None,
-                      help='The name of the section to set up (default: app:main)')
-    parser.add_option('--force-yes',
-                       action='store_true',
-                       dest='force_ask',
-                       default=None,
-                       help='Force yes to every question')
-    parser.add_option('--force-no',
-                       action='store_false',
-                       dest='force_ask',
-                       default=None,
-                       help='Force no to every question')
-    parser.add_option('--public-access',
-                       action='store_true',
-                       dest='public_access',
-                       default=None,
-                       help='Enable public access on this installation (default)')
-    parser.add_option('--no-public-access',
-                       action='store_false',
-                       dest='public_access',
-                       default=None,
-                       help='Disable public access on this installation ')
-    def command(self):
-        config_spec = self.args[0]
-        section = self.options.section_name
-        if section is None:
-            if '#' in config_spec:
-                config_spec, section = config_spec.split('#', 1)
-            else:
-                section = 'main'
-        if not ':' in section:
-            plain_section = section
-            section = 'app:' + section
-        else:
-            plain_section = section.split(':', 1)[0]
-        if not config_spec.startswith('config:'):
-            config_spec = 'config:' + config_spec
-        if plain_section != 'main':
-            config_spec += '#' + plain_section
-        config_file = config_spec[len('config:'):].split('#', 1)[0]
-        config_file = os.path.join(os.getcwd(), config_file)
-        self.logging_file_config(config_file)
-        conf = appconfig(config_spec, relative_to=os.getcwd())
-        ep_name = conf.context.entry_point_name
-        ep_group = conf.context.protocol
-        dist = conf.context.distribution
-        if dist is None:
-            raise BadCommand(
-                "The section %r is not the application (probably a filter).  "
-                "You should add #section_name, where section_name is the "
-                "section that configures your application" % plain_section)
-        installer = self.get_installer(dist, ep_group, ep_name)
-        installer.setup_config(
-            self, config_file, section, self.sysconfig_install_vars(installer))
-        self.call_sysconfig_functions(
-            'post_setup_hook', installer, config_file)
-
-        print 'Database set up successfully.'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/paster_commands/template.ini.mako	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,648 @@
+## -*- coding: utf-8 -*-
+<%text>################################################################################</%text>
+<%text>################################################################################</%text>
+# Kallithea - config file generated with kallithea-config                      #
+#                                                                              #
+# The %(here)s variable will be replaced with the parent directory of this file#
+<%text>################################################################################</%text>
+<%text>################################################################################</%text>
+
+[DEFAULT]
+
+<%text>################################################################################</%text>
+<%text>## Email settings                                                             ##</%text>
+<%text>##                                                                            ##</%text>
+<%text>## Refer to the documentation ("Email settings") for more details.            ##</%text>
+<%text>##                                                                            ##</%text>
+<%text>## It is recommended to use a valid sender address that passes access         ##</%text>
+<%text>## validation and spam filtering in mail servers.                             ##</%text>
+<%text>################################################################################</%text>
+
+<%text>## 'From' header for application emails. You can optionally add a name.</%text>
+<%text>## Default:</%text>
+#app_email_from = Kallithea
+<%text>## Examples:</%text>
+#app_email_from = Kallithea <kallithea-noreply@example.com>
+#app_email_from = kallithea-noreply@example.com
+
+<%text>## Subject prefix for application emails.</%text>
+<%text>## A space between this prefix and the real subject is automatically added.</%text>
+<%text>## Default:</%text>
+#email_prefix =
+<%text>## Example:</%text>
+#email_prefix = [Kallithea]
+
+<%text>## Recipients for error emails and fallback recipients of application mails.</%text>
+<%text>## Multiple addresses can be specified, comma-separated.</%text>
+<%text>## Only addresses are allowed, do not add any name part.</%text>
+<%text>## Default:</%text>
+#email_to =
+<%text>## Examples:</%text>
+#email_to = admin@example.com
+#email_to = admin@example.com,another_admin@example.com
+email_to =
+
+<%text>## 'From' header for error emails. You can optionally add a name.</%text>
+<%text>## Default: (none)</%text>
+<%text>## Examples:</%text>
+#error_email_from = Kallithea Errors <kallithea-noreply@example.com>
+#error_email_from = kallithea_errors@example.com
+error_email_from =
+
+<%text>## SMTP server settings</%text>
+<%text>## If specifying credentials, make sure to use secure connections.</%text>
+<%text>## Default: Send unencrypted unauthenticated mails to the specified smtp_server.</%text>
+<%text>## For "SSL", use smtp_use_ssl = true and smtp_port = 465.</%text>
+<%text>## For "STARTTLS", use smtp_use_tls = true and smtp_port = 587.</%text>
+smtp_server =
+#smtp_username =
+#smtp_password =
+smtp_port =
+#smtp_use_ssl = false
+#smtp_use_tls = false
+
+%if http_server != 'uwsgi':
+<%text>## Entry point for 'gearbox serve'</%text>
+[server:main]
+host = ${host}
+port = ${port}
+
+%if http_server == 'gearbox':
+<%text>## Gearbox default web server ##</%text>
+use = egg:gearbox#wsgiref
+<%text>## nr of worker threads to spawn</%text>
+threadpool_workers = 1
+<%text>## max request before thread respawn</%text>
+threadpool_max_requests = 100
+<%text>## option to use threads of process</%text>
+use_threadpool = true
+
+%elif http_server == 'gevent':
+<%text>## Gearbox gevent web server ##</%text>
+use = egg:gearbox#gevent
+
+%elif http_server == 'waitress':
+<%text>## WAITRESS ##</%text>
+use = egg:waitress#main
+<%text>## number of worker threads</%text>
+threads = 1
+<%text>## MAX BODY SIZE 100GB</%text>
+max_request_body_size = 107374182400
+<%text>## use poll instead of select, fixes fd limits, may not work on old</%text>
+<%text>## windows systems.</%text>
+#asyncore_use_poll = True
+
+%elif http_server == 'gunicorn':
+<%text>## GUNICORN ##</%text>
+use = egg:gunicorn#main
+<%text>## number of process workers. You must set `instance_id = *` when this option</%text>
+<%text>## is set to more than one worker</%text>
+workers = 4
+<%text>## process name</%text>
+proc_name = kallithea
+<%text>## type of worker class, one of sync, eventlet, gevent, tornado</%text>
+<%text>## recommended for bigger setup is using of of other than sync one</%text>
+worker_class = sync
+max_requests = 1000
+<%text>## amount of time a worker can handle request before it gets killed and</%text>
+<%text>## restarted</%text>
+timeout = 3600
+
+%endif
+%else:
+<%text>## UWSGI ##</%text>
+<%text>## run with uwsgi --ini-paste-logged <inifile.ini></%text>
+[uwsgi]
+socket = /tmp/uwsgi.sock
+master = true
+http = ${host}:${port}
+
+<%text>## set as daemon and redirect all output to file</%text>
+#daemonize = ./uwsgi_kallithea.log
+
+<%text>## master process PID</%text>
+pidfile = ./uwsgi_kallithea.pid
+
+<%text>## stats server with workers statistics, use uwsgitop</%text>
+<%text>## for monitoring, `uwsgitop 127.0.0.1:1717`</%text>
+stats = 127.0.0.1:1717
+memory-report = true
+
+<%text>## log 5XX errors</%text>
+log-5xx = true
+
+<%text>## Set the socket listen queue size.</%text>
+listen = 128
+
+<%text>## Gracefully Reload workers after the specified amount of managed requests</%text>
+<%text>## (avoid memory leaks).</%text>
+max-requests = 1000
+
+<%text>## enable large buffers</%text>
+buffer-size = 65535
+
+<%text>## socket and http timeouts ##</%text>
+http-timeout = 3600
+socket-timeout = 3600
+
+<%text>## Log requests slower than the specified number of milliseconds.</%text>
+log-slow = 10
+
+<%text>## Exit if no app can be loaded.</%text>
+need-app = true
+
+<%text>## Set lazy mode (load apps in workers instead of master).</%text>
+lazy = true
+
+<%text>## scaling ##</%text>
+<%text>## set cheaper algorithm to use, if not set default will be used</%text>
+cheaper-algo = spare
+
+<%text>## minimum number of workers to keep at all times</%text>
+cheaper = 1
+
+<%text>## number of workers to spawn at startup</%text>
+cheaper-initial = 1
+
+<%text>## maximum number of workers that can be spawned</%text>
+workers = 4
+
+<%text>## how many workers should be spawned at a time</%text>
+cheaper-step = 1
+
+%endif
+<%text>## middleware for hosting the WSGI application under a URL prefix</%text>
+#[filter:proxy-prefix]
+#use = egg:PasteDeploy#prefix
+#prefix = /<your-prefix>
+
+[app:main]
+use = egg:kallithea
+<%text>## enable proxy prefix middleware</%text>
+#filter-with = proxy-prefix
+
+full_stack = true
+static_files = true
+
+<%text>## Internationalization (see setup documentation for details)</%text>
+<%text>## By default, the language requested by the browser is used if available.</%text>
+#i18n.enable = false
+<%text>## Fallback language, empty for English (valid values are the names of subdirectories in kallithea/i18n):</%text>
+i18n.lang =
+
+cache_dir = %(here)s/data
+index_dir = %(here)s/data/index
+
+<%text>## uncomment and set this path to use archive download cache</%text>
+archive_cache_dir = %(here)s/tarballcache
+
+<%text>## change this to unique ID for security</%text>
+app_instance_uuid = ${uuid()}
+
+<%text>## cut off limit for large diffs (size in bytes)</%text>
+cut_off_limit = 256000
+
+<%text>## force https in Kallithea, fixes https redirects, assumes it's always https</%text>
+force_https = false
+
+<%text>## use Strict-Transport-Security headers</%text>
+use_htsts = false
+
+<%text>## number of commits stats will parse on each iteration</%text>
+commit_parse_limit = 25
+
+<%text>## path to git executable</%text>
+git_path = git
+
+<%text>## git rev filter option, --all is the default filter, if you need to</%text>
+<%text>## hide all refs in changelog switch this to --branches --tags</%text>
+#git_rev_filter = --branches --tags
+
+<%text>## RSS feed options</%text>
+rss_cut_off_limit = 256000
+rss_items_per_page = 10
+rss_include_diff = false
+
+<%text>## options for showing and identifying changesets</%text>
+show_sha_length = 12
+show_revision_number = false
+
+<%text>## Canonical URL to use when creating full URLs in UI and texts.</%text>
+<%text>## Useful when the site is available under different names or protocols.</%text>
+<%text>## Defaults to what is provided in the WSGI environment.</%text>
+#canonical_url = https://kallithea.example.com/repos
+
+<%text>## gist URL alias, used to create nicer urls for gist. This should be an</%text>
+<%text>## url that does rewrites to _admin/gists/<gistid>.</%text>
+<%text>## example: http://gist.example.com/{gistid}. Empty means use the internal</%text>
+<%text>## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid></%text>
+gist_alias_url =
+
+<%text>## white list of API enabled controllers. This allows to add list of</%text>
+<%text>## controllers to which access will be enabled by api_key. eg: to enable</%text>
+<%text>## api access to raw_files put `FilesController:raw`, to enable access to patches</%text>
+<%text>## add `ChangesetController:changeset_patch`. This list should be "," separated</%text>
+<%text>## Syntax is <ControllerClass>:<function>. Check debug logs for generated names</%text>
+<%text>## Recommended settings below are commented out:</%text>
+api_access_controllers_whitelist =
+#    ChangesetController:changeset_patch,
+#    ChangesetController:changeset_raw,
+#    FilesController:raw,
+#    FilesController:archivefile
+
+<%text>## default encoding used to convert from and to unicode</%text>
+<%text>## can be also a comma separated list of encoding in case of mixed encodings</%text>
+default_encoding = utf-8
+
+<%text>## Set Mercurial encoding, similar to setting HGENCODING before launching Kallithea</%text>
+hgencoding = utf-8
+
+<%text>## issue tracker for Kallithea (leave blank to disable, absent for default)</%text>
+#bugtracker = https://bitbucket.org/conservancy/kallithea/issues
+
+<%text>## issue tracking mapping for commit messages, comments, PR descriptions, ...</%text>
+<%text>## Refer to the documentation ("Integration with issue trackers") for more details.</%text>
+
+<%text>## regular expression to match issue references</%text>
+<%text>## This pattern may/should contain parenthesized groups, that can</%text>
+<%text>## be referred to in issue_server_link or issue_sub using Python backreferences</%text>
+<%text>## (e.g. \1, \2, ...). You can also create named groups with '(?P<groupname>)'.</%text>
+<%text>## To require mandatory whitespace before the issue pattern, use:</%text>
+<%text>## (?:^|(?<=\s)) before the actual pattern, and for mandatory whitespace</%text>
+<%text>## behind the issue pattern, use (?:$|(?=\s)) after the actual pattern.</%text>
+
+issue_pat = #(\d+)
+
+<%text>## server url to the issue</%text>
+<%text>## This pattern may/should contain backreferences to parenthesized groups in issue_pat.</%text>
+<%text>## A backreference can be \1, \2, ... or \g<groupname> if you specified a named group</%text>
+<%text>## called 'groupname' in issue_pat.</%text>
+<%text>## The special token {repo} is replaced with the full repository name</%text>
+<%text>## including repository groups, while {repo_name} is replaced with just</%text>
+<%text>## the name of the repository.</%text>
+
+issue_server_link = https://issues.example.com/{repo}/issue/\1
+
+<%text>## substitution pattern to use as the link text</%text>
+<%text>## If issue_sub is empty, the text matched by issue_pat is retained verbatim</%text>
+<%text>## for the link text. Otherwise, the link text is that of issue_sub, with any</%text>
+<%text>## backreferences to groups in issue_pat replaced.</%text>
+
+issue_sub =
+
+<%text>## issue_pat, issue_server_link and issue_sub can have suffixes to specify</%text>
+<%text>## multiple patterns, to other issues server, wiki or others</%text>
+<%text>## below an example how to create a wiki pattern</%text>
+# wiki-some-id -> https://wiki.example.com/some-id
+
+#issue_pat_wiki = wiki-(\S+)
+#issue_server_link_wiki = https://wiki.example.com/\1
+#issue_sub_wiki = WIKI-\1
+
+<%text>## alternative return HTTP header for failed authentication. Default HTTP</%text>
+<%text>## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with</%text>
+<%text>## handling that. Set this variable to 403 to return HTTPForbidden</%text>
+auth_ret_code =
+
+<%text>## locking return code. When repository is locked return this HTTP code. 2XX</%text>
+<%text>## codes don't break the transactions while 4XX codes do</%text>
+lock_ret_code = 423
+
+<%text>## allows to change the repository location in settings page</%text>
+allow_repo_location_change = True
+
+<%text>## allows to setup custom hooks in settings page</%text>
+allow_custom_hooks_settings = True
+
+<%text>## extra extensions for indexing, space separated and without the leading '.'.</%text>
+# index.extensions =
+#    gemfile
+#    lock
+
+<%text>## extra filenames for indexing, space separated</%text>
+# index.filenames =
+#    .dockerignore
+#    .editorconfig
+#    INSTALL
+#    CHANGELOG
+
+<%text>####################################</%text>
+<%text>###        CELERY CONFIG        ####</%text>
+<%text>####################################</%text>
+
+use_celery = false
+
+<%text>## Example: connect to the virtual host 'rabbitmqhost' on localhost as rabbitmq:</%text>
+broker.url = amqp://rabbitmq:qewqew@localhost:5672/rabbitmqhost
+
+celery.imports = kallithea.lib.celerylib.tasks
+celery.accept.content = pickle
+celery.result.backend = amqp
+celery.result.dburi = amqp://
+celery.result.serialier = json
+
+#celery.send.task.error.emails = true
+#celery.amqp.task.result.expires = 18000
+
+celeryd.concurrency = 2
+celeryd.max.tasks.per.child = 1
+
+<%text>## If true, tasks will never be sent to the queue, but executed locally instead.</%text>
+celery.always.eager = false
+
+<%text>####################################</%text>
+<%text>###         BEAKER CACHE        ####</%text>
+<%text>####################################</%text>
+
+beaker.cache.data_dir = %(here)s/data/cache/data
+beaker.cache.lock_dir = %(here)s/data/cache/lock
+
+beaker.cache.regions = short_term,long_term,sql_cache_short
+
+beaker.cache.short_term.type = memory
+beaker.cache.short_term.expire = 60
+beaker.cache.short_term.key_length = 256
+
+beaker.cache.long_term.type = memory
+beaker.cache.long_term.expire = 36000
+beaker.cache.long_term.key_length = 256
+
+beaker.cache.sql_cache_short.type = memory
+beaker.cache.sql_cache_short.expire = 10
+beaker.cache.sql_cache_short.key_length = 256
+
+<%text>####################################</%text>
+<%text>###       BEAKER SESSION        ####</%text>
+<%text>####################################</%text>
+
+<%text>## Name of session cookie. Should be unique for a given host and path, even when running</%text>
+<%text>## on different ports. Otherwise, cookie sessions will be shared and messed up.</%text>
+beaker.session.key = kallithea
+<%text>## Sessions should always only be accessible by the browser, not directly by JavaScript.</%text>
+beaker.session.httponly = true
+<%text>## Session lifetime. 2592000 seconds is 30 days.</%text>
+beaker.session.timeout = 2592000
+
+<%text>## Server secret used with HMAC to ensure integrity of cookies.</%text>
+beaker.session.secret = ${uuid()}
+<%text>## Further, encrypt the data with AES.</%text>
+#beaker.session.encrypt_key = <key_for_encryption>
+#beaker.session.validate_key = <validation_key>
+
+<%text>## Type of storage used for the session, current types are</%text>
+<%text>## dbm, file, memcached, database, and memory.</%text>
+
+<%text>## File system storage of session data. (default)</%text>
+#beaker.session.type = file
+
+<%text>## Cookie only, store all session data inside the cookie. Requires secure secrets.</%text>
+#beaker.session.type = cookie
+
+<%text>## Database storage of session data.</%text>
+#beaker.session.type = ext:database
+#beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
+#beaker.session.table_name = db_session
+
+%if error_aggregation_service == 'appenlight':
+<%text>############################</%text>
+<%text>## ERROR HANDLING SYSTEMS ##</%text>
+<%text>############################</%text>
+
+# Propagate email settings to ErrorReporter of TurboGears2
+# You do not normally need to change these lines
+get trace_errors.error_email = email_to
+get trace_errors.smtp_server = smtp_server
+get trace_errors.smtp_port = smtp_port
+get trace_errors.from_address = error_email_from
+
+<%text>####################</%text>
+<%text>### [appenlight] ###</%text>
+<%text>####################</%text>
+
+<%text>## AppEnlight is tailored to work with Kallithea, see</%text>
+<%text>## http://appenlight.com for details how to obtain an account</%text>
+<%text>## you must install python package `appenlight_client` to make it work</%text>
+
+<%text>## appenlight enabled</%text>
+appenlight = false
+
+appenlight.server_url = https://api.appenlight.com
+appenlight.api_key = YOUR_API_KEY
+
+<%text>## TWEAK AMOUNT OF INFO SENT HERE</%text>
+
+<%text>## enables 404 error logging (default False)</%text>
+appenlight.report_404 = false
+
+<%text>## time in seconds after request is considered being slow (default 1)</%text>
+appenlight.slow_request_time = 1
+
+<%text>## record slow requests in application</%text>
+<%text>## (needs to be enabled for slow datastore recording and time tracking)</%text>
+appenlight.slow_requests = true
+
+<%text>## enable hooking to application loggers</%text>
+#appenlight.logging = true
+
+<%text>## minimum log level for log capture</%text>
+#appenlight.logging.level = WARNING
+
+<%text>## send logs only from erroneous/slow requests</%text>
+<%text>## (saves API quota for intensive logging)</%text>
+appenlight.logging_on_error = false
+
+<%text>## list of additional keywords that should be grabbed from environ object</%text>
+<%text>## can be string with comma separated list of words in lowercase</%text>
+<%text>## (by default client will always send following info:</%text>
+<%text>## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that</%text>
+<%text>## start with HTTP* this list be extended with additional keywords here</%text>
+appenlight.environ_keys_whitelist =
+
+<%text>## list of keywords that should be blanked from request object</%text>
+<%text>## can be string with comma separated list of words in lowercase</%text>
+<%text>## (by default client will always blank keys that contain following words</%text>
+<%text>## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'</%text>
+<%text>## this list be extended with additional keywords set here</%text>
+appenlight.request_keys_blacklist =
+
+<%text>## list of namespaces that should be ignores when gathering log entries</%text>
+<%text>## can be string with comma separated list of namespaces</%text>
+<%text>## (by default the client ignores own entries: appenlight_client.client)</%text>
+appenlight.log_namespace_blacklist =
+
+%elif error_aggregation_service == 'sentry':
+<%text>################</%text>
+<%text>### [sentry] ###</%text>
+<%text>################</%text>
+
+<%text>## sentry is a alternative open source error aggregator</%text>
+<%text>## you must install python packages `sentry` and `raven` to enable</%text>
+
+sentry.dsn = YOUR_DNS
+sentry.servers =
+sentry.name =
+sentry.key =
+sentry.public_key =
+sentry.secret_key =
+sentry.project =
+sentry.site =
+sentry.include_paths =
+sentry.exclude_paths =
+
+%endif
+<%text>################################################################################</%text>
+<%text>## WARNING: *DEBUG MODE MUST BE OFF IN A PRODUCTION ENVIRONMENT*              ##</%text>
+<%text>## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##</%text>
+<%text>## execute malicious code after an exception is raised.                       ##</%text>
+<%text>################################################################################</%text>
+debug = false
+
+<%text>##################################</%text>
+<%text>###       LOGVIEW CONFIG       ###</%text>
+<%text>##################################</%text>
+
+logview.sqlalchemy = #faa
+logview.pylons.templating = #bfb
+logview.pylons.util = #eee
+
+<%text>#########################################################</%text>
+<%text>### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###</%text>
+<%text>#########################################################</%text>
+
+%if database_engine == 'sqlite':
+# SQLITE [default]
+sqlalchemy.url = sqlite:///%(here)s/kallithea.db?timeout=60
+
+%elif database_engine == 'postgres':
+# POSTGRESQL
+sqlalchemy.url = postgresql://user:pass@localhost/kallithea
+
+%elif database_engine == 'mysql':
+# MySQL
+sqlalchemy.url = mysql://user:pass@localhost/kallithea?charset=utf8
+
+%endif
+# see sqlalchemy docs for others
+
+sqlalchemy.pool_recycle = 3600
+
+<%text>################################</%text>
+<%text>### ALEMBIC CONFIGURATION   ####</%text>
+<%text>################################</%text>
+
+[alembic]
+script_location = kallithea:alembic
+
+<%text>################################</%text>
+<%text>### LOGGING CONFIGURATION   ####</%text>
+<%text>################################</%text>
+
+[loggers]
+keys = root, routes, kallithea, sqlalchemy, tg, gearbox, beaker, templates, whoosh_indexer, werkzeug, backlash
+
+[handlers]
+keys = console, console_sql
+
+[formatters]
+keys = generic, color_formatter, color_formatter_sql
+
+<%text>#############</%text>
+<%text>## LOGGERS ##</%text>
+<%text>#############</%text>
+
+[logger_root]
+level = NOTSET
+handlers = console
+
+[logger_routes]
+level = WARN
+handlers =
+qualname = routes.middleware
+<%text>## "level = DEBUG" logs the route matched and routing variables.</%text>
+propagate = 1
+
+[logger_beaker]
+level = WARN
+handlers =
+qualname = beaker.container
+propagate = 1
+
+[logger_templates]
+level = WARN
+handlers =
+qualname = pylons.templating
+propagate = 1
+
+[logger_kallithea]
+level = WARN
+handlers =
+qualname = kallithea
+propagate = 1
+
+[logger_tg]
+level = WARN
+handlers =
+qualname = tg
+propagate = 1
+
+[logger_gearbox]
+level = WARN
+handlers =
+qualname = gearbox
+propagate = 1
+
+[logger_sqlalchemy]
+level = WARN
+handlers = console_sql
+qualname = sqlalchemy.engine
+propagate = 0
+
+[logger_whoosh_indexer]
+level = WARN
+handlers =
+qualname = whoosh_indexer
+propagate = 1
+
+[logger_werkzeug]
+level = WARN
+handlers =
+qualname = werkzeug
+propagate = 1
+
+[logger_backlash]
+level = WARN
+handlers =
+qualname = backlash
+propagate = 1
+
+<%text>##############</%text>
+<%text>## HANDLERS ##</%text>
+<%text>##############</%text>
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+formatter = generic
+
+[handler_console_sql]
+class = StreamHandler
+args = (sys.stderr,)
+formatter = generic
+
+<%text>################</%text>
+<%text>## FORMATTERS ##</%text>
+<%text>################</%text>
+
+[formatter_generic]
+format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %Y-%m-%d %H:%M:%S
+
+[formatter_color_formatter]
+class = kallithea.lib.colored_formatter.ColorFormatter
+format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %Y-%m-%d %H:%M:%S
+
+[formatter_color_formatter_sql]
+class = kallithea.lib.colored_formatter.ColorFormatterSql
+format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %Y-%m-%d %H:%M:%S
--- a/kallithea/lib/paster_commands/update_repoinfo.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +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.lib.paster_commands.update_repoinfo
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-update-repoinfo paster command for Kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Jul 14, 2012
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-import os
-import sys
-import logging
-import string
-
-from kallithea.lib.utils import BasePasterCommand
-from kallithea.model.db import Repository
-from kallithea.model.repo import RepoModel
-from kallithea.model.meta import Session
-
-# Add location of top level folder to sys.path
-from os.path import dirname as dn
-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 = "Updates repositories caches for last changeset"
-
-    def command(self):
-        #get SqlAlchemy session
-        self._init_session()
-
-        repo_update_list = map(string.strip,
-                               self.options.repo_update_list.split(',')) \
-                               if self.options.repo_update_list else None
-
-        if repo_update_list is not None:
-            repo_list = list(Repository.query()\
-                .filter(Repository.repo_name.in_(repo_update_list)))
-        else:
-            repo_list = Repository.getAll()
-        RepoModel.update_repoinfo(repositories=repo_list)
-        Session().commit()
-
-        if self.options.invalidate_cache:
-            for r in repo_list:
-                r.set_invalidate()
-        print 'Updated cache for %s repositories' % (len(repo_list))
-
-    def update_parser(self):
-        self.parser.add_option('--update-only',
-                           action='store',
-                           dest='repo_update_list',
-                           help="Specifies a comma separated list of repositories "
-                                "to update last commit info for. OPTIONAL")
-        self.parser.add_option('--invalidate-cache',
-                           action='store_true',
-                           dest='invalidate_cache',
-                           help="Trigger cache invalidation event for repos. "
-                                "OPTIONAL")
--- a/kallithea/lib/pidlock.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/pidlock.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,23 +28,21 @@
     """daemon locking
     USAGE:
     try:
-        l = DaemonLock(file_='/path/tolockfile',desc='test lock')
+        l = DaemonLock('/path/tolockfile',desc='test lock')
         main()
         l.release()
     except LockHeld:
         sys.exit(1)
     """
 
-    def __init__(self, file_=None, callbackfn=None,
+    def __init__(self, file_, callbackfn=None,
                  desc='daemon lock', debug=False):
-
-        lock_name = os.path.join(os.path.dirname(__file__), 'running.lock')
-        self.pidfile = file_ if file_ else lock_name
+        self.pidfile = file_
         self.callbackfn = callbackfn
         self.desc = desc
         self.debug = debug
         self.held = False
-        #run the lock automatically !
+        # run the lock automatically!
         self.lock()
         self._finalize = Finalize(self, DaemonLock._on_finalize,
                                   args=(self, debug), exitpriority=10)
--- a/kallithea/lib/profiler.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-
-import objgraph
-import cProfile
-import pstats
-import cgi
-import pprint
-import threading
-
-from StringIO import StringIO
-
-
-class ProfilingMiddleware(object):
-    def __init__(self, app):
-        self.lock = threading.Lock()
-        self.app = app
-
-    def __call__(self, environ, start_response):
-        with self.lock:
-            profiler = cProfile.Profile()
-
-            def run_app(*a, **kw):
-                self.response = self.app(environ, start_response)
-
-            profiler.runcall(run_app, environ, start_response)
-
-            profiler.snapshot_stats()
-
-            stats = pstats.Stats(profiler)
-            stats.sort_stats('calls') #cumulative
-
-            # Redirect output
-            out = StringIO()
-            stats.stream = out
-
-            stats.print_stats()
-
-            resp = ''.join(self.response)
-
-            # Lets at least only put this on html-like responses.
-            if resp.strip().startswith('<'):
-                ## The profiling info is just appended to the response.
-                ##  Browsers don't mind this.
-                resp += ('<pre style="text-align:left; '
-                         'border-top: 4px dashed red; padding: 1em;">')
-                resp += cgi.escape(out.getvalue(), True)
-
-                ct = objgraph.show_most_common_types()
-                print ct
-
-                resp += ct if ct else '---'
-
-                output = StringIO()
-                pprint.pprint(environ, output, depth=3)
-
-                resp += cgi.escape(output.getvalue(), True)
-                resp += '</pre>'
-
-            return resp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/lib/pygmentsutils.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.pygmentsutils
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Functions for extracting internal Pygments data.
+
+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: Jan 5, 2011
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+from collections import defaultdict
+from itertools import ifilter
+
+from pygments import lexers
+
+
+def get_extension_descriptions():
+    """
+    Based on what's inside pygments lexers, return a mapping from lowercase
+    extensions to lists of very brief descriptions.
+    """
+    ext_descs = defaultdict(list)
+
+    for lx, t in sorted(lexers.LEXERS.items()):
+        desc = lx.replace('Lexer', '')
+        for glob in t[-2]:
+            s = glob.lstrip('*').lstrip('.').lower()
+            start = s.find('[')
+            if start > -1 and s.endswith(']'):
+                # expand trailing [] range
+                prefix = s[:start]
+                for char in s[start + 1:-1]:
+                    ext_descs[prefix + char].append(desc)
+            else:
+                # use stripped glob as extension
+                ext_descs[s].append(desc)
+
+    return dict(ext_descs)
+
+
+def get_index_filenames():
+    """
+    Get list of known indexable filenames from pygment lexer internals
+    """
+
+    filenames = []
+
+    def likely_filename(s):
+        return s.find('*') == -1 and s.find('[') == -1
+
+    for lx, t in sorted(lexers.LEXERS.items()):
+        for f in ifilter(likely_filename, t[-2]):
+            filenames.append(f)
+
+    return filenames
+
+
+def get_custom_lexer(extension):
+    """
+    returns a custom lexer if it's defined in rcextensions module, or None
+    if there's no custom lexer defined
+    """
+    import kallithea
+    lexer_name = getattr(kallithea.EXTENSIONS, 'EXTRA_LEXERS', {}).get(extension)
+    if lexer_name is None:
+        return None
+    return lexers.get_lexer_by_name(lexer_name)
--- a/kallithea/lib/rcmail/response.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/rcmail/response.py	Sun Mar 31 21:28:56 2019 +0200
@@ -196,7 +196,7 @@
         self.attachments.append({'filename': filename,
                                  'content_type': content_type,
                                  'data': data,
-                                 'disposition': disposition,})
+                                 'disposition': disposition})
 
     def attach_part(self, part):
         """
--- a/kallithea/lib/rcmail/smtp_mailer.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/rcmail/smtp_mailer.py	Sun Mar 31 21:28:56 2019 +0200
@@ -60,9 +60,9 @@
         self.debug = debug
         self.auth = smtp_auth
 
-    def send(self, recipients=[], subject='', body='', html='',
+    def send(self, recipients=None, subject='', body='', html='',
              attachment_files=None, headers=None):
-
+        recipients = recipients or []
         if isinstance(recipients, basestring):
             recipients = [recipients]
         if headers is None:
--- a/kallithea/lib/rcmail/utils.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/rcmail/utils.py	Sun Mar 31 21:28:56 2019 +0200
@@ -16,4 +16,5 @@
             self._fqdn = socket.getfqdn()
         return self._fqdn
 
+
 DNS_NAME = CachedDnsName()
--- a/kallithea/lib/recaptcha.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/recaptcha.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,10 +1,7 @@
 # -*- coding: utf-8 -*-
 import urllib
 import urllib2
-
-API_SSL_SERVER = "https://www.google.com/recaptcha/api"
-API_SERVER = "http://www.google.com/recaptcha/api"
-VERIFY_SERVER = "www.google.com"
+import json
 
 
 class RecaptchaResponse(object):
@@ -16,53 +13,17 @@
         return '<RecaptchaResponse:%s>' % (self.is_valid)
 
 
-def displayhtml(public_key, use_ssl=False, error=None):
-    """Gets the HTML to display for reCAPTCHA
-
-    public_key -- The public api key
-    use_ssl -- Should the request be sent over ssl?
-    error -- An error message to display (from RecaptchaResponse.error_code)"""
-
-    error_param = ''
-    if error:
-        error_param = '&error=%s' % error
-
-    if use_ssl:
-        server = API_SSL_SERVER
-    else:
-        server = API_SERVER
-
-    return """<script type="text/javascript" src="%(ApiServer)s/challenge?k=%(PublicKey)s%(ErrorParam)s"></script>
+def submit(g_recaptcha_response, private_key, remoteip):
+    """
+    Submits a reCAPTCHA request for verification. Returns RecaptchaResponse for the request
 
-<noscript>
-  <iframe src="%(ApiServer)s/noscript?k=%(PublicKey)s%(ErrorParam)s" height="300" width="500" frameborder="0"></iframe><br />
-  <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
-  <input type='hidden' name='recaptcha_response_field' value='manual_challenge' />
-</noscript>
-""" % {
-        'ApiServer': server,
-        'PublicKey': public_key,
-        'ErrorParam': error_param,
-    }
-
-
-def submit(recaptcha_challenge_field, recaptcha_response_field, private_key,
-           remoteip):
-    """
-    Submits a reCAPTCHA request for verification. Returns RecaptchaResponse
-    for the request
-
-    recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form
-    recaptcha_response_field -- The value of recaptcha_response_field from the form
+    g_recaptcha_response -- The value of g_recaptcha_response from the form
     private_key -- your reCAPTCHA private key
     remoteip -- the user's IP address
     """
 
-    if not (recaptcha_response_field and recaptcha_challenge_field and
-                len(recaptcha_response_field) and len(
-            recaptcha_challenge_field)):
-        return RecaptchaResponse(is_valid=False,
-                                 error_code='incorrect-captcha-sol')
+    if not (g_recaptcha_response and len(g_recaptcha_response)):
+        return RecaptchaResponse(is_valid=False, error_code='incorrect-captcha-sol')
 
     def encode_if_necessary(s):
         if isinstance(s, unicode):
@@ -70,14 +31,13 @@
         return s
 
     params = urllib.urlencode({
-        'privatekey': encode_if_necessary(private_key),
+        'secret': encode_if_necessary(private_key),
         'remoteip': encode_if_necessary(remoteip),
-        'challenge': encode_if_necessary(recaptcha_challenge_field),
-        'response': encode_if_necessary(recaptcha_response_field),
+        'response': encode_if_necessary(g_recaptcha_response),
     })
 
-    request = urllib2.Request(
-        url="http://%s/recaptcha/api/verify" % VERIFY_SERVER,
+    req = urllib2.Request(
+        url="https://www.google.com/recaptcha/api/siteverify",
         data=params,
         headers={
             "Content-type": "application/x-www-form-urlencoded",
@@ -85,14 +45,15 @@
         }
     )
 
-    httpresp = urllib2.urlopen(request)
-
-    return_values = httpresp.read().splitlines()
+    httpresp = urllib2.urlopen(req)
+    return_values = json.loads(httpresp.read())
     httpresp.close()
 
-    return_code = return_values[0]
-
-    if return_code == "true":
+    if not (isinstance(return_values, dict)):
+        return RecaptchaResponse(is_valid=False, error_code='incorrect-captcha-sol')
+    elif (("success" in return_values) and ((return_values["success"] == True) or (return_values["success"] == "true"))):
         return RecaptchaResponse(is_valid=True)
+    elif (("error-codes" in return_values) and isinstance(return_values["error-codes"], list) and (len(return_values["error-codes"]) > 0)):
+        return RecaptchaResponse(is_valid=False, error_code=return_values["error-codes"][0])
     else:
-        return RecaptchaResponse(is_valid=False, error_code=return_values[1])
+        return RecaptchaResponse(is_valid=False, error_code='incorrect-captcha-sol')
--- a/kallithea/lib/utils.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/utils.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,30 +30,20 @@
 import logging
 import datetime
 import traceback
-import paste
 import beaker
-import tarfile
-import shutil
-import decorator
-import warnings
-from os.path import abspath
-from os.path import dirname as dn, join as jn
 
-from paste.script.command import Command, BadCommand
-
-from webhelpers.text import collapse, remove_formatting, strip_tags
+from tg import request, response
+from tg.i18n import ugettext as _
 from beaker.cache import _cache_decorate
 
-from kallithea import BRAND
-
 from kallithea.lib.vcs.utils.hgcompat import ui, config
 from kallithea.lib.vcs.utils.helpers import get_scm
 from kallithea.lib.vcs.exceptions import VCSError
 
+from kallithea.lib.exceptions import HgsubversionImportError
 from kallithea.model import meta
 from kallithea.model.db import Repository, User, Ui, \
     UserLog, RepoGroup, Setting, UserGroup
-from kallithea.model.meta import Session
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.lib.utils2 import safe_str, safe_unicode, get_current_authuser
 from kallithea.lib.vcs.utils.fakemod import create_module
@@ -63,42 +53,6 @@
 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}_.*')
 
 
-def recursive_replace(str_, replace=' '):
-    """
-    Recursive replace of given sign to just one instance
-
-    :param str_: given string
-    :param replace: char to find and replace multiple instances
-
-    Examples::
-    >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
-    'Mighty-Mighty-Bo-sstones'
-    """
-
-    if str_.find(replace * 2) == -1:
-        return str_
-    else:
-        str_ = str_.replace(replace * 2, replace)
-        return recursive_replace(str_, replace)
-
-
-def repo_name_slug(value):
-    """
-    Return slug of name of repository
-    This function is called on each creation/modification
-    of repository to prevent bad names in repo
-    """
-
-    slug = remove_formatting(value)
-    slug = strip_tags(slug)
-
-    for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
-        slug = slug.replace(c, '-')
-    slug = recursive_replace(slug, '-')
-    slug = collapse(slug, '-')
-    return slug
-
-
 #==============================================================================
 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
 #==============================================================================
@@ -149,7 +103,7 @@
     return None
 
 
-def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
+def action_logger(user, action, repo, ipaddr='', commit=False):
     """
     Action logger for various actions made by users
 
@@ -160,12 +114,9 @@
     :param repo: string name of repository or object containing repo_id,
         that action was made on
     :param ipaddr: optional IP address from what the action was made
-    :param sa: optional sqlalchemy session
 
     """
 
-    if not sa:
-        sa = meta.Session()
     # if we don't get explicit IP address try to get one from registered user
     # in tmpl context var
     if not ipaddr:
@@ -186,7 +137,7 @@
         repo_obj = Repository.get_by_repo_name(repo_name)
     else:
         repo_obj = None
-        repo_name = ''
+        repo_name = u''
 
     user_log = UserLog()
     user_log.user_id = user_obj.user_id
@@ -198,12 +149,12 @@
 
     user_log.action_date = datetime.datetime.now()
     user_log.user_ip = ipaddr
-    sa.add(user_log)
+    meta.Session().add(user_log)
 
     log.info('Logging action:%s on %s by user:%s ip:%s',
              action, safe_unicode(repo), user_obj, ipaddr)
     if commit:
-        sa.commit()
+        meta.Session().commit()
 
 
 def get_filesystem_repos(path):
@@ -228,7 +179,7 @@
             if REMOVED_REPO_PAT.match(subdir):
                 continue
 
-            #skip .<something> dirs TODO: rly? then we should prevent creating them ...
+            # skip .<something> dirs TODO: rly? then we should prevent creating them ...
             if subdir.startswith('.'):
                 continue
 
@@ -274,7 +225,11 @@
             # or does it pass basic auth
             MercurialRepository._check_url(url, ui)
         elif url.startswith('svn+http'):
-            from hgsubversion.svnrepo import svnremoterepo
+            try:
+                from hgsubversion.svnrepo import svnremoterepo
+            except ImportError:
+                raise HgsubversionImportError(_('Unable to activate hgsubversion support. '
+                                                'The "hgsubversion" library is missing'))
             svnremoterepo(ui, url).svn.uuid
         elif url.startswith('git+http'):
             raise NotImplementedError()
@@ -347,19 +302,7 @@
     return False
 
 
-def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
-    while True:
-        ok = raw_input(prompt)
-        if ok in ('y', 'ye', 'yes'):
-            return True
-        if ok in ('n', 'no', 'nop', 'nope'):
-            return False
-        retries = retries - 1
-        if retries < 0:
-            raise IOError
-        print complaint
-
-#propagated from mercurial documentation
+# propagated from mercurial documentation
 ui_sections = ['alias', 'auth',
                 'decode/encode', 'defaults',
                 'diff', 'email',
@@ -372,13 +315,12 @@
                 'ui', 'web', ]
 
 
-def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
+def make_ui(read_from='file', path=None, clear_session=True):
     """
     A function that will read python rc files or database
     and make an mercurial ui object from read options
 
     :param path: path to mercurial config file
-    :param checkpaths: check the path
     :param read_from: read from 'file' or 'db'
     """
 
@@ -392,7 +334,7 @@
     if read_from == 'file':
         if not os.path.isfile(path):
             log.debug('hgrc file is not present at %s, skipping...', path)
-            return False
+            return baseui
         log.debug('reading hgrc from %s', path)
         cfg = config.config()
         cfg.read(path)
@@ -408,21 +350,17 @@
         hg_ui = ret
         for ui_ in hg_ui:
             if ui_.ui_active:
-                ui_val = safe_str(ui_.ui_value)
-                if ui_.ui_section == 'hooks' and BRAND != 'kallithea' and ui_val.startswith('python:' + BRAND + '.lib.hooks.'):
-                    ui_val = ui_val.replace('python:' + BRAND + '.lib.hooks.', 'python:kallithea.lib.hooks.')
-                log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
+                ui_val = '' if ui_.ui_value is None else safe_str(ui_.ui_value)
+                log.debug('settings ui from db: [%s] %s=%r', ui_.ui_section,
                           ui_.ui_key, ui_val)
                 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
                                  ui_val)
-            if ui_.ui_key == 'push_ssl':
-                # force set push_ssl requirement to False, kallithea
-                # handles that
-                baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
-                                 False)
         if clear_session:
             meta.Session.remove()
 
+        # force set push_ssl requirement to False, Kallithea handles that
+        baseui.setconfig('web', 'push_ssl', False)
+        baseui.setconfig('web', 'allow_push', '*')
         # prevent interactive questions for ssh password / passphrase
         ssh = baseui.config('ui', 'ssh', default='ssh')
         baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
@@ -432,14 +370,16 @@
 
 def set_app_settings(config):
     """
-    Updates pylons config with new settings from database
+    Updates app config with new settings from database
 
     :param config:
     """
-    hgsettings = Setting.get_app_settings()
-
-    for k, v in hgsettings.items():
-        config[k] = v
+    try:
+        hgsettings = Setting.get_app_settings()
+        for k, v in hgsettings.items():
+            config[k] = v
+    finally:
+        meta.Session.remove()
 
 
 def set_vcs_config(config):
@@ -458,7 +398,22 @@
     conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
     conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
     conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
-                                                        'utf8'), sep=',')
+                                                        'utf-8'), sep=',')
+
+
+def set_indexer_config(config):
+    """
+    Update Whoosh index mapping
+
+    :param config: kallithea.CONFIG
+    """
+    from kallithea.config import conf
+
+    log.debug('adding extra into INDEX_EXTENSIONS')
+    conf.INDEX_EXTENSIONS.extend(re.split('\s+', config.get('index.extensions', '')))
+
+    log.debug('adding extra into INDEX_FILENAMES')
+    conf.INDEX_FILENAMES.extend(re.split('\s+', config.get('index.filenames', '')))
 
 
 def map_groups(path):
@@ -476,10 +431,10 @@
 
     # last element is repo in nested groups structure
     groups = groups[:-1]
-    rgm = RepoGroupModel(sa)
+    rgm = RepoGroupModel()
     owner = User.get_first_admin()
     for lvl, group_name in enumerate(groups):
-        group_name = '/'.join(groups[:lvl] + [group_name])
+        group_name = u'/'.join(groups[:lvl] + [group_name])
         group = RepoGroup.get_by_group_name(group_name)
         desc = '%s group' % group_name
 
@@ -492,10 +447,9 @@
                       lvl, group_name)
             group = RepoGroup(group_name, parent)
             group.group_description = desc
-            group.user = owner
+            group.owner = owner
             sa.add(group)
-            perm_obj = rgm._create_default_perms(group)
-            sa.add(perm_obj)
+            rgm._create_default_perms(group)
             sa.flush()
 
         parent = group
@@ -524,7 +478,7 @@
         user = User.get_first_admin()
     added = []
 
-    ##creation defaults
+    # creation defaults
     defs = Setting.get_default_repo_settings(strip_prefix=True)
     enable_statistics = defs.get('repo_enable_statistics')
     enable_locking = defs.get('repo_enable_locking')
@@ -571,13 +525,14 @@
 
     removed = []
     # remove from database those repositories that are not in the filesystem
+    unicode_initial_repo_list = set(safe_unicode(name) for name in initial_repo_list)
     for repo in sa.query(Repository).all():
-        if repo.repo_name not in initial_repo_list.keys():
+        if repo.repo_name not in unicode_initial_repo_list:
             if remove_obsolete:
                 log.debug("Removing non-existing repository found in db `%s`",
                           repo.repo_name)
                 try:
-                    RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
+                    RepoModel().delete(repo, forks='detach', fs_remove=False)
                     sa.commit()
                 except Exception:
                     #don't hold further removals on error
@@ -587,34 +542,6 @@
     return added, removed
 
 
-# set cache regions for beaker so celery can utilise it
-def add_cache(settings):
-    cache_settings = {'regions': None}
-    for key in settings.keys():
-        for prefix in ['beaker.cache.', 'cache.']:
-            if key.startswith(prefix):
-                name = key.split(prefix)[1].strip()
-                cache_settings[name] = settings[key].strip()
-    if cache_settings['regions']:
-        for region in cache_settings['regions'].split(','):
-            region = region.strip()
-            region_settings = {}
-            for key, value in cache_settings.items():
-                if key.startswith(region):
-                    region_settings[key.split('.')[1]] = value
-            region_settings['expire'] = int(region_settings.get('expire',
-                                                                60))
-            region_settings.setdefault('lock_dir',
-                                       cache_settings.get('lock_dir'))
-            region_settings.setdefault('data_dir',
-                                       cache_settings.get('data_dir'))
-
-            if 'type' not in region_settings:
-                region_settings['type'] = cache_settings.get('type',
-                                                             'memory')
-            beaker.cache.cache_regions[region] = region_settings
-
-
 def load_rcextensions(root_path):
     import kallithea
     from kallithea.config import conf
@@ -628,13 +555,13 @@
         # Additional mappings that are not present in the pygments lexers
         conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
 
-        #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
+        # OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
 
         if getattr(EXT, 'INDEX_EXTENSIONS', []):
             log.debug('settings custom INDEX_EXTENSIONS')
             conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
 
-        #ADDITIONAL MAPPINGS
+        # ADDITIONAL MAPPINGS
         log.debug('adding extra into INDEX_EXTENSIONS')
         conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
 
@@ -646,187 +573,9 @@
         #        setattr(EXT, k, getattr(rcextensions, k))
 
 
-def get_custom_lexer(extension):
-    """
-    returns a custom lexer if it's defined in rcextensions module, or None
-    if there's no custom lexer defined
-    """
-    import kallithea
-    from pygments import lexers
-    #check if we didn't define this extension as other lexer
-    if kallithea.EXTENSIONS and extension in kallithea.EXTENSIONS.EXTRA_LEXERS:
-        _lexer_name = kallithea.EXTENSIONS.EXTRA_LEXERS[extension]
-        return lexers.get_lexer_by_name(_lexer_name)
-
-
-#==============================================================================
-# TEST FUNCTIONS AND CREATORS
 #==============================================================================
-def create_test_index(repo_location, config, full_index):
-    """
-    Makes default test index
-
-    :param config: test config
-    :param full_index:
-    """
-
-    from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
-    from kallithea.lib.pidlock import DaemonLock, LockHeld
-
-    repo_location = repo_location
-
-    index_location = os.path.join(config['app_conf']['index_dir'])
-    if not os.path.exists(index_location):
-        os.makedirs(index_location)
-
-    try:
-        l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
-        WhooshIndexingDaemon(index_location=index_location,
-                             repo_location=repo_location)\
-            .run(full_index=full_index)
-        l.release()
-    except LockHeld:
-        pass
-
-
-def create_test_env(repos_test_path, config):
-    """
-    Makes a fresh database and
-    install test repository into tmp dir
-    """
-    from kallithea.lib.db_manage import DbManage
-    from kallithea.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
-
-    # PART ONE create db
-    dbconf = config['sqlalchemy.db1.url']
-    log.debug('making test db %s', dbconf)
-
-    # create test dir if it doesn't exist
-    if not os.path.isdir(repos_test_path):
-        log.debug('Creating testdir %s', repos_test_path)
-        os.makedirs(repos_test_path)
-
-    dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
-                        tests=True)
-    dbmanage.create_tables(override=True)
-    # for tests dynamically set new root paths based on generated content
-    dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
-    dbmanage.create_default_user()
-    dbmanage.admin_prompt()
-    dbmanage.create_permissions()
-    dbmanage.populate_default_permissions()
-    Session().commit()
-    # PART TWO make test repo
-    log.debug('making test vcs repositories')
-
-    idx_path = config['app_conf']['index_dir']
-    data_path = config['app_conf']['cache_dir']
-
-    #clean index and data
-    if idx_path and os.path.exists(idx_path):
-        log.debug('remove %s', idx_path)
-        shutil.rmtree(idx_path)
-
-    if data_path and os.path.exists(data_path):
-        log.debug('remove %s', data_path)
-        shutil.rmtree(data_path)
-
-    #CREATE DEFAULT TEST REPOS
-    cur_dir = dn(dn(abspath(__file__)))
-    tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz"))
-    tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
-    tar.close()
-
-    cur_dir = dn(dn(abspath(__file__)))
-    tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz"))
-    tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
-    tar.close()
-
-    #LOAD VCS test stuff
-    from kallithea.tests.vcs import setup_package
-    setup_package()
-
-
+# MISC
 #==============================================================================
-# PASTER COMMANDS
-#==============================================================================
-class BasePasterCommand(Command):
-    """
-    Abstract Base Class for paster commands.
-
-    The celery commands are somewhat aggressive about loading
-    celery.conf, and since our module sets the `CELERY_LOADER`
-    environment variable to our loader, we have to bootstrap a bit and
-    make sure we've had a chance to load the pylons config off of the
-    command line, otherwise everything fails.
-    """
-    min_args = 1
-    min_args_error = "Please provide a paster config file as an argument."
-    takes_config_file = 1
-    requires_config_file = True
-
-    def notify_msg(self, msg, log=False):
-        """Make a notification to user, additionally if logger is passed
-        it logs this action using given logger
-
-        :param msg: message that will be printed to user
-        :param log: logging instance, to use to additionally log this message
-
-        """
-        if log and isinstance(log, logging):
-            log(msg)
-
-    def run(self, args):
-        """
-        Overrides Command.run
-
-        Checks for a config file argument and loads it.
-        """
-        if len(args) < self.min_args:
-            raise BadCommand(
-                self.min_args_error % {'min_args': self.min_args,
-                                       'actual_args': len(args)})
-
-        # Decrement because we're going to lob off the first argument.
-        # @@ This is hacky
-        self.min_args -= 1
-        self.bootstrap_config(args[0])
-        self.update_parser()
-        return super(BasePasterCommand, self).run(args[1:])
-
-    def update_parser(self):
-        """
-        Abstract method.  Allows for the class's parser to be updated
-        before the superclass's `run` method is called.  Necessary to
-        allow options/arguments to be passed through to the underlying
-        celery command.
-        """
-        raise NotImplementedError("Abstract Method.")
-
-    def bootstrap_config(self, conf):
-        """
-        Loads the pylons configuration.
-        """
-        from pylons import config as pylonsconfig
-
-        self.path_to_ini_file = os.path.realpath(conf)
-        conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
-        pylonsconfig.init_app(conf.global_conf, conf.local_conf)
-
-    def _init_session(self):
-        """
-        Inits SqlAlchemy Session
-        """
-        logging.config.fileConfig(self.path_to_ini_file)
-        from pylons import config
-        from kallithea.model import init_model
-        from kallithea.lib.utils2 import engine_from_config
-
-        #get to remove repos !!
-        add_cache(config)
-        engine = engine_from_config(config, 'sqlalchemy.db1.')
-        init_model(engine)
-
 
 def check_git_version():
     """
@@ -863,28 +612,38 @@
     return ver
 
 
-@decorator.decorator
-def jsonify(func, *args, **kwargs):
-    """Action decorator that formats output for JSON
-
-    Given a function that will return content, this decorator will turn
-    the result into JSON, with a content-type of 'application/json' and
-    output it.
+#===============================================================================
+# CACHE RELATED METHODS
+#===============================================================================
 
-    """
-    from pylons.decorators.util import get_pylons
-    from kallithea.lib.compat import json
-    pylons = get_pylons(args)
-    pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
-    data = func(*args, **kwargs)
-    if isinstance(data, (list, tuple)):
-        msg = "JSON responses with Array envelopes are susceptible to " \
-              "cross-site data leak attacks, see " \
-              "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
-        warnings.warn(msg, Warning, 2)
-        log.warning(msg)
-    log.debug("Returning JSON wrapped action output")
-    return json.dumps(data, encoding='utf-8')
+# set cache regions for beaker so celery can utilise it
+def setup_cache_regions(settings):
+    # Create dict with just beaker cache configs with prefix stripped
+    cache_settings = {'regions': None}
+    prefix = 'beaker.cache.'
+    for key in settings:
+        if key.startswith(prefix):
+            name = key[len(prefix):]
+            cache_settings[name] = settings[key]
+    # Find all regions, apply defaults, and apply to beaker
+    if cache_settings['regions']:
+        for region in cache_settings['regions'].split(','):
+            region = region.strip()
+            prefix = region + '.'
+            region_settings = {}
+            for key in cache_settings:
+                if key.startswith(prefix):
+                    name = key[len(prefix):]
+                    region_settings[name] = cache_settings[key]
+            region_settings.setdefault('expire',
+                                       cache_settings.get('expire', '60'))
+            region_settings.setdefault('lock_dir',
+                                       cache_settings.get('lock_dir'))
+            region_settings.setdefault('data_dir',
+                                       cache_settings.get('data_dir'))
+            region_settings.setdefault('type',
+                                       cache_settings.get('type', 'memory'))
+            beaker.cache.cache_regions[region] = region_settings
 
 
 def conditional_cache(region, prefix, condition, func):
@@ -895,8 +654,8 @@
             #heavy computation function
             return data
 
-        # denpending from condition the compute is wrapped in cache or not
-        compute = conditional_cache('short_term', 'cache_desc', codnition=True, func=func)
+        # depending from condition the compute is wrapped in cache or not
+        compute = conditional_cache('short_term', 'cache_desc', condition=True, func=func)
         return compute(arg)
 
     :param region: name of cache region
--- a/kallithea/lib/utils2.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/utils2.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,7 +15,9 @@
 kallithea.lib.utils2
 ~~~~~~~~~~~~~~~~~~~~
 
-Some simple helper functions
+Some simple helper functions.
+Note: all these functions should be independent of Kallithea classes, i.e.
+models, controllers, etc.  to prevent import cycles.
 
 This file was forked by the Kallithea project in July 2014.
 Original author and date, and relevant copyright and licensing information is below:
@@ -37,50 +39,16 @@
 
 import webob
 import urlobject
+from webhelpers.text import collapse, remove_formatting, strip_tags
 
-from pylons.i18n.translation import _, ungettext
+from tg.i18n import ugettext as _, ungettext
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.compat import json
 
 
-def __get_lem():
-    """
-    Get language extension map based on what's inside pygments lexers
-    """
-    from pygments import lexers
-    from string import lower
-    from collections import defaultdict
-
-    d = defaultdict(lambda: [])
-
-    def __clean(s):
-        s = s.lstrip('*')
-        s = s.lstrip('.')
-
-        if s.find('[') != -1:
-            exts = []
-            start, stop = s.find('['), s.find(']')
-
-            for suffix in s[start + 1:stop]:
-                exts.append(s[:s.find('[')] + suffix)
-            return map(lower, exts)
-        else:
-            return map(lower, [s])
-
-    for lx, t in sorted(lexers.LEXERS.items()):
-        m = map(__clean, t[-2])
-        if m:
-            m = reduce(lambda x, y: x + y, m)
-            for ext in m:
-                desc = lx.replace('Lexer', '')
-                d[ext].append(desc)
-
-    return dict(d)
-
-
 def str2bool(_str):
     """
-    returs True/False value from given string, it tries to translate the
+    returns True/False value from given string, it tries to translate the
     string into boolean
 
     :param _str: string value to translate into boolean
@@ -166,7 +134,14 @@
 def generate_api_key():
     """
     Generates a random (presumably unique) API key.
+
+    This value is used in URLs and "Bearer" HTTP Authorization headers,
+    which in practice means it should only contain URL-safe characters
+    (RFC 3986):
+
+        unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
     """
+    # Hexadecimal certainly qualifies as URL-safe.
     return binascii.hexlify(os.urandom(20))
 
 
@@ -204,7 +179,7 @@
     if not from_encoding:
         import kallithea
         DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
-                                                        'utf8'), sep=',')
+                                                        'utf-8'), sep=',')
         from_encoding = DEFAULT_ENCODINGS
 
     if not isinstance(from_encoding, (list, tuple)):
@@ -253,7 +228,7 @@
     if not to_encoding:
         import kallithea
         DEFAULT_ENCODINGS = aslist(kallithea.CONFIG.get('default_encoding',
-                                                        'utf8'), sep=',')
+                                                        'utf-8'), sep=',')
         to_encoding = DEFAULT_ENCODINGS
 
     if not isinstance(to_encoding, (list, tuple)):
@@ -288,63 +263,6 @@
     return s
 
 
-def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
-    """
-    Custom engine_from_config functions that makes sure we use NullPool for
-    file based sqlite databases. This prevents errors on sqlite. This only
-    applies to sqlalchemy versions < 0.7.0
-
-    """
-    import sqlalchemy
-    from sqlalchemy import engine_from_config as efc
-    import logging
-
-    if int(sqlalchemy.__version__.split('.')[1]) < 7:
-
-        # This solution should work for sqlalchemy < 0.7.0, and should use
-        # proxy=TimerProxy() for execution time profiling
-
-        from sqlalchemy.pool import NullPool
-        url = configuration[prefix + 'url']
-
-        if url.startswith('sqlite'):
-            kwargs.update({'poolclass': NullPool})
-        return efc(configuration, prefix, **kwargs)
-    else:
-        import time
-        from sqlalchemy import event
-
-        log = logging.getLogger('sqlalchemy.engine')
-        BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
-        engine = efc(configuration, prefix, **kwargs)
-
-        def color_sql(sql):
-            COLOR_SEQ = "\033[1;%dm"
-            COLOR_SQL = YELLOW
-            normal = '\x1b[0m'
-            return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
-
-        if configuration['debug']:
-            #attach events only for debug configuration
-
-            def before_cursor_execute(conn, cursor, statement,
-                                    parameters, context, executemany):
-                context._query_start_time = time.time()
-                log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
-
-            def after_cursor_execute(conn, cursor, statement,
-                                    parameters, context, executemany):
-                total = time.time() - context._query_start_time
-                log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
-
-            event.listen(engine, "before_cursor_execute",
-                         before_cursor_execute)
-            event.listen(engine, "after_cursor_execute",
-                         after_cursor_execute)
-
-    return engine
-
-
 def age(prevdate, show_short_version=False, now=None):
     """
     turns a datetime into an age string.
@@ -352,7 +270,7 @@
     example: 2days ago, instead of 2 days and 23 hours ago.
 
     :param prevdate: datetime object
-    :param show_short_version: if it should aproximate the date and return a shorter string
+    :param show_short_version: if it should approximate the date and return a shorter string
     :rtype: unicode
     :returns: unicode words describing age
     """
@@ -486,7 +404,7 @@
     """
 
     uri = uri_filter(uri)
-    #check if we have port
+    # check if we have port
     if len(uri) > 2 and uri[2]:
         uri[2] = ':' + uri[2]
 
@@ -553,25 +471,32 @@
                 return
         return datetime.datetime.fromtimestamp(tm)
 
+
 # Must match regexp in kallithea/public/js/base.js MentionsAutoComplete()
 # Check char before @ - it must not look like we are in an email addresses.
-# Matching is gready so we don't have to look beyond the end.
+# Matching is greedy so we don't have to look beyond the end.
 MENTIONS_REGEX = re.compile(r'(?:^|(?<=[^a-zA-Z0-9]))@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])')
 
-def extract_mentioned_users(s):
-    r"""
-    Returns unique usernames from given string s that have @mention
 
-    :param s: string to get mentions
+def extract_mentioned_usernames(text):
+    r"""
+    Returns list of (possible) usernames @mentioned in given text.
 
-    >>> extract_mentioned_users('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
+    >>> extract_mentioned_usernames('@1-2.a_X,@1234 not@not @ddd@not @n @ee @ff @gg, @gg;@hh @n\n@zz,')
     ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'hh', 'zz']
     """
-    usrs = set()
-    for username in MENTIONS_REGEX.findall(s):
-        usrs.add(username)
+    return MENTIONS_REGEX.findall(text)
+
 
-    return sorted(list(usrs), key=lambda k: k.lower())
+def extract_mentioned_users(text):
+    """ Returns set of actual database Users @mentioned in given text. """
+    from kallithea.model.db import User
+    result = set()
+    for name in extract_mentioned_usernames(text):
+        user = User.get_by_username(name, case_insensitive=True)
+        if user is not None and not user.is_default_user:
+            result.add(user)
+    return result
 
 
 class AttributeDict(dict):
@@ -613,68 +538,28 @@
     return req.host_url + req.script_name
 
 
-def _extract_extras(env=None):
+def _extract_extras():
     """
     Extracts the Kallithea extras data from os.environ, and wraps it into named
     AttributeDict object
     """
-    if not env:
-        env = os.environ
-
     try:
-        extras = json.loads(env['KALLITHEA_EXTRAS'])
+        extras = json.loads(os.environ['KALLITHEA_EXTRAS'])
     except KeyError:
-        extras = {}
+        raise Exception("Environment variable KALLITHEA_EXTRAS not found")
 
     try:
         for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
                   'action', 'ip']:
             extras[k]
-    except KeyError as e:
-        raise Exception('Missing key %s in os.environ %s' % (e, extras))
+    except KeyError:
+        raise Exception('Missing key %s in KALLITHEA_EXTRAS %s' % (k, extras))
 
     return AttributeDict(extras)
 
 
 def _set_extras(extras):
-    # RC_SCM_DATA can probably be removed in the future, but for compatibilty now...
-    os.environ['KALLITHEA_EXTRAS'] = os.environ['RC_SCM_DATA'] = json.dumps(extras)
-
-
-def unique_id(hexlen=32):
-    alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
-    return suuid(truncate_to=hexlen, alphabet=alphabet)
-
-
-def suuid(url=None, truncate_to=22, alphabet=None):
-    """
-    Generate and return a short URL safe UUID.
-
-    If the url parameter is provided, set the namespace to the provided
-    URL and generate a UUID.
-
-    :param url to get the uuid for
-    :truncate_to: truncate the basic 22 UUID to shorter version
-
-    The IDs won't be universally unique any longer, but the probability of
-    a collision will still be very low.
-    """
-    # Define our alphabet.
-    _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
-
-    # If no URL is given, generate a random UUID.
-    if url is None:
-        unique_id = uuid.uuid4().int
-    else:
-        unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
-
-    alphabet_length = len(_ALPHABET)
-    output = []
-    while unique_id > 0:
-        digit = unique_id % alphabet_length
-        output.append(_ALPHABET[digit])
-        unique_id = int(unique_id / alphabet_length)
-    return "".join(output)[:truncate_to]
+    os.environ['KALLITHEA_EXTRAS'] = json.dumps(extras)
 
 
 def get_current_authuser():
@@ -682,7 +567,7 @@
     Gets kallithea user from threadlocal tmpl_context variable if it's
     defined, else returns None.
     """
-    from pylons import tmpl_context
+    from tg import tmpl_context
     if hasattr(tmpl_context, 'authuser'):
         return tmpl_context.authuser
 
@@ -708,7 +593,8 @@
     def __call__(self):
         return self
 
-#alias
+
+# alias
 OAttr = OptionalAttr
 
 
@@ -756,5 +642,55 @@
             return val.getval()
         return val
 
+
 def urlreadable(s, _cleanstringsub=re.compile('[^-a-zA-Z0-9./]+').sub):
     return _cleanstringsub('_', safe_str(s)).rstrip('_')
+
+
+def recursive_replace(str_, replace=' '):
+    """
+    Recursive replace of given sign to just one instance
+
+    :param str_: given string
+    :param replace: char to find and replace multiple instances
+
+    Examples::
+    >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
+    'Mighty-Mighty-Bo-sstones'
+    """
+
+    if str_.find(replace * 2) == -1:
+        return str_
+    else:
+        str_ = str_.replace(replace * 2, replace)
+        return recursive_replace(str_, replace)
+
+
+def repo_name_slug(value):
+    """
+    Return slug of name of repository
+    This function is called on each creation/modification
+    of repository to prevent bad names in repo
+    """
+
+    slug = remove_formatting(value)
+    slug = strip_tags(slug)
+
+    for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
+        slug = slug.replace(c, '-')
+    slug = recursive_replace(slug, '-')
+    slug = collapse(slug, '-')
+    return slug
+
+
+def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
+    while True:
+        ok = raw_input(prompt)
+        if ok in ('y', 'ye', 'yes'):
+            return True
+        if ok in ('n', 'no', 'nop', 'nope'):
+            return False
+        retries = retries - 1
+        if retries < 0:
+            raise IOError
+        print complaint
--- a/kallithea/lib/vcs/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -29,15 +29,3 @@
     Returns shorter version (digit parts only) as string.
     """
     return '.'.join((str(each) for each in VERSION[:3]))
-
-
-def main(argv=None):
-    if argv is None:
-        argv = sys.argv
-    from kallithea.lib.vcs.cli import ExecutionManager
-    manager = ExecutionManager(argv)
-    manager.execute()
-    return 0
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
--- a/kallithea/lib/vcs/backends/base.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -97,6 +97,9 @@
 
     @LazyProperty
     def name(self):
+        """
+        Return repository name (without group name)
+        """
         raise NotImplementedError
 
     @property
@@ -137,9 +140,6 @@
     def is_empty(self):
         return self._empty
 
-    def get_last_change(self):
-        self.get_changesets()
-
     #==========================================================================
     # CHANGESETS
     #==========================================================================
@@ -163,11 +163,10 @@
             yield self.get_changeset(revision)
 
     def get_changesets(self, start=None, end=None, start_date=None,
-                       end_date=None, branch_name=None, reverse=False):
+                       end_date=None, branch_name=None, reverse=False, max_revisions=None):
         """
-        Returns iterator of ``MercurialChangeset`` objects from start to end
-        not inclusive This should behave just like a list, ie. end is not
-        inclusive
+        Returns iterator of ``BaseChangeset`` objects from start to end,
+        both inclusive.
 
         :param start: None or str
         :param end: None or str
@@ -385,15 +384,28 @@
     def __eq__(self, other):
         return self.raw_id == other.raw_id
 
-    def __json__(self):
-        return dict(
-            short_id=self.short_id,
-            raw_id=self.raw_id,
-            revision=self.revision,
-            message=self.message,
-            date=self.date,
-            author=self.author,
-        )
+    def __json__(self, with_file_list=False):
+        if with_file_list:
+            return dict(
+                short_id=self.short_id,
+                raw_id=self.raw_id,
+                revision=self.revision,
+                message=self.message,
+                date=self.date,
+                author=self.author,
+                added=[safe_unicode(el.path) for el in self.added],
+                changed=[safe_unicode(el.path) for el in self.changed],
+                removed=[safe_unicode(el.path) for el in self.removed],
+            )
+        else:
+            return dict(
+                short_id=self.short_id,
+                raw_id=self.raw_id,
+                revision=self.revision,
+                message=self.message,
+                date=self.date,
+                author=self.author,
+            )
 
     @LazyProperty
     def last(self):
@@ -655,9 +667,9 @@
         data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
             'revision', 'date', 'message'])
         data['author'] = {'name': self.author_name, 'email': self.author_email}
-        data['added'] = [node.path for node in self.added]
-        data['changed'] = [node.path for node in self.changed]
-        data['removed'] = [node.path for node in self.removed]
+        data['added'] = [safe_unicode(node.path) for node in self.added]
+        data['changed'] = [safe_unicode(node.path) for node in self.changed]
+        data['removed'] = [safe_unicode(node.path) for node in self.removed]
         return data
 
     @LazyProperty
@@ -668,6 +680,27 @@
     def obsolete(self):
         return False
 
+    @LazyProperty
+    def bumped(self):
+        return False
+
+    @LazyProperty
+    def divergent(self):
+        return False
+
+    @LazyProperty
+    def extinct(self):
+        return False
+
+    @LazyProperty
+    def unstable(self):
+        return False
+
+    @LazyProperty
+    def phase(self):
+        return ''
+
+
 class BaseWorkdir(object):
     """
     Working directory representation of single repository.
@@ -1005,6 +1038,11 @@
         return get_backend(self.alias).DEFAULT_BRANCH_NAME
 
     @LazyProperty
+    def branches(self):
+        from kallithea.lib.vcs.backends import get_backend
+        return [get_backend(self.alias).DEFAULT_BRANCH_NAME]
+
+    @LazyProperty
     def short_id(self):
         return self.raw_id[:12]
 
@@ -1031,12 +1069,13 @@
         for rev in self.revs:
             yield self.repo.get_changeset(rev)
 
-    def __getslice__(self, i, j):
-        """
-        Returns a iterator of sliced repository
-        """
-        sliced_revs = self.revs[i:j]
-        return CollectionGenerator(self.repo, sliced_revs)
+    def __getitem__(self, what):
+        """Return either a single element by index, or a sliced collection."""
+        if isinstance(what, slice):
+            return CollectionGenerator(self.repo, self.revs[what])
+        else:
+            # single item
+            return self.repo.get_changeset(self.revs[what])
 
     def __repr__(self):
         return '<CollectionGenerator[len:%s]>' % (len(self))
--- a/kallithea/lib/vcs/backends/git/changeset.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/git/changeset.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,9 @@
 import re
 from itertools import chain
 from dulwich import objects
+from dulwich.config import ConfigFile
 from subprocess import Popen, PIPE
+from io import BytesIO
 
 from kallithea.lib.vcs.conf import settings
 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
@@ -50,6 +52,10 @@
         self._paths = {}
 
     @LazyProperty
+    def bookmarks(self):
+        return ()
+
+    @LazyProperty
     def message(self):
         return safe_unicode(self._commit.message)
 
@@ -87,13 +93,19 @@
 
     @LazyProperty
     def branch(self):
-
+        # Note: This function will return one branch name for the changeset -
+        # that might not make sense in Git where branches() is a better match
+        # for the basic model
         heads = self.repository._heads(reverse=False)
-
         ref = heads.get(self.raw_id)
         if ref:
             return safe_unicode(ref)
 
+    @LazyProperty
+    def branches(self):
+        heads = self.repository._heads(reverse=True)
+        return [b for b in heads if heads[b] == self.raw_id] # FIXME: Inefficient ... and returning None!
+
     def _fix_path(self, path):
         """
         Paths are stored without trailing slash so we need to get rid off it if
@@ -106,7 +118,7 @@
     def _get_id_for_path(self, path):
         path = safe_str(path)
         # FIXME: Please, spare a couple of minutes and make those codes cleaner;
-        if not path in self._paths:
+        if path not in self._paths:
             path = path.strip('/')
             # set root tree
             tree = self.repository._repo[self._tree_id]
@@ -151,7 +163,7 @@
                         name = item
                     self._paths[name] = id
                     self._stat_modes[name] = stat
-            if not path in self._paths:
+            if path not in self._paths:
                 raise NodeDoesNotExistError("There is no file nor directory "
                     "at the given path '%s' at revision %s"
                     % (path, safe_str(self.short_id)))
@@ -398,16 +410,19 @@
         filenodes = []
         als = self.repository.alias
         for name, stat, id in tree.iteritems():
+            if path != '':
+                obj_path = '/'.join((path, name))
+            else:
+                obj_path = name
             if objects.S_ISGITLINK(stat):
-                dirnodes.append(SubModuleNode(name, url=None, changeset=id,
+                root_tree = self.repository._repo[self._tree_id]
+                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(root_tree['.gitmodules'][1]).data))
+                url = cf.get(('submodule', obj_path), 'url')
+                dirnodes.append(SubModuleNode(obj_path, url=url, changeset=id,
                                               alias=als))
                 continue
 
             obj = self.repository._repo.get_object(id)
-            if path != '':
-                obj_path = '/'.join((path, name))
-            else:
-                obj_path = name
             if obj_path not in self._stat_modes:
                 self._stat_modes[obj_path] = stat
             if isinstance(obj, objects.Tree):
@@ -419,7 +434,7 @@
                                      "or Blob, is %r" % type(obj))
         nodes = dirnodes + filenodes
         for node in nodes:
-            if not node.path in self.nodes:
+            if node.path not in self.nodes:
                 self.nodes[node.path] = node
         nodes.sort()
         return nodes
@@ -428,7 +443,7 @@
         if isinstance(path, unicode):
             path = path.encode('utf-8')
         path = self._fix_path(path)
-        if not path in self.nodes:
+        if path not in self.nodes:
             try:
                 id_ = self._get_id_for_path(path)
             except ChangesetError:
@@ -437,7 +452,10 @@
 
             _GL = lambda m: m and objects.S_ISGITLINK(m)
             if _GL(self._stat_modes.get(path)):
-                node = SubModuleNode(path, url=None, changeset=id_,
+                tree = self.repository._repo[self._tree_id]
+                cf = ConfigFile.from_file(BytesIO(self.repository._repo.get_object(tree['.gitmodules'][1]).data))
+                url = cf.get(('submodule', path), 'url')
+                node = SubModuleNode(path, url=url, changeset=id_,
                                      alias=self.repository.alias)
             else:
                 obj = self.repository._repo.get_object(id_)
--- a/kallithea/lib/vcs/backends/git/inmemory.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/git/inmemory.py	Sun Mar 31 21:28:56 2019 +0200
@@ -40,7 +40,7 @@
         ENCODING = "UTF-8"
 
         # Create tree and populates it with blobs
-        commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
+        commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or \
             objects.Tree()
         for node in self.added + self.changed:
             # Compute subdirs if needed
--- a/kallithea/lib/vcs/backends/git/repository.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/git/repository.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,7 +30,7 @@
     BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
     RepositoryError, TagAlreadyExistError, TagDoesNotExistError
 )
-from kallithea.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
+from kallithea.lib.vcs.utils import safe_str, safe_unicode, makedate, date_fromtimestamp
 from kallithea.lib.vcs.utils.lazy import LazyProperty
 from kallithea.lib.vcs.utils.ordered_dict import OrderedDict
 from kallithea.lib.vcs.utils.paths import abspath, get_user_home
@@ -58,9 +58,9 @@
     def __init__(self, repo_path, create=False, src_url=None,
                  update_after_clone=False, bare=False):
 
-        self.path = abspath(repo_path)
-        repo = self._get_repo(create, src_url, update_after_clone, bare)
-        self.bare = repo.bare
+        self.path = safe_unicode(abspath(repo_path))
+        self.repo = self._get_repo(create, src_url, update_after_clone, bare)
+        self.bare = self.repo.bare
 
     @property
     def _config_files(self):
@@ -72,7 +72,7 @@
 
     @property
     def _repo(self):
-        return Repo(self.path)
+        return self.repo
 
     @property
     def head(self):
@@ -118,7 +118,7 @@
             _copts = ['-c', 'core.quotepath=false', ]
         safe_call = False
         if '_safe' in opts:
-            #no exc on failure
+            # no exc on failure
             del opts['_safe']
             safe_call = True
 
@@ -189,7 +189,7 @@
             test_uri = test_uri.rstrip('/') + '/info/refs'
 
         if authinfo:
-            #create a password manager
+            # create a password manager
             passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
             passmgr.add_password(*authinfo)
 
@@ -214,7 +214,7 @@
 
         # now detect if it's proper git repo
         gitdata = resp.read()
-        if not 'service=git-upload-pack' in gitdata:
+        if 'service=git-upload-pack' not in gitdata:
             raise urllib2.URLError(
                 "url [%s] does not look like an git" % (cleaned_uri))
 
@@ -239,7 +239,7 @@
                 else:
                     return Repo.init(self.path)
             else:
-                return self._repo
+                return Repo(self.path)
         except (NotGitRepository, OSError) as err:
             raise RepositoryError(err)
 
@@ -262,7 +262,7 @@
         return so.splitlines()
 
     def _get_all_revisions2(self):
-        #alternate implementation using dulwich
+        # alternate implementation using dulwich
         includes = [x[1][0] for x in self._parsed_refs.iteritems()
                     if x[1][1] != 'T']
         return [c.commit.id for c in self._repo.get_walker(include=includes)]
@@ -328,8 +328,8 @@
         Returns normalized url. If schema is not given, would fall to
         filesystem (``file:///``) schema.
         """
-        url = str(url)
-        if url != 'default' and not '://' in url:
+        url = safe_str(url)
+        if url != 'default' and '://' not in url:
             url = ':///'.join(('file', url))
         return url
 
@@ -504,7 +504,7 @@
         return changeset
 
     def get_changesets(self, start=None, end=None, start_date=None,
-           end_date=None, branch_name=None, reverse=False):
+           end_date=None, branch_name=None, reverse=False, max_revisions=None):
         """
         Returns iterator of ``GitChangeset`` objects from start to end (both
         are inclusive), in ascending date order (unless ``reverse`` is set).
@@ -527,7 +527,7 @@
 
         """
         if branch_name and branch_name not in self.branches:
-            raise BranchDoesNotExistError("Branch '%s' not found" \
+            raise BranchDoesNotExistError("Branch '%s' not found"
                                           % branch_name)
         # actually we should check now if it's not an empty repo to not spaw
         # subprocess commands
@@ -537,6 +537,8 @@
         # %H at format means (full) commit hash, initial hashes are retrieved
         # in ascending date order
         cmd = ['log', '--date-order', '--reverse', '--pretty=format:%H']
+        if max_revisions:
+            cmd += ['--max-count=%s' % max_revisions]
         if start_date:
             cmd += ['--since', start_date.strftime('%m/%d/%y %H:%M:%S')]
         if end_date:
@@ -604,7 +606,7 @@
         overflowed_long_int = 2**31
 
         if context >= overflowed_long_int:
-            context = overflowed_long_int-1
+            context = overflowed_long_int - 1
 
         # Negative context values make no sense, and will result in
         # errors. Ensure this does not happen.
--- a/kallithea/lib/vcs/backends/hg/changeset.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/hg/changeset.py	Sun Mar 31 21:28:56 2019 +0200
@@ -18,6 +18,7 @@
 
 from mercurial import obsolete
 
+
 class MercurialChangeset(BaseChangeset):
     """
     Represents state of the repository at the single revision.
@@ -37,19 +38,63 @@
 
     @LazyProperty
     def branch(self):
-        return  safe_unicode(self._ctx.branch())
+        return safe_unicode(self._ctx.branch())
+
+    @LazyProperty
+    def branches(self):
+        return [safe_unicode(self._ctx.branch())]
 
     @LazyProperty
     def closesbranch(self):
-        return  self._ctx.closesbranch()
+        return self._ctx.closesbranch()
 
     @LazyProperty
     def obsolete(self):
-        return  self._ctx.obsolete()
+        return self._ctx.obsolete()
+
+    @LazyProperty
+    def bumped(self):
+        try:
+            return self._ctx.phasedivergent()
+        except AttributeError: # renamed in Mercurial 4.6 (9fa874fb34e1)
+            return self._ctx.bumped()
+
+    @LazyProperty
+    def divergent(self):
+        try:
+            return self._ctx.contentdivergent()
+        except AttributeError: # renamed in Mercurial 4.6 (8b2d7684407b)
+            return self._ctx.divergent()
+
+    @LazyProperty
+    def extinct(self):
+        return self._ctx.extinct()
+
+    @LazyProperty
+    def unstable(self):
+        try:
+            return self._ctx.orphan()
+        except AttributeError: # renamed in Mercurial 4.6 (03039ff3082b)
+            return self._ctx.unstable()
+
+    @LazyProperty
+    def phase(self):
+        if(self._ctx.phase() == 1):
+            return 'Draft'
+        elif(self._ctx.phase() == 2):
+            return 'Secret'
+        else:
+            return ''
 
     @LazyProperty
     def successors(self):
-        successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
+        try:
+            # This works starting from Mercurial 4.3: the function `successorssets` was moved to the mercurial.obsutil module and gained the `closest` parameter.
+            from mercurial import obsutil
+            successors = obsutil.successorssets(self._ctx._repo, self._ctx.node(), closest=True)
+        except ImportError:
+            # fallback for older versions
+            successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
         if successors:
             # flatten the list here handles both divergent (len > 1)
             # and the usual case (len = 1)
@@ -58,14 +103,20 @@
         return successors
 
     @LazyProperty
-    def precursors(self):
-        precursors = set()
-        nm = self._ctx._repo.changelog.nodemap
-        for p in self._ctx._repo.obsstore.precursors.get(self._ctx.node(), ()):
-            pr = nm.get(p[0])
-            if pr is not None:
-                precursors.add(hex(p[0])[:12])
-        return precursors
+    def predecessors(self):
+        try:
+            # This works starting from Mercurial 4.3: the function `closestpredecessors` was added.
+            from mercurial import obsutil
+            return [hex(n)[:12] for n in obsutil.closestpredecessors(self._ctx._repo, self._ctx.node())]
+        except ImportError:
+            # fallback for older versions
+            predecessors = set()
+            nm = self._ctx._repo.changelog.nodemap
+            for p in self._ctx._repo.obsstore.precursors.get(self._ctx.node(), ()):
+                pr = nm.get(p[0])
+                if pr is not None:
+                    predecessors.add(hex(p[0])[:12])
+            return predecessors
 
     @LazyProperty
     def bookmarks(self):
@@ -147,7 +198,7 @@
         cs = self
         while True:
             try:
-                next_ = cs.revision + 1
+                next_ = cs.repository.revisions.index(cs.raw_id) + 1
                 next_rev = cs.repository.revisions[next_]
             except IndexError:
                 raise ChangesetDoesNotExistError
@@ -164,7 +215,7 @@
         cs = self
         while True:
             try:
-                prev_ = cs.revision - 1
+                prev_ = cs.repository.revisions.index(cs.raw_id) - 1
                 if prev_ < 0:
                     raise IndexError
                 prev_rev = cs.repository.revisions[prev_]
@@ -175,10 +226,9 @@
             if not branch or branch == cs.branch:
                 return cs
 
-    def diff(self, ignore_whitespace=True, context=3):
-        return ''.join(self._ctx.diff(git=True,
-                                      ignore_whitespace=ignore_whitespace,
-                                      context=context))
+    def diff(self):
+        # Only used for feed diffstat
+        return ''.join(self._ctx.diff())
 
     def _fix_path(self, path):
         """
@@ -266,12 +316,17 @@
         Returns a generator of four element tuples with
             lineno, sha, changeset lazy loader and line
         """
-
-        fctx = self._get_filectx(path)
-        for i, annotate_data in enumerate(fctx.annotate(linenumber=False)):
-            ln_no = i + 1
-            sha = hex(annotate_data[0][0].node())
-            yield (ln_no, sha, lambda: self.repository.get_changeset(sha), annotate_data[1],)
+        annotations = self._get_filectx(path).annotate()
+        try:
+            annotation_lines = [(annotateline.fctx, annotateline.text) for annotateline in annotations]
+        except AttributeError: # annotateline was introduced in Mercurial 4.6 (b33b91ca2ec2)
+            try:
+                annotation_lines = [(aline.fctx, l) for aline, l in annotations]
+            except AttributeError: # aline.fctx was introduced in Mercurial 4.4
+                annotation_lines = [(aline[0], l) for aline, l in annotations]
+        for i, (fctx, l) in enumerate(annotation_lines):
+            sha = fctx.hex()
+            yield (i + 1, sha, lambda sha=sha, l=l: self.repository.get_changeset(sha), l)
 
     def fill_archive(self, stream=None, kind='tgz', prefix=None,
                      subrepos=False):
@@ -351,7 +406,7 @@
 
         path = self._fix_path(path)
 
-        if not path in self.nodes:
+        if path not in self.nodes:
             if path in self._file_paths:
                 node = FileNode(path, changeset=self)
             elif path in self._dir_paths or path in self._dir_paths:
@@ -386,7 +441,7 @@
         """
         Returns list of modified ``FileNode`` objects.
         """
-        return ChangedFileNodesGenerator([n for n in  self.status[0]], self)
+        return ChangedFileNodesGenerator([n for n in self.status[0]], self)
 
     @property
     def removed(self):
--- a/kallithea/lib/vcs/backends/hg/inmemory.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/hg/inmemory.py	Sun Mar 31 21:28:56 2019 +0200
@@ -47,18 +47,13 @@
 
             # check if this path is removed
             if path in (node.path for node in self.removed):
-                if getattr(memctx, '_returnnoneformissingfiles', False):
-                    return None
-                else:
-                    # (hg < 3.2) Raising exception is the way to mark node for
-                    # removal
-                    raise IOError(errno.ENOENT, '%s is deleted' % path)
+                return None
 
             # check if this path is added
             for node in self.added:
                 if node.path == path:
-                    return memfilectx(_repo, path=node.path,
-                        data=(node.content.encode('utf8')
+                    return memfilectx(_repo, memctx, path=node.path,
+                        data=(node.content.encode('utf-8')
                               if not node.is_binary else node.content),
                         islink=False,
                         isexec=node.is_executable,
@@ -67,8 +62,8 @@
             # or changed
             for node in self.changed:
                 if node.path == path:
-                    return memfilectx(_repo, path=node.path,
-                        data=(node.content.encode('utf8')
+                    return memfilectx(_repo, memctx, path=node.path,
+                        data=(node.content.encode('utf-8')
                               if not node.is_binary else node.content),
                         islink=False,
                         isexec=node.is_executable,
@@ -83,7 +78,7 @@
                 parents[i] = parent._ctx.node()
 
         if date and isinstance(date, datetime.datetime):
-            date = date.ctime()
+            date = date.strftime('%a, %d %b %Y %H:%M:%S')
 
         commit_ctx = memctx(repo=self.repository._repo,
             parents=parents,
--- a/kallithea/lib/vcs/backends/hg/repository.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/hg/repository.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,8 +30,8 @@
 from kallithea.lib.vcs.utils.paths import abspath
 from kallithea.lib.vcs.utils.hgcompat import (
     ui, nullid, match, patch, diffopts, clone, get_contact,
-    localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
-    httpbasicauthhandler, httpdigestauthhandler, peer, httppeer, sshpeer
+    localrepo, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
+    httpbasicauthhandler, httpdigestauthhandler, peer, httppeer, sshpeer, tag
 )
 
 from .changeset import MercurialChangeset
@@ -172,11 +172,10 @@
                 changeset.short_id)
 
         if date is None:
-            date = datetime.datetime.now().ctime()
+            date = datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')
 
         try:
-            self._repo.tag(name, changeset._ctx.node(), message, local, user,
-                date)
+            tag(self._repo, name, changeset._ctx.node(), message, local, user, date)
         except Abort as e:
             raise RepositoryError(e.message)
 
@@ -202,11 +201,11 @@
         if message is None:
             message = "Removed tag %s" % name
         if date is None:
-            date = datetime.datetime.now().ctime()
+            date = datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S')
         local = False
 
         try:
-            self._repo.tag(name, nullid, message, local, user, date)
+            tag(self._repo, name, nullid, message, local, user, date)
             self.tags = self._get_tags()
         except Abort as e:
             raise RepositoryError(e.message)
@@ -293,7 +292,7 @@
         if url.startswith('ssh:'):
             # in case of invalid uri or authentication issues, sshpeer will
             # throw an exception.
-            sshpeer(repoui or ui.ui(), url).lookup('tip')
+            sshpeer.instance(repoui or ui.ui(), url, False).lookup('tip')
             return True
 
         url_prefix = None
@@ -307,7 +306,7 @@
         cleaned_uri = str(url_obj)
 
         if authinfo:
-            #create a password manager
+            # create a password manager
             passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
             passmgr.add_password(*authinfo)
 
@@ -335,7 +334,7 @@
         if not url_prefix: # skip svn+http://... (and git+... too)
             # now check if it's a proper hg repo
             try:
-                httppeer(repoui or ui.ui(), url).lookup('tip')
+                httppeer.instance(repoui or ui.ui(), url, False).lookup('tip')
             except Exception as e:
                 raise urllib2.URLError(
                     "url [%s] does not look like an hg repo org_exc: %s"
@@ -356,7 +355,7 @@
 
         try:
             if src_url:
-                url = str(self._get_url(src_url))
+                url = safe_str(self._get_url(src_url))
                 opts = {}
                 if not update_after_clone:
                     opts.update({'noupdate': True})
@@ -365,13 +364,13 @@
 
                 # Don't try to create if we've already cloned repo
                 create = False
-            return localrepository(self.baseui, self.path, create=create)
+            return localrepo.instance(self.baseui, self.path, create=create)
         except (Abort, RepoError) as err:
             if create:
-                msg = "Cannot create repository at %s. Original error was %s"\
+                msg = "Cannot create repository at %s. Original error was %s" \
                     % (self.path, err)
             else:
-                msg = "Not valid repository at %s. Original error was %s"\
+                msg = "Not valid repository at %s. Original error was %s" \
                     % (self.path, err)
             raise RepositoryError(msg)
 
@@ -402,7 +401,7 @@
         try:
             return time.mktime(self.get_changeset().date.timetuple())
         except RepositoryError:
-            #fallback to filesystem
+            # fallback to filesystem
             cl_path = os.path.join(self.path, '.hg', "00changelog.i")
             st_path = os.path.join(self.path, '.hg', "store")
             if os.path.exists(cl_path):
@@ -412,7 +411,7 @@
 
     def _get_revision(self, revision):
         """
-        Gets an ID revision given as str. This will always return a fill
+        Gets an ID revision given as str. This will always return a full
         40 char revision number
 
         :param revision: str or int or None
@@ -423,19 +422,22 @@
         if self._empty:
             raise EmptyRepositoryError("There are no changesets yet")
 
-        if revision in [-1, 'tip', None]:
+        if revision in [-1, None]:
             revision = 'tip'
 
         try:
-            revision = hex(self._repo.lookup(revision))
+            if isinstance(revision, int):
+                return self._repo[revision].hex()
+            try:
+                return scmutil.revsymbol(self._repo, revision).hex()
+            except AttributeError: # revsymbol was introduced in Mercurial 4.6
+                return self._repo[revision].hex()
+        except (IndexError, ValueError, RepoLookupError, TypeError):
+            msg = ("Revision %s does not exist for %s" % (revision, self))
+            raise ChangesetDoesNotExistError(msg)
         except (LookupError, ):
             msg = ("Ambiguous identifier `%s` for %s" % (revision, self))
             raise ChangesetDoesNotExistError(msg)
-        except (IndexError, ValueError, RepoLookupError, TypeError):
-            msg = ("Revision %s does not exist for %s" % (revision, self))
-            raise ChangesetDoesNotExistError(msg)
-
-        return revision
 
     def get_ref_revision(self, ref_type, ref_name):
         """
@@ -488,8 +490,8 @@
         to filesystem
         (``file:///``) schema.
         """
-        url = str(url)
-        if url != 'default' and not '://' in url:
+        url = safe_str(url)
+        if url != 'default' and '://' not in url:
             url = "file:" + urllib.pathname2url(url)
         return url
 
@@ -509,7 +511,7 @@
         return changeset
 
     def get_changesets(self, start=None, end=None, start_date=None,
-                       end_date=None, branch_name=None, reverse=False):
+                       end_date=None, branch_name=None, reverse=False, max_revisions=None):
         """
         Returns iterator of ``MercurialChangeset`` objects from start to end
         (both are inclusive)
@@ -521,7 +523,6 @@
         :param branch_name:
         :param reversed: return changesets in reversed order
         """
-
         start_raw_id = self._get_revision(start)
         start_pos = self.revisions.index(start_raw_id) if start else None
         end_raw_id = self._get_revision(end)
@@ -536,19 +537,22 @@
             raise BranchDoesNotExistError(msg)
         if end_pos is not None:
             end_pos += 1
-        #filter branches
+        # filter branches
         filter_ = []
         if branch_name:
-            filter_.append('branch("%s")' % (branch_name))
-
-        if start_date and not end_date:
+            filter_.append('branch("%s")' % safe_str(branch_name))
+        if start_date:
             filter_.append('date(">%s")' % start_date)
-        if end_date and not start_date:
+        if end_date:
             filter_.append('date("<%s")' % end_date)
-        if start_date and end_date:
-            filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
-        if filter_:
-            revisions = scmutil.revrange(self._repo, filter_)
+        if filter_ or max_revisions:
+            if filter_:
+                revspec = ' and '.join(filter_)
+            else:
+                revspec = 'all()'
+            if max_revisions:
+                revspec = 'limit(%s, %s)' % (revspec, max_revisions)
+            revisions = scmutil.revrange(self._repo, [revspec])
         else:
             revisions = self.revisions
 
@@ -598,8 +602,10 @@
             config_file = [config_file]
 
         config = self._repo.ui
-        for path in config_file:
-            config.readconfig(path)
+        if config_file:
+            config = ui.ui()
+            for path in config_file:
+                config.readconfig(path)
         return config.config(section, name)
 
     def get_user_name(self, config_file=None):
@@ -609,7 +615,7 @@
         :param config_file: A path to file which should be used to retrieve
           configuration from (might also be a list of file paths)
         """
-        username = self.get_config_value('ui', 'username')
+        username = self.get_config_value('ui', 'username', config_file=config_file)
         if username:
             return author_name(username)
         return None
@@ -621,7 +627,7 @@
         :param config_file: A path to file which should be used to retrieve
           configuration from (might also be a list of file paths)
         """
-        username = self.get_config_value('ui', 'username')
+        username = self.get_config_value('ui', 'username', config_file=config_file)
         if username:
             return author_email(username)
         return None
--- a/kallithea/lib/vcs/backends/hg/workdir.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/backends/hg/workdir.py	Sun Mar 31 21:28:56 2019 +0200
@@ -19,4 +19,5 @@
         if branch not in self.repository.branches:
             raise BranchDoesNotExistError
 
-        hg_merge.update(self.repository._repo, branch, False, False, None)
+        raw_id = self.repository.branches[branch]
+        hg_merge.update(self.repository._repo, raw_id, False, False, None)
--- a/kallithea/lib/vcs/conf/settings.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/conf/settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -17,7 +17,7 @@
     VCSRC_PATH = os.path.join(VCSRC_PATH, '__init__.py')
 
 # list of default encoding used in safe_unicode/safe_str methods
-DEFAULT_ENCODINGS = aslist('utf8')
+DEFAULT_ENCODINGS = aslist('utf-8')
 
 # path to git executable run by run_git_command function
 GIT_EXECUTABLE_PATH = 'git'
--- a/kallithea/lib/vcs/nodes.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/nodes.py	Sun Mar 31 21:28:56 2019 +0200
@@ -315,18 +315,8 @@
 
     def get_mimetype(self):
         """
-        Mimetype is calculated based on the file's content. If ``_mimetype``
-        attribute is available, it will be returned (backends which store
-        mimetypes or can easily recognize them, should set this private
-        attribute to indicate that type should *NOT* be calculated).
+        Mimetype is calculated based on the file's content.
         """
-        if hasattr(self, '_mimetype'):
-            if (isinstance(self._mimetype, (tuple, list,)) and
-                len(self._mimetype) == 2):
-                return self._mimetype
-            else:
-                raise NodeError('given _mimetype attribute must be an 2 '
-                                'element list or tuple')
 
         mtype, encoding = mimetypes.guess_type(self.name)
 
@@ -338,7 +328,7 @@
                 mtype = 'text/plain'
                 encoding = None
 
-                #try with pygments
+                # try with pygments
                 try:
                     from pygments import lexers
                     mt = lexers.get_lexer_for_filename(self.name).mimetypes
@@ -604,27 +594,19 @@
     is_binary = False
     size = 0
 
-    def __init__(self, name, url=None, changeset=None, alias=None):
+    def __init__(self, name, url, changeset=None, alias=None):
         self.path = name
         self.kind = NodeKind.SUBMODULE
         self.alias = alias
         # we have to use emptyChangeset here since this can point to svn/git/hg
         # submodules we cannot get from repository
         self.changeset = EmptyChangeset(str(changeset), alias=alias)
-        self.url = url or self._extract_submodule_url()
+        self.url = url
 
     def __repr__(self):
         return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
                                  getattr(self.changeset, 'short_id', ''))
 
-    def _extract_submodule_url(self):
-        if self.alias == 'git':
-            #TODO: find a way to parse gits submodule file and extract the
-            # linking URL
-            return self.path
-        if self.alias == 'hg':
-            return self.path
-
     @LazyProperty
     def name(self):
         """
--- a/kallithea/lib/vcs/subprocessio.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/subprocessio.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,8 +1,8 @@
 """
 Module provides a class allowing to wrap communication over subprocess.Popen
-input, output, error streams into a meaningfull, non-blocking, concurrent
+input, output, error streams into a meaningful, non-blocking, concurrent
 stream processor exposing the output data as an iterator fitting to be a
-return value passed by a WSGI applicaiton to a WSGI server per PEP 3333.
+return value passed by a WSGI application to a WSGI server per PEP 3333.
 
 Copyright (c) 2011  Daniel Dotsenko <dotsa[at]hotmail.com>
 
@@ -24,10 +24,11 @@
 """
 import os
 import subprocess
-from kallithea.lib.vcs.utils.compat import deque, Event, Thread, _bytes, _bytearray
+import collections
+import threading
 
 
-class StreamFeeder(Thread):
+class StreamFeeder(threading.Thread):
     """
     Normal writing into pipe-like is blocking once the buffer is filled.
     This thread allows a thread to seep data from a file-like into a pipe
@@ -39,12 +40,12 @@
         super(StreamFeeder, self).__init__()
         self.daemon = True
         filelike = False
-        self.bytes = _bytes()
-        if type(source) in (type(''), _bytes, _bytearray):  # string-like
-            self.bytes = _bytes(source)
+        self.bytes = bytes()
+        if type(source) in (type(''), bytes, bytearray):  # string-like
+            self.bytes = bytes(source)
         else:  # can be either file pointer or file-like
             if type(source) in (int, long):  # file pointer it is
-                ## converting file descriptor (int) stdin into file-like
+                # converting file descriptor (int) stdin into file-like
                 source = os.fdopen(source, 'rb', 16384)
             # let's see if source is file-like by now
             filelike = hasattr(source, 'read')
@@ -71,7 +72,7 @@
         return self.readiface
 
 
-class InputStreamChunker(Thread):
+class InputStreamChunker(threading.Thread):
     def __init__(self, source, target, buffer_size, chunk_size):
 
         super(InputStreamChunker, self).__init__()
@@ -83,16 +84,16 @@
         self.chunk_count_max = int(buffer_size / chunk_size) + 1
         self.chunk_size = chunk_size
 
-        self.data_added = Event()
+        self.data_added = threading.Event()
         self.data_added.clear()
 
-        self.keep_reading = Event()
+        self.keep_reading = threading.Event()
         self.keep_reading.set()
 
-        self.EOF = Event()
+        self.EOF = threading.Event()
         self.EOF.clear()
 
-        self.go = Event()
+        self.go = threading.Event()
         self.go.set()
 
     def stop(self):
@@ -156,14 +157,14 @@
     """
 
     def __init__(self, source, buffer_size=65536, chunk_size=4096,
-                 starting_values=[], bottomless=False):
-
+                 starting_values=None, bottomless=False):
+        starting_values = starting_values or []
         if bottomless:
             maxlen = int(buffer_size / chunk_size)
         else:
             maxlen = None
 
-        self.data = deque(starting_values, maxlen)
+        self.data = collections.deque(starting_values, maxlen)
         self.worker = InputStreamChunker(source, self.data, buffer_size,
                                          chunk_size)
         if starting_values:
@@ -183,7 +184,7 @@
             self.worker.data_added.wait(0.2)
         if len(self.data):
             self.worker.keep_reading.set()
-            return _bytes(self.data.popleft())
+            return bytes(self.data.popleft())
         elif self.worker.EOF.is_set():
             raise StopIteration
 
@@ -233,7 +234,7 @@
         Iterator might have done reading from underlying source, but the read
         chunks might still be available for serving through .next() method.
 
-        :returns: An Event class instance.
+        :returns: An threading.Event class instance.
         """
         return self.worker.EOF
 
@@ -326,7 +327,7 @@
     """
 
     def __init__(self, cmd, inputstream=None, buffer_size=65536,
-                 chunk_size=4096, starting_values=[], **kwargs):
+                 chunk_size=4096, starting_values=None, **kwargs):
         """
         Initializes SubprocessIOChunker
 
@@ -336,7 +337,7 @@
         :param chunk_size: (Default: 4096) A max size of a chunk. Actual chunk may be smaller.
         :param starting_values: (Default: []) An array of strings to put in front of output que.
         """
-
+        starting_values = starting_values or []
         if inputstream:
             input_streamer = StreamFeeder(inputstream)
             input_streamer.start()
@@ -355,7 +356,7 @@
                                    starting_values)
         bg_err = BufferedGenerator(_p.stderr, 16000, 1, bottomless=True)
 
-        while not bg_out.done_reading and not bg_out.reading_paused and not bg_err.length:
+        while not bg_out.done_reading and not bg_out.reading_paused:
             # doing this until we reach either end of file, or end of buffer.
             bg_out.data_added_event.wait(1)
             bg_out.data_added_event.clear()
@@ -364,12 +365,9 @@
         # Either way, if error (returned by ended process, or implied based on
         # presence of stuff in stderr output) we error out.
         # Else, we are happy.
-        _returncode = _p.poll()
-        if _returncode or (_returncode is None and bg_err.length):
-            try:
-                _p.terminate()
-            except Exception:
-                pass
+        returncode = _p.poll()
+        if (returncode is not None # process has terminated
+            and returncode != 0): # and it failed
             bg_out.stop()
             out = ''.join(bg_out)
             bg_err.stop()
@@ -384,7 +382,7 @@
                     "Subprocess exited due to an error:\n" + err)
             else:
                 raise EnvironmentError(
-                    "Subprocess exited with non 0 ret code:%s" % _returncode)
+                    "Subprocess exited with non 0 ret code: %s" % returncode)
         self.process = _p
         self.output = bg_out
         self.error = bg_err
@@ -394,9 +392,14 @@
         return self
 
     def next(self):
-        if self.process and self.process.poll():
-            err = ''.join(self.error)
-            raise EnvironmentError("Subprocess exited due to an error:\n" + err)
+        if self.process:
+            returncode = self.process.poll()
+            if (returncode is not None # process has terminated
+                and returncode != 0): # and it failed
+                self.output.stop()
+                self.error.stop()
+                err = ''.join(self.error)
+                raise EnvironmentError("Subprocess exited due to an error:\n" + err)
         return self.output.next()
 
     def throw(self, type, value=None, traceback=None):
--- a/kallithea/lib/vcs/utils/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -5,6 +5,7 @@
 
 import time
 import datetime
+import re
 
 
 def makedate():
@@ -150,30 +151,34 @@
         return unicode_.encode(to_encoding[0], 'replace')
 
 
+# Regex taken from http://www.regular-expressions.info/email.html
+email_re = re.compile(
+    r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@"""
+    r"""(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?""",
+    re.IGNORECASE)
+
+
 def author_email(author):
     """
-    returns email address of given author.
-    If any of <,> sign are found, it fallbacks to regex findall()
-    and returns first found result or empty string
+    Returns email address of given author string.
+    If author contains <> brackets, only look inside that.
+    If any RFC valid email address is found, return that.
+    Else, return empty string.
 
-    Regex taken from http://www.regular-expressions.info/email.html
     """
     if not author:
         return ''
-    import re
-    r = author.find('>')
-    l = author.find('<')
 
-    if l == -1 or r == -1:
-        # fallback to regex match of email out of a string
-        email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
-                              r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
-                              r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
-                              r"""*[a-z0-9])?""", re.IGNORECASE)
-        m = re.findall(email_re, author)
-        return m[0] if m else ''
+    l = author.find('<') + 1
+    if l != 0:
+        r = author.find('>', l)
+        if r != -1:
+            author = author[l:r]
 
-    return author[l + 1:r].strip()
+    m = email_re.search(author)
+    if m is None:
+        return ''
+    return safe_str(m.group(0))
 
 
 def author_name(author):
@@ -184,7 +189,7 @@
     """
     if not author:
         return ''
-    if not '@' in author:
+    if '@' not in author:
         return author
-    return author.replace(author_email(author), '').replace('<', '')\
+    return author.replace(author_email(author), '').replace('<', '') \
         .replace('>', '').strip()
--- a/kallithea/lib/vcs/utils/annotate.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/annotate.py	Sun Mar 31 21:28:56 2019 +0200
@@ -41,7 +41,7 @@
         following function as ``annotate_from_changeset_func``::
 
             def changeset_to_anchor(changeset):
-                return '<a href="/changesets/%s/">%s</a>\n' %\
+                return '<a href="/changesets/%s/">%s</a>\n' % \
                        (changeset.id, changeset.id)
 
         :param annotate_from_changeset_func: see above
@@ -122,7 +122,7 @@
             for i in range(fl, fl + lncount):
                 if i % st == 0:
                     if aln:
-                        lines.append('<a href="#%s-%d">%*d</a>' \
+                        lines.append('<a href="#%s-%d">%*d</a>'
                                      % (la, i, mw, i))
                     else:
                         lines.append('%*d' % (mw, i))
@@ -134,7 +134,7 @@
         # If pygments cropped last lines break we need do that too
         ln_cs = len(annotate_changesets)
         ln_ = len(ls.splitlines())
-        if  ln_cs > ln_:
+        if ln_cs > ln_:
             annotate_changesets = annotate_changesets[:ln_ - ln_cs]
         annotate = ''.join((self.annotate_from_changeset(changeset)
             for changeset in annotate_changesets))
--- a/kallithea/lib/vcs/utils/baseui_config.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-from kallithea.lib.vcs.utils.hgcompat import ui, config
-
-
-def make_ui(self, path='hgwebdir.config'):
-    """
-    A function that will read python rc files and make an ui from read options
-
-    :param path: path to mercurial config file
-    """
-    #propagated from mercurial documentation
-    sections = [
-                'alias',
-                'auth',
-                'decode/encode',
-                'defaults',
-                'diff',
-                'email',
-                'extensions',
-                'format',
-                'merge-patterns',
-                'merge-tools',
-                'hooks',
-                'http_proxy',
-                'smtp',
-                'patch',
-                'paths',
-                'profiling',
-                'server',
-                'trusted',
-                'ui',
-                'web',
-                ]
-
-    repos = path
-    baseui = ui.ui()
-    cfg = config.config()
-    cfg.read(repos)
-    self.paths = cfg.items('paths')
-    self.base_path = self.paths[0][1].replace('*', '')
-    self.check_repo_dir(self.paths)
-    self.set_statics(cfg)
-
-    for section in sections:
-        for k, v in cfg.items(section):
-            baseui.setconfig(section, k, v)
-
-    return baseui
--- a/kallithea/lib/vcs/utils/compat.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/compat.py	Sun Mar 31 21:28:56 2019 +0200
@@ -4,6 +4,7 @@
 Those utilities may be deleted once ``vcs`` stops support for older Python
 versions.
 """
+
 import sys
 import array
 
@@ -11,308 +12,3 @@
     unittest = __import__('unittest')
 else:
     unittest = __import__('unittest2')
-
-
-if sys.version_info >= (2, 6):
-    _bytes = bytes
-else:
-    # in py2.6 bytes is a synonym for str
-    _bytes = str
-
-if sys.version_info >= (2, 6):
-    _bytearray = bytearray
-else:
-    # no idea if this is correct but all integration tests are passing
-    # i think we never use bytearray anyway
-    _bytearray = array
-
-if sys.version_info >= (2, 6):
-    from collections import deque
-else:
-    #need to implement our own deque with maxlen
-    class deque(object):
-
-        def __init__(self, iterable=(), maxlen= -1):
-            if not hasattr(self, 'data'):
-                self.left = self.right = 0
-                self.data = {}
-            self.maxlen = maxlen or -1
-            self.extend(iterable)
-
-        def append(self, x):
-            self.data[self.right] = x
-            self.right += 1
-            if self.maxlen != -1 and len(self) > self.maxlen:
-                self.popleft()
-
-        def appendleft(self, x):
-            self.left -= 1
-            self.data[self.left] = x
-            if self.maxlen != -1 and len(self) > self.maxlen:
-                self.pop()
-
-        def pop(self):
-            if self.left == self.right:
-                raise IndexError('cannot pop from empty deque')
-            self.right -= 1
-            elem = self.data[self.right]
-            del self.data[self.right]
-            return elem
-
-        def popleft(self):
-            if self.left == self.right:
-                raise IndexError('cannot pop from empty deque')
-            elem = self.data[self.left]
-            del self.data[self.left]
-            self.left += 1
-            return elem
-
-        def clear(self):
-            self.data.clear()
-            self.left = self.right = 0
-
-        def extend(self, iterable):
-            for elem in iterable:
-                self.append(elem)
-
-        def extendleft(self, iterable):
-            for elem in iterable:
-                self.appendleft(elem)
-
-        def rotate(self, n=1):
-            if self:
-                n %= len(self)
-                for i in xrange(n):
-                    self.appendleft(self.pop())
-
-        def __getitem__(self, i):
-            if i < 0:
-                i += len(self)
-            try:
-                return self.data[i + self.left]
-            except KeyError:
-                raise IndexError
-
-        def __setitem__(self, i, value):
-            if i < 0:
-                i += len(self)
-            try:
-                self.data[i + self.left] = value
-            except KeyError:
-                raise IndexError
-
-        def __delitem__(self, i):
-            size = len(self)
-            if not (-size <= i < size):
-                raise IndexError
-            data = self.data
-            if i < 0:
-                i += size
-            for j in xrange(self.left + i, self.right - 1):
-                data[j] = data[j + 1]
-            self.pop()
-
-        def __len__(self):
-            return self.right - self.left
-
-        def __cmp__(self, other):
-            if type(self) != type(other):
-                return cmp(type(self), type(other))
-            return cmp(list(self), list(other))
-
-        def __repr__(self, _track=[]):
-            if id(self) in _track:
-                return '...'
-            _track.append(id(self))
-            r = 'deque(%r, maxlen=%s)' % (list(self), self.maxlen)
-            _track.remove(id(self))
-            return r
-
-        def __getstate__(self):
-            return (tuple(self),)
-
-        def __setstate__(self, s):
-            self.__init__(s[0])
-
-        def __hash__(self):
-            raise TypeError
-
-        def __copy__(self):
-            return self.__class__(self)
-
-        def __deepcopy__(self, memo={}):
-            from copy import deepcopy
-            result = self.__class__()
-            memo[id(self)] = result
-            result.__init__(deepcopy(tuple(self), memo))
-            return result
-
-
-#==============================================================================
-# threading.Event
-#==============================================================================
-
-if sys.version_info >= (2, 6):
-    from threading import Event, Thread
-else:
-    from threading import _Verbose, Lock, Thread, _time, \
-        _allocate_lock, RLock, _sleep
-
-    def Condition(*args, **kwargs):
-        return _Condition(*args, **kwargs)
-
-    class _Condition(_Verbose):
-
-        def __init__(self, lock=None, verbose=None):
-            _Verbose.__init__(self, verbose)
-            if lock is None:
-                lock = RLock()
-            self.__lock = lock
-            # Export the lock's acquire() and release() methods
-            self.acquire = lock.acquire
-            self.release = lock.release
-            # If the lock defines _release_save() and/or _acquire_restore(),
-            # these override the default implementations (which just call
-            # release() and acquire() on the lock).  Ditto for _is_owned().
-            try:
-                self._release_save = lock._release_save
-            except AttributeError:
-                pass
-            try:
-                self._acquire_restore = lock._acquire_restore
-            except AttributeError:
-                pass
-            try:
-                self._is_owned = lock._is_owned
-            except AttributeError:
-                pass
-            self.__waiters = []
-
-        def __enter__(self):
-            return self.__lock.__enter__()
-
-        def __exit__(self, *args):
-            return self.__lock.__exit__(*args)
-
-        def __repr__(self):
-            return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters))
-
-        def _release_save(self):
-            self.__lock.release()           # No state to save
-
-        def _acquire_restore(self, x):
-            self.__lock.acquire()           # Ignore saved state
-
-        def _is_owned(self):
-            # Return True if lock is owned by current_thread.
-            # This method is called only if __lock doesn't have _is_owned().
-            if self.__lock.acquire(0):
-                self.__lock.release()
-                return False
-            else:
-                return True
-
-        def wait(self, timeout=None):
-            if not self._is_owned():
-                raise RuntimeError("cannot wait on un-acquired lock")
-            waiter = _allocate_lock()
-            waiter.acquire()
-            self.__waiters.append(waiter)
-            saved_state = self._release_save()
-            try:    # restore state no matter what (e.g., KeyboardInterrupt)
-                if timeout is None:
-                    waiter.acquire()
-                    if __debug__:
-                        self._note("%s.wait(): got it", self)
-                else:
-                    # Balancing act:  We can't afford a pure busy loop, so we
-                    # have to sleep; but if we sleep the whole timeout time,
-                    # we'll be unresponsive.  The scheme here sleeps very
-                    # little at first, longer as time goes on, but never longer
-                    # than 20 times per second (or the timeout time remaining).
-                    endtime = _time() + timeout
-                    delay = 0.0005 # 500 us -> initial delay of 1 ms
-                    while True:
-                        gotit = waiter.acquire(0)
-                        if gotit:
-                            break
-                        remaining = endtime - _time()
-                        if remaining <= 0:
-                            break
-                        delay = min(delay * 2, remaining, .05)
-                        _sleep(delay)
-                    if not gotit:
-                        if __debug__:
-                            self._note("%s.wait(%s): timed out", self, timeout)
-                        try:
-                            self.__waiters.remove(waiter)
-                        except ValueError:
-                            pass
-                    else:
-                        if __debug__:
-                            self._note("%s.wait(%s): got it", self, timeout)
-            finally:
-                self._acquire_restore(saved_state)
-
-        def notify(self, n=1):
-            if not self._is_owned():
-                raise RuntimeError("cannot notify on un-acquired lock")
-            __waiters = self.__waiters
-            waiters = __waiters[:n]
-            if not waiters:
-                if __debug__:
-                    self._note("%s.notify(): no waiters", self)
-                return
-            self._note("%s.notify(): notifying %d waiter%s", self, n,
-                       n != 1 and "s" or "")
-            for waiter in waiters:
-                waiter.release()
-                try:
-                    __waiters.remove(waiter)
-                except ValueError:
-                    pass
-
-        def notifyAll(self):
-            self.notify(len(self.__waiters))
-
-        notify_all = notifyAll
-
-    def Event(*args, **kwargs):
-        return _Event(*args, **kwargs)
-
-    class _Event(_Verbose):
-
-        # After Tim Peters' event class (without is_posted())
-
-        def __init__(self, verbose=None):
-            _Verbose.__init__(self, verbose)
-            self.__cond = Condition(Lock())
-            self.__flag = False
-
-        def isSet(self):
-            return self.__flag
-
-        is_set = isSet
-
-        def set(self):
-            self.__cond.acquire()
-            try:
-                self.__flag = True
-                self.__cond.notify_all()
-            finally:
-                self.__cond.release()
-
-        def clear(self):
-            self.__cond.acquire()
-            try:
-                self.__flag = False
-            finally:
-                self.__cond.release()
-
-        def wait(self, timeout=None):
-            self.__cond.acquire()
-            try:
-                if not self.__flag:
-                    self.__cond.wait(timeout)
-            finally:
-                self.__cond.release()
--- a/kallithea/lib/vcs/utils/diffs.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,460 +0,0 @@
-# -*- coding: utf-8 -*-
-# original copyright: 2007-2008 by Armin Ronacher
-# licensed under the BSD license.
-
-import re
-import difflib
-import logging
-import itertools
-
-from difflib import unified_diff
-
-from kallithea.lib.vcs.exceptions import VCSError
-from kallithea.lib.vcs.nodes import FileNode, NodeError
-from kallithea.lib.vcs.utils import safe_unicode
-
-
-def get_udiff(filenode_old, filenode_new, show_whitespace=True):
-    """
-    Returns unified diff between given ``filenode_old`` and ``filenode_new``.
-    """
-    try:
-        filenode_old_date = filenode_old.changeset.date
-    except NodeError:
-        filenode_old_date = None
-
-    try:
-        filenode_new_date = filenode_new.changeset.date
-    except NodeError:
-        filenode_new_date = None
-
-    for filenode in (filenode_old, filenode_new):
-        if not isinstance(filenode, FileNode):
-            raise VCSError("Given object should be FileNode object, not %s"
-                % filenode.__class__)
-
-    if filenode_old_date and filenode_new_date:
-        if not filenode_old_date < filenode_new_date:
-            logging.debug("Generating udiff for filenodes with not increasing "
-                "dates")
-
-    vcs_udiff = unified_diff(filenode_old.content.splitlines(True),
-                               filenode_new.content.splitlines(True),
-                               filenode_old.name,
-                               filenode_new.name,
-                               filenode_old_date,
-                               filenode_old_date)
-    return vcs_udiff
-
-
-def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True):
-    """
-    Returns git style diff between given ``filenode_old`` and ``filenode_new``.
-
-    :param ignore_whitespace: ignore whitespaces in diff
-    """
-
-    for filenode in (filenode_old, filenode_new):
-        if not isinstance(filenode, FileNode):
-            raise VCSError("Given object should be FileNode object, not %s"
-                % filenode.__class__)
-
-    old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
-    new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
-
-    repo = filenode_new.changeset.repository
-    vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
-                                 ignore_whitespace)
-
-    return vcs_gitdiff
-
-
-class DiffProcessor(object):
-    """
-    Give it a unified diff and it returns a list of the files that were
-    mentioned in the diff together with a dict of meta information that
-    can be used to render it in a HTML template.
-    """
-    _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
-
-    def __init__(self, diff, differ='diff', format='udiff'):
-        """
-        :param diff:   a text in diff format or generator
-        :param format: format of diff passed, `udiff` or `gitdiff`
-        """
-        if isinstance(diff, basestring):
-            diff = [diff]
-
-        self.__udiff = diff
-        self.__format = format
-        self.adds = 0
-        self.removes = 0
-
-        if isinstance(self.__udiff, basestring):
-            self.lines = iter(self.__udiff.splitlines(1))
-
-        elif self.__format == 'gitdiff':
-            udiff_copy = self.copy_iterator()
-            self.lines = itertools.imap(self.escaper,
-                                        self._parse_gitdiff(udiff_copy))
-        else:
-            udiff_copy = self.copy_iterator()
-            self.lines = itertools.imap(self.escaper, udiff_copy)
-
-        # Select a differ.
-        if differ == 'difflib':
-            self.differ = self._highlight_line_difflib
-        else:
-            self.differ = self._highlight_line_udiff
-
-    def escaper(self, string):
-        return string.replace('<', '&lt;').replace('>', '&gt;')
-
-    def copy_iterator(self):
-        """
-        make a fresh copy of generator, we should not iterate thru
-        an original as it's needed for repeating operations on
-        this instance of DiffProcessor
-        """
-        self.__udiff, iterator_copy = itertools.tee(self.__udiff)
-        return iterator_copy
-
-    def _extract_rev(self, line1, line2):
-        """
-        Extract the filename and revision hint from a line.
-        """
-
-        try:
-            if line1.startswith('--- ') and line2.startswith('+++ '):
-                l1 = line1[4:].split(None, 1)
-                old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None
-                old_rev = l1[1] if len(l1) == 2 else 'old'
-
-                l2 = line2[4:].split(None, 1)
-                new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None
-                new_rev = l2[1] if len(l2) == 2 else 'new'
-
-                filename = old_filename if (old_filename !=
-                                            'dev/null') else new_filename
-
-                return filename, new_rev, old_rev
-        except (ValueError, IndexError):
-            pass
-
-        return None, None, None
-
-    def _parse_gitdiff(self, diffiterator):
-        def line_decoder(l):
-            if l.startswith('+') and not l.startswith('+++'):
-                self.adds += 1
-            elif l.startswith('-') and not l.startswith('---'):
-                self.removes += 1
-            return safe_unicode(l)
-
-        output = list(diffiterator)
-        size = len(output)
-
-        if size == 2:
-            l = []
-            l.extend([output[0]])
-            l.extend(output[1].splitlines(1))
-            return map(line_decoder, l)
-        elif size == 1:
-            return  map(line_decoder, output[0].splitlines(1))
-        elif size == 0:
-            return []
-
-        raise Exception('wrong size of diff %s' % size)
-
-    def _highlight_line_difflib(self, line, next):
-        """
-        Highlight inline changes in both lines.
-        """
-
-        if line['action'] == 'del':
-            old, new = line, next
-        else:
-            old, new = next, line
-
-        oldwords = re.split(r'(\W)', old['line'])
-        newwords = re.split(r'(\W)', new['line'])
-
-        sequence = difflib.SequenceMatcher(None, oldwords, newwords)
-
-        oldfragments, newfragments = [], []
-        for tag, i1, i2, j1, j2 in sequence.get_opcodes():
-            oldfrag = ''.join(oldwords[i1:i2])
-            newfrag = ''.join(newwords[j1:j2])
-            if tag != 'equal':
-                if oldfrag:
-                    oldfrag = '<del>%s</del>' % oldfrag
-                if newfrag:
-                    newfrag = '<ins>%s</ins>' % newfrag
-            oldfragments.append(oldfrag)
-            newfragments.append(newfrag)
-
-        old['line'] = "".join(oldfragments)
-        new['line'] = "".join(newfragments)
-
-    def _highlight_line_udiff(self, line, next):
-        """
-        Highlight inline changes in both lines.
-        """
-        start = 0
-        limit = min(len(line['line']), len(next['line']))
-        while start < limit and line['line'][start] == next['line'][start]:
-            start += 1
-        end = -1
-        limit -= start
-        while -end <= limit and line['line'][end] == next['line'][end]:
-            end -= 1
-        end += 1
-        if start or end:
-            def do(l):
-                last = end + len(l['line'])
-                if l['action'] == 'add':
-                    tag = 'ins'
-                else:
-                    tag = 'del'
-                l['line'] = '%s<%s>%s</%s>%s' % (
-                    l['line'][:start],
-                    tag,
-                    l['line'][start:last],
-                    tag,
-                    l['line'][last:]
-                )
-            do(line)
-            do(next)
-
-    def _parse_udiff(self):
-        """
-        Parse the diff an return data for the template.
-        """
-        lineiter = self.lines
-        files = []
-        try:
-            line = lineiter.next()
-            # skip first context
-            skipfirst = True
-            while 1:
-                # continue until we found the old file
-                if not line.startswith('--- '):
-                    line = lineiter.next()
-                    continue
-
-                chunks = []
-                filename, old_rev, new_rev = \
-                    self._extract_rev(line, lineiter.next())
-                files.append({
-                    'filename':         filename,
-                    'old_revision':     old_rev,
-                    'new_revision':     new_rev,
-                    'chunks':           chunks
-                })
-
-                line = lineiter.next()
-                while line:
-                    match = self._chunk_re.match(line)
-                    if not match:
-                        break
-
-                    lines = []
-                    chunks.append(lines)
-
-                    old_line, old_end, new_line, new_end = \
-                        [int(x or 1) for x in match.groups()[:-1]]
-                    old_line -= 1
-                    new_line -= 1
-                    context = len(match.groups()) == 5
-                    old_end += old_line
-                    new_end += new_line
-
-                    if context:
-                        if not skipfirst:
-                            lines.append({
-                                'old_lineno': '...',
-                                'new_lineno': '...',
-                                'action': 'context',
-                                'line': line,
-                            })
-                        else:
-                            skipfirst = False
-
-                    line = lineiter.next()
-                    while old_line < old_end or new_line < new_end:
-                        if line:
-                            command, line = line[0], line[1:]
-                        else:
-                            command = ' '
-                        affects_old = affects_new = False
-
-                        # ignore those if we don't expect them
-                        if command in '#@':
-                            continue
-                        elif command == '+':
-                            affects_new = True
-                            action = 'add'
-                        elif command == '-':
-                            affects_old = True
-                            action = 'del'
-                        else:
-                            affects_old = affects_new = True
-                            action = 'unmod'
-
-                        old_line += affects_old
-                        new_line += affects_new
-                        lines.append({
-                            'old_lineno':   affects_old and old_line or '',
-                            'new_lineno':   affects_new and new_line or '',
-                            'action':       action,
-                            'line':         line
-                        })
-                        line = lineiter.next()
-
-        except StopIteration:
-            pass
-
-        # highlight inline changes
-        for file in files:
-            for chunk in chunks:
-                lineiter = iter(chunk)
-                #first = True
-                try:
-                    while 1:
-                        line = lineiter.next()
-                        if line['action'] != 'unmod':
-                            nextline = lineiter.next()
-                            if nextline['action'] == 'unmod' or \
-                               nextline['action'] == line['action']:
-                                continue
-                            self.differ(line, nextline)
-                except StopIteration:
-                    pass
-
-        return files
-
-    def prepare(self):
-        """
-        Prepare the passed udiff for HTML rendering. It'l return a list
-        of dicts
-        """
-        return self._parse_udiff()
-
-    def _safe_id(self, idstring):
-        """Make a string safe for including in an id attribute.
-
-        The HTML spec says that id attributes 'must begin with
-        a letter ([A-Za-z]) and may be followed by any number
-        of letters, digits ([0-9]), hyphens ("-"), underscores
-        ("_"), colons (":"), and periods (".")'. These regexps
-        are slightly over-zealous, in that they remove colons
-        and periods unnecessarily.
-
-        Whitespace is transformed into underscores, and then
-        anything which is not a hyphen or a character that
-        matches \w (alphanumerics and underscore) is removed.
-
-        """
-        # Transform all whitespace to underscore
-        idstring = re.sub(r'\s', "_", idstring)
-        # Remove everything that is not a hyphen or a member of \w
-        idstring = re.sub(r'(?!-)\W', "", idstring).lower()
-        return idstring
-
-    def raw_diff(self):
-        """
-        Returns raw string as udiff
-        """
-        udiff_copy = self.copy_iterator()
-        if self.__format == 'gitdiff':
-            udiff_copy = self._parse_gitdiff(udiff_copy)
-        return u''.join(udiff_copy)
-
-    def as_html(self, table_class='code-difftable', line_class='line',
-                new_lineno_class='lineno old', old_lineno_class='lineno new',
-                code_class='code'):
-        """
-        Return udiff as html table with customized css classes
-        """
-        def _link_to_if(condition, label, url):
-            """
-            Generates a link if condition is meet or just the label if not.
-            """
-
-            if condition:
-                return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
-                                                                'label': label}
-            else:
-                return label
-        diff_lines = self.prepare()
-        _html_empty = True
-        _html = []
-        _html.append('''<table class="%(table_class)s">\n''' \
-                                            % {'table_class': table_class})
-        for diff in diff_lines:
-            for line in diff['chunks']:
-                _html_empty = False
-                for change in line:
-                    _html.append('''<tr class="%(line_class)s %(action)s">\n''' \
-                        % {'line_class': line_class,
-                           'action': change['action']})
-                    anchor_old_id = ''
-                    anchor_new_id = ''
-                    anchor_old = "%(filename)s_o%(oldline_no)s" % \
-                                {'filename': self._safe_id(diff['filename']),
-                                 'oldline_no': change['old_lineno']}
-                    anchor_new = "%(filename)s_n%(oldline_no)s" % \
-                                {'filename': self._safe_id(diff['filename']),
-                                 'oldline_no': change['new_lineno']}
-                    cond_old = change['old_lineno'] != '...' and \
-                                                        change['old_lineno']
-                    cond_new = change['new_lineno'] != '...' and \
-                                                        change['new_lineno']
-                    if cond_old:
-                        anchor_old_id = 'id="%s"' % anchor_old
-                    if cond_new:
-                        anchor_new_id = 'id="%s"' % anchor_new
-                    ###########################################################
-                    # OLD LINE NUMBER
-                    ###########################################################
-                    _html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
-                                    % {'a_id': anchor_old_id,
-                                       'old_lineno_cls': old_lineno_class})
-
-                    _html.append('''<pre>%(link)s</pre>''' \
-                        % {'link':
-                        _link_to_if(cond_old, change['old_lineno'], '#%s' \
-                                                                % anchor_old)})
-                    _html.append('''</td>\n''')
-                    ###########################################################
-                    # NEW LINE NUMBER
-                    ###########################################################
-
-                    _html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
-                                    % {'a_id': anchor_new_id,
-                                       'new_lineno_cls': new_lineno_class})
-
-                    _html.append('''<pre>%(link)s</pre>''' \
-                        % {'link':
-                        _link_to_if(cond_new, change['new_lineno'], '#%s' \
-                                                                % anchor_new)})
-                    _html.append('''</td>\n''')
-                    ###########################################################
-                    # CODE
-                    ###########################################################
-                    _html.append('''\t<td class="%(code_class)s">''' \
-                                                % {'code_class': code_class})
-                    _html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
-                                                % {'code': change['line']})
-                    _html.append('''\t</td>''')
-                    _html.append('''\n</tr>\n''')
-        _html.append('''</table>''')
-        if _html_empty:
-            return None
-        return ''.join(_html)
-
-    def stat(self):
-        """
-        Returns tuple of adde,and removed lines for this instance
-        """
-        return self.adds, self.removes
--- a/kallithea/lib/vcs/utils/helpers.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/helpers.py	Sun Mar 31 21:28:56 2019 +0200
@@ -99,16 +99,6 @@
     return result
 
 
-def run_command(cmd, *args):
-    """
-    Runs command on the system with given ``args``.
-    """
-    command = ' '.join((cmd, args))
-    p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
-    stdout, stderr = p.communicate()
-    return p.retcode, stdout, stderr
-
-
 def get_highlighted_code(name, code, type='terminal'):
     """
     If pygments are available on the system
@@ -149,7 +139,7 @@
     """
     text = text.strip()
     CID_RE = r'[a-zA-Z0-9]+'
-    if not '..' in text:
+    if '..' not in text:
         m = re.match(r'^(?P<cid>%s)$' % CID_RE, text)
         if m:
             return {
--- a/kallithea/lib/vcs/utils/hgcompat.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/hgcompat.py	Sun Mar 31 21:28:56 2019 +0200
@@ -3,46 +3,52 @@
 """
 
 import mercurial
-import mercurial.demandimport
-## patch demandimport, due to bug in mercurial when it always triggers demandimport.enable()
-mercurial.demandimport.enable = lambda *args, **kwargs: 1
+from mercurial import demandimport
+# patch demandimport, due to bug in mercurial when it always triggers demandimport.enable()
+demandimport.enable = lambda *args, **kwargs: 1
 from mercurial import archival, merge as hg_merge, patch, ui
 from mercurial import discovery
 from mercurial import localrepo
 from mercurial import unionrepo
 from mercurial import scmutil
 from mercurial import config
+from mercurial import tags as tagsmod
+from mercurial import httppeer
+from mercurial import sshpeer
 from mercurial.commands import clone, nullid, pull
 from mercurial.context import memctx, memfilectx
 from mercurial.error import RepoError, RepoLookupError, Abort
 from mercurial.hgweb import hgweb_mod
 from mercurial.hgweb.common import get_contact
-from mercurial.localrepo import localrepository
 from mercurial.match import match
 from mercurial.mdiff import diffopts
 from mercurial.node import hex
 from mercurial.encoding import tolocal
 from mercurial.discovery import findcommonoutgoing
 from mercurial.hg import peer
-from mercurial.httppeer import httppeer
-from mercurial.sshpeer import sshpeer
 from mercurial.util import url as hg_url
 from mercurial.scmutil import revrange
 from mercurial.node import nullrev
-
-# those authhandlers are patched for python 2.6.5 bug an
-# infinite looping when given invalid resources
 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
 
+
+# Mercurial 4.5 8a0cac20a1ad introduced an extra memctx changectx argument
+# - introduce an optional wrapper factory that doesn't pass it on
 import inspect
-# Mercurial 3.1 503bb3af70fe
-if inspect.getargspec(memfilectx.__init__).args[1] != 'repo':
-    _org__init__=memfilectx.__init__
-    def _memfilectx__init__(self, repo, *a, **b):
-        return _org__init__(self, *a, **b)
-    memfilectx.__init__ = _memfilectx__init__
+if inspect.getargspec(memfilectx.__init__).args[2] != 'changectx':
+    __org_memfilectx = memfilectx
+    memfilectx = lambda repo, changectx, *args, **kwargs: __org_memfilectx(repo, *args, **kwargs)
+
 
 # workaround for 3.3 94ac64bcf6fe and not calling largefiles reposetup correctly
-localrepository._lfstatuswriters = [lambda *msg, **opts: None]
+localrepo.localrepository._lfstatuswriters = [lambda *msg, **opts: None]
 # 3.5 7699d3212994 added the invariant that repo.lfstatus must exist before hitting overridearchive
-localrepository.lfstatus = False
+localrepo.localrepository.lfstatus = False
+
+# Mercurial 4.2 moved tag from localrepo to the tags module
+def tag(repo, *args):
+    try:
+        tag_f  = tagsmod.tag
+    except AttributeError:
+        return repo.tag(*args)
+    tag_f(repo, *args)
--- a/kallithea/lib/vcs/utils/imports.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/imports.py	Sun Mar 31 21:28:56 2019 +0200
@@ -19,7 +19,7 @@
     try:
         class_mod = __import__(mod_path, {}, {}, [class_name])
     except ImportError as err:
-        msg = "There was problem while trying to import backend class. "\
+        msg = "There was problem while trying to import backend class. " \
             "Original error was:\n%s" % err
         raise VCSError(msg)
     cls = getattr(class_mod, class_name)
--- a/kallithea/lib/vcs/utils/lazy.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/lazy.py	Sun Mar 31 21:28:56 2019 +0200
@@ -9,6 +9,7 @@
     def __reduce__(self):
         return '_missing'
 
+
 _missing = _Missing()
 
 
--- a/kallithea/lib/vcs/utils/progressbar.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/progressbar.py	Sun Mar 31 21:28:56 2019 +0200
@@ -10,6 +10,7 @@
 class ProgressBarError(Exception):
     pass
 
+
 class AlreadyFinishedError(ProgressBarError):
     pass
 
@@ -179,6 +180,7 @@
 RESET = '0'
 opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
 
+
 def colorize(text='', opts=(), **kwargs):
     """
     Returns your text, enclosed in ANSI graphics codes.
@@ -223,6 +225,7 @@
         text = text + '\x1b[%sm' % RESET
     return ('\x1b[%sm' % ';'.join(code_list)) + text
 
+
 def make_style(opts=(), **kwargs):
     """
     Returns a function with default parameters for colorize()
@@ -235,6 +238,7 @@
     """
     return lambda text: colorize(text, opts, **kwargs)
 
+
 NOCOLOR_PALETTE = 'nocolor'
 DARK_PALETTE = 'dark'
 LIGHT_PALETTE = 'light'
@@ -348,7 +352,6 @@
     pass
 
 
-
 def main():
     import time
 
--- a/kallithea/lib/vcs/utils/termcolors.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/vcs/utils/termcolors.py	Sun Mar 31 21:28:56 2019 +0200
@@ -11,6 +11,7 @@
 RESET = '0'
 opt_dict = {'bold': '1', 'underscore': '4', 'blink': '5', 'reverse': '7', 'conceal': '8'}
 
+
 def colorize(text='', opts=(), **kwargs):
     """
     Returns your text, enclosed in ANSI graphics codes.
@@ -55,6 +56,7 @@
         text = text + '\x1b[%sm' % RESET
     return ('\x1b[%sm' % ';'.join(code_list)) + text
 
+
 def make_style(opts=(), **kwargs):
     """
     Returns a function with default parameters for colorize()
@@ -67,6 +69,7 @@
     """
     return lambda text: colorize(text, opts, **kwargs)
 
+
 NOCOLOR_PALETTE = 'nocolor'
 DARK_PALETTE = 'dark'
 LIGHT_PALETTE = 'light'
@@ -120,10 +123,11 @@
 }
 DEFAULT_PALETTE = DARK_PALETTE
 
+
 def parse_color_setting(config_string):
     """Parse a DJANGO_COLORS environment variable to produce the system palette
 
-    The general form of a pallete definition is:
+    The general form of a palette definition is:
 
         "palette;role=fg;role=fg/bg;role=fg,option,option;role=fg/bg,option,option"
 
@@ -135,7 +139,7 @@
         option is a display options.
 
     Specifying a named palette is the same as manually specifying the individual
-    definitions for each role. Any individual definitions following the pallete
+    definitions for each role. Any individual definitions following the palette
     definition will augment the base palette definition.
 
     Valid roles:
--- a/kallithea/lib/verlib.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/lib/verlib.py	Sun Mar 31 21:28:56 2019 +0200
@@ -5,10 +5,12 @@
 
 import re
 
+
 class IrrationalVersionError(Exception):
     """This is an irrational version."""
     pass
 
+
 class HugeMajorVersionNumError(IrrationalVersionError):
     """An irrational version because the major version number is huge
     (often because a year or date was used).
@@ -18,6 +20,7 @@
     """
     pass
 
+
 # A marker used in the second and third parts of the `parts` tuple, for
 # versions that don't have those segments, to sort properly. An example
 # of versions in sort order ('highest' last):
@@ -47,6 +50,7 @@
     (?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)?
     $''', re.VERBOSE)
 
+
 class NormalizedVersion(object):
     """A rational version.
 
@@ -212,6 +216,7 @@
     def __ge__(self, other):
         return self.__eq__(other) or self.__gt__(other)
 
+
 def suggest_normalized_version(s):
     """Suggest a normalized version close to the given version string.
 
@@ -251,7 +256,7 @@
     rs = re.sub(r"dev$", r"dev0", rs)
 
     # if we have something like "b-2" or "a.2" at the end of the
-    # version, that is pobably beta, alpha, etc
+    # version, that is probably beta, alpha, etc
     # let's remove the dash or dot
     rs = re.sub(r"([abc|rc])[\-\.](\d+)$", r"\1\2", rs)
 
@@ -267,7 +272,7 @@
         rs = rs[1:]
 
     # Clean leading '0's on numbers.
-    #TODO: unintended side-effect on, e.g., "2003.05.09"
+    # TODO: unintended side-effect on, e.g., "2003.05.09"
     # PyPI stats: 77 (~2%) better
     rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs)
 
@@ -313,7 +318,6 @@
     # PyPI stats: ~21 (0.62%) better
     rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs)
 
-
     # Tcl/Tk uses "px" for their post release markers
     rs = re.sub(r"p(\d+)$", r".post\1", rs)
 
--- a/kallithea/model/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -23,119 +23,4 @@
 :author: marcink
 :copyright: (c) 2013 RhodeCode GmbH, and others.
 :license: GPLv3, see LICENSE.md for more details.
-
-
-:example:
-
-    .. code-block:: python
-
-       from paste.deploy import appconfig
-       from pylons import config
-       from sqlalchemy import engine_from_config
-       from kallithea.config.environment import load_environment
-
-       conf = appconfig('config:development.ini', relative_to = './../../')
-       load_environment(conf.global_conf, conf.local_conf)
-
-       engine = engine_from_config(config, 'sqlalchemy.')
-       init_model(engine)
-       # RUN YOUR CODE HERE
-
 """
-
-
-import logging
-from kallithea.model import meta
-from kallithea.lib.utils2 import safe_str, obfuscate_url_pw
-
-log = logging.getLogger(__name__)
-
-
-def init_model(engine):
-    """
-    Initializes db session, bind the engine with the metadata,
-    Call this before using any of the tables or classes in the model,
-    preferably once in application start
-
-    :param engine: engine to bind to
-    """
-    engine_str = obfuscate_url_pw(str(engine.url))
-    log.info("initializing db for %s", engine_str)
-    meta.Base.metadata.bind = engine
-
-
-class BaseModel(object):
-    """
-    Base Model for all Kallithea models, it adds sql alchemy session
-    into instance of model
-
-    :param sa: If passed it reuses this session instead of creating a new one
-    """
-
-    cls = None  # override in child class
-
-    def __init__(self, sa=None):
-        if sa is not None:
-            self.sa = sa
-        else:
-            self.sa = meta.Session()
-
-    def _get_instance(self, cls, instance, callback=None):
-        """
-        Gets instance of given cls using some simple lookup mechanism.
-
-        :param cls: class to fetch
-        :param instance: int or Instance
-        :param callback: callback to call if all lookups failed
-        """
-
-        if isinstance(instance, cls):
-            return instance
-        elif isinstance(instance, (int, long)) or safe_str(instance).isdigit():
-            return cls.get(instance)
-        else:
-            if instance is not None:
-                if callback is None:
-                    raise Exception(
-                        'given object must be int, long or Instance of %s '
-                        'got %s, no callback provided' % (cls, type(instance))
-                    )
-                else:
-                    return callback(instance)
-
-    def _get_user(self, user):
-        """
-        Helper method to get user by ID, or username fallback
-
-        :param user: UserID, username, or User instance
-        """
-        from kallithea.model.db import User
-        return self._get_instance(User, user,
-                                  callback=User.get_by_username)
-
-    def _get_repo(self, repository):
-        """
-        Helper method to get repository by ID, or repository name
-
-        :param repository: RepoID, repository name or Repository Instance
-        """
-        from kallithea.model.db import Repository
-        return self._get_instance(Repository, repository,
-                                  callback=Repository.get_by_repo_name)
-
-    def _get_perm(self, permission):
-        """
-        Helper method to get permission by ID, or permission name
-
-        :param permission: PermissionID, permission_name or Permission instance
-        """
-        from kallithea.model.db import Permission
-        return self._get_instance(Permission, permission,
-                                  callback=Permission.get_by_key)
-
-    @classmethod
-    def get_all(cls):
-        """
-        Returns all instances of what is defined in `cls` class variable
-        """
-        return cls.cls.getAll()
--- a/kallithea/model/api_key.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/api_key.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,18 +27,15 @@
 
 import time
 import logging
-from sqlalchemy import or_
 
 from kallithea.lib.utils2 import generate_api_key
-from kallithea.model import BaseModel
-from kallithea.model.db import UserApiKeys
+from kallithea.model.db import User, UserApiKeys
 from kallithea.model.meta import Session
 
 log = logging.getLogger(__name__)
 
 
-class ApiKeyModel(BaseModel):
-    cls = UserApiKeys
+class ApiKeyModel(object):
 
     def create(self, user, description, lifetime=-1):
         """
@@ -46,7 +43,7 @@
         :param description: description of ApiKey
         :param lifetime: expiration time in seconds
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
 
         new_api_key = UserApiKeys()
         new_api_key.api_key = generate_api_key()
@@ -65,18 +62,16 @@
         api_key = UserApiKeys.query().filter(UserApiKeys.api_key == api_key)
 
         if user is not None:
-            user = self._get_user(user)
+            user = User.guess_instance(user)
             api_key = api_key.filter(UserApiKeys.user_id == user.user_id)
 
         api_key = api_key.scalar()
         Session().delete(api_key)
 
     def get_api_keys(self, user, show_expired=True):
-        user = self._get_user(user)
-        user_api_keys = UserApiKeys.query()\
+        user = User.guess_instance(user)
+        user_api_keys = UserApiKeys.query() \
             .filter(UserApiKeys.user_id == user.user_id)
         if not show_expired:
-            user_api_keys = user_api_keys\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time()))
+            user_api_keys = user_api_keys.filter_by(is_expired=False)
         return user_api_keys
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/model/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,46 @@
+# -*- 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.model.base
+~~~~~~~~~~~~~~~~~~~~
+
+The application's model objects
+
+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: Nov 25, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+
+import logging
+from kallithea.model import meta
+from kallithea.lib.utils2 import obfuscate_url_pw
+
+log = logging.getLogger(__name__)
+
+
+def init_model(engine):
+    """
+    Initializes db session, bind the engine with the metadata,
+    Call this before using any of the tables or classes in the model,
+    preferably once in application start
+
+    :param engine: engine to bind to
+    """
+    engine_str = obfuscate_url_pw(str(engine.url))
+    log.info("initializing db for %s", engine_str)
+    meta.Base.metadata.bind = engine
--- a/kallithea/model/changeset_status.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/changeset_status.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,36 +28,27 @@
 import logging
 from sqlalchemy.orm import joinedload
 
-from kallithea.model import BaseModel
-from kallithea.model.db import ChangesetStatus, PullRequest
-from kallithea.lib.exceptions import StatusChangeOnClosedPullRequestError
+from kallithea.model.db import ChangesetStatus, PullRequest, Repository, User, Session
 
 log = logging.getLogger(__name__)
 
 
-class ChangesetStatusModel(BaseModel):
-
-    cls = ChangesetStatus
-
-    def __get_changeset_status(self, changeset_status):
-        return self._get_instance(ChangesetStatus, changeset_status)
-
-    def __get_pull_request(self, pull_request):
-        return self._get_instance(PullRequest, pull_request)
+class ChangesetStatusModel(object):
 
     def _get_status_query(self, repo, revision, pull_request,
                           with_revisions=False):
-        repo = self._get_repo(repo)
+        repo = Repository.guess_instance(repo)
 
-        q = ChangesetStatus.query()\
+        q = ChangesetStatus.query() \
             .filter(ChangesetStatus.repo == repo)
         if not with_revisions:
+            # only report the latest vote across all users! TODO: be smarter!
             q = q.filter(ChangesetStatus.version == 0)
 
         if revision:
             q = q.filter(ChangesetStatus.revision == revision)
         elif pull_request:
-            pull_request = self.__get_pull_request(pull_request)
+            pull_request = PullRequest.guess_instance(pull_request)
             q = q.filter(ChangesetStatus.pull_request == pull_request)
         else:
             raise Exception('Please specify revision or pull_request')
@@ -98,14 +89,14 @@
         pull_request_reviewers = []
         pull_request_pending_reviewers = []
         relevant_statuses = []
-        for r in pull_request.reviewers:
-            st = cs_statuses.get(r.user.username)
+        for user in pull_request.get_reviewer_users():
+            st = cs_statuses.get(user.username)
             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))
+            status = ChangesetStatus.STATUS_NOT_REVIEWED if st is None else st.status
+            if status in (ChangesetStatus.STATUS_NOT_REVIEWED,
+                          ChangesetStatus.STATUS_UNDER_REVIEW):
+                pull_request_pending_reviewers.append(user)
+            pull_request_reviewers.append((user, status))
 
         result = self._calculate_status(relevant_statuses)
 
@@ -141,7 +132,7 @@
         return status
 
     def set_status(self, repo, status, user, comment, revision=None,
-                   pull_request=None, dont_allow_on_closed_pull_request=False):
+                   pull_request=None):
         """
         Creates new status for changeset or updates the old ones bumping their
         version, leaving the current status at the value of 'status'.
@@ -152,11 +143,8 @@
         :param comment:
         :param revision:
         :param pull_request:
-        :param dont_allow_on_closed_pull_request: don't allow a status change
-            if last status was for pull request and it's closed. We shouldn't
-            mess around this manually
         """
-        repo = self._get_repo(repo)
+        repo = Repository.guess_instance(repo)
 
         q = ChangesetStatus.query()
         if revision is not None:
@@ -166,37 +154,27 @@
             revisions = [revision]
         else:
             assert pull_request is not None
-            pull_request = self.__get_pull_request(pull_request)
+            pull_request = PullRequest.guess_instance(pull_request)
             repo = pull_request.org_repo
             q = q.filter(ChangesetStatus.repo == repo)
             q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
             revisions = pull_request.revisions
         cur_statuses = q.all()
 
-        #if statuses exists and last is associated with a closed pull request
-        # we need to check if we can allow this status change
-        if (dont_allow_on_closed_pull_request and cur_statuses
-            and getattr(cur_statuses[0].pull_request, 'status', '')
-                == PullRequest.STATUS_CLOSED):
-            raise StatusChangeOnClosedPullRequestError(
-                'Changing status on closed pull request is not allowed'
-            )
-
-        #update all current statuses with older version
+        # update all current statuses with older version
         for st in cur_statuses:
             st.version += 1
-            self.sa.add(st)
 
         new_statuses = []
         for rev in revisions:
             new_status = ChangesetStatus()
             new_status.version = 0 # default
-            new_status.author = self._get_user(user)
-            new_status.repo = self._get_repo(repo)
+            new_status.author = User.guess_instance(user)
+            new_status.repo = Repository.guess_instance(repo)
             new_status.status = status
             new_status.comment = comment
             new_status.revision = rev
             new_status.pull_request = pull_request
             new_statuses.append(new_status)
-            self.sa.add(new_status)
+            Session().add(new_status)
         return new_statuses
--- a/kallithea/model/comment.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/comment.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,39 +27,35 @@
 
 import logging
 
-from pylons.i18n.translation import _
+from tg.i18n import ugettext as _
 from collections import defaultdict
 
 from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
 from kallithea.lib import helpers as h
-from kallithea.model import BaseModel
 from kallithea.model.db import ChangesetComment, User, \
-    Notification, PullRequest
+    PullRequest, Repository
 from kallithea.model.notification import NotificationModel
 from kallithea.model.meta import Session
 
 log = logging.getLogger(__name__)
 
 
-class ChangesetCommentsModel(BaseModel):
-
-    cls = ChangesetComment
-
-    def __get_changeset_comment(self, changeset_comment):
-        return self._get_instance(ChangesetComment, changeset_comment)
-
-    def __get_pull_request(self, pull_request):
-        return self._get_instance(PullRequest, pull_request)
+def _list_changeset_commenters(revision):
+    return (Session().query(User)
+        .join(ChangesetComment.author)
+        .filter(ChangesetComment.revision == revision)
+        .all())
 
-    def _extract_mentions(self, s):
-        user_objects = []
-        for username in extract_mentioned_users(s):
-            user_obj = User.get_by_username(username, case_insensitive=True)
-            if user_obj:
-                user_objects.append(user_obj)
-        return user_objects
+def _list_pull_request_commenters(pull_request):
+    return (Session().query(User)
+        .join(ChangesetComment.author)
+        .filter(ChangesetComment.pull_request_id == pull_request.pull_request_id)
+        .all())
 
-    def _get_notification_data(self, repo, comment, user, comment_text,
+
+class ChangesetCommentsModel(object):
+
+    def _get_notification_data(self, repo, comment, author, comment_text,
                                line_no=None, revision=None, pull_request=None,
                                status_change=None, closing_pr=False):
         """
@@ -71,9 +67,9 @@
         if line_no:
             line = _('on line %s') % line_no
 
-        #changeset
+        # changeset
         if revision:
-            notification_type = Notification.TYPE_CHANGESET_COMMENT
+            notification_type = NotificationModel.TYPE_CHANGESET_COMMENT
             cs = repo.scm_instance.get_changeset(revision)
             desc = cs.short_id
 
@@ -91,31 +87,37 @@
                           comment_url)
             )
             # get the current participants of this changeset
-            recipients = ChangesetComment.get_users(revision=revision)
+            recipients = _list_changeset_commenters(revision)
             # add changeset author if it's known locally
             cs_author = User.get_from_cs_author(cs.author)
             if not cs_author:
-                #use repo owner if we cannot extract the author correctly
-                cs_author = repo.user
-            recipients += [cs_author]
+                # use repo owner if we cannot extract the author correctly
+                # FIXME: just use committer name even if not a user
+                cs_author = repo.owner
+            recipients.append(cs_author)
+
             email_kwargs = {
                 'status_change': status_change,
-                'cs_comment_user': user.full_name_and_username,
+                'cs_comment_user': author.full_name_and_username,
                 'cs_target_repo': h.canonical_url('summary_home', repo_name=repo.repo_name),
                 'cs_comment_url': comment_url,
+                'cs_url': h.canonical_url('changeset_home', repo_name=repo.repo_name, revision=revision),
                 'raw_id': revision,
                 'message': cs.message,
+                'message_short': h.shorter(cs.message, 50, firstline=True),
+                'cs_author': cs_author,
                 'repo_name': repo.repo_name,
                 'short_id': h.short_id(revision),
                 'branch': cs.branch,
-                'comment_username': user.username,
+                'comment_username': author.username,
                 'threading': threading,
             }
-        #pull request
+        # pull request
         elif pull_request:
-            notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
+            notification_type = NotificationModel.TYPE_PULL_REQUEST_COMMENT
             desc = comment.pull_request.title
             _org_ref_type, org_ref_name, _org_rev = comment.pull_request.org_ref.split(':')
+            _other_ref_type, other_ref_name, _other_rev = comment.pull_request.other_ref.split(':')
             threading = ['%s-pr-%s@%s' % (pull_request.other_repo.repo_name,
                                           pull_request.pull_request_id,
                                           h.canonical_hostname())]
@@ -126,40 +128,43 @@
             comment_url = pull_request.url(canonical=True,
                 anchor='comment-%s' % comment.comment_id)
             subj = safe_unicode(
-                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' % \
+                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' %
                           {'desc': desc,
                            'pr_nice_id': comment.pull_request.nice_id(),
                            'line': line},
                           comment_url)
             )
             # get the current participants of this pull request
-            recipients = ChangesetComment.get_users(pull_request_id=
-                                                pull_request.pull_request_id)
-            # add pull request author
-            recipients += [pull_request.owner]
+            recipients = _list_pull_request_commenters(pull_request)
+            recipients.append(pull_request.owner)
+            recipients += pull_request.get_reviewer_users()
 
-            # add the reviewers to notification
-            recipients += [x.user for x in pull_request.reviewers]
-
-            #set some variables for email notification
+            # set some variables for email notification
             email_kwargs = {
                 'pr_title': pull_request.title,
+                'pr_title_short': h.shorter(pull_request.title, 50),
                 'pr_nice_id': pull_request.nice_id(),
                 'status_change': status_change,
                 'closing_pr': closing_pr,
                 'pr_comment_url': comment_url,
-                'pr_comment_user': user.full_name_and_username,
+                'pr_url': pull_request.url(canonical=True),
+                'pr_comment_user': author.full_name_and_username,
                 'pr_target_repo': h.canonical_url('summary_home',
                                    repo_name=pull_request.other_repo.repo_name),
+                'pr_target_branch': other_ref_name,
+                'pr_source_repo': h.canonical_url('summary_home',
+                                   repo_name=pull_request.org_repo.repo_name),
+                'pr_source_branch': org_ref_name,
+                'pr_owner': pull_request.owner,
+                'pr_owner_username': pull_request.owner.username,
                 'repo_name': pull_request.other_repo.repo_name,
-                'ref': org_ref_name,
-                'comment_username': user.username,
+                'comment_username': author.username,
                 'threading': threading,
             }
 
         return subj, body, recipients, notification_type, email_kwargs
 
-    def create(self, text, repo, user, revision=None, pull_request=None,
+    def create(self, text, repo, author, revision=None, pull_request=None,
                f_path=None, line_no=None, status_change=None, closing_pr=False,
                send_email=True):
         """
@@ -172,11 +177,11 @@
             log.warning('Missing text for comment, skipping...')
             return None
 
-        repo = self._get_repo(repo)
-        user = self._get_user(user)
+        repo = Repository.guess_instance(repo)
+        author = User.guess_instance(author)
         comment = ChangesetComment()
         comment.repo = repo
-        comment.author = user
+        comment.author = author
         comment.text = text
         comment.f_path = f_path
         comment.line_no = line_no
@@ -184,7 +189,7 @@
         if revision is not None:
             comment.revision = revision
         elif pull_request is not None:
-            pull_request = self.__get_pull_request(pull_request)
+            pull_request = PullRequest.guess_instance(pull_request)
             comment.pull_request = pull_request
         else:
             raise Exception('Please specify revision or pull_request_id')
@@ -195,7 +200,7 @@
         if send_email:
             (subj, body, recipients, notification_type,
              email_kwargs) = self._get_notification_data(
-                                repo, comment, user,
+                                repo, comment, author,
                                 comment_text=text,
                                 line_no=line_no,
                                 revision=revision,
@@ -205,18 +210,18 @@
             email_kwargs['is_mention'] = False
             # create notification objects, and emails
             NotificationModel().create(
-                created_by=user, subject=subj, body=body,
+                created_by=author, subject=subj, body=body,
                 recipients=recipients, type_=notification_type,
                 email_kwargs=email_kwargs,
             )
 
-            mention_recipients = set(self._extract_mentions(body))\
-                                    .difference(recipients)
+            mention_recipients = extract_mentioned_users(body).difference(recipients)
             if mention_recipients:
                 email_kwargs['is_mention'] = True
                 subj = _('[Mention]') + ' ' + subj
+                # FIXME: this subject is wrong and unused!
                 NotificationModel().create(
-                    created_by=user, subject=subj, body=body,
+                    created_by=author, subject=subj, body=body,
                     recipients=mention_recipients,
                     type_=notification_type,
                     email_kwargs=email_kwargs
@@ -225,7 +230,7 @@
         return comment
 
     def delete(self, comment):
-        comment = self.__get_changeset_comment(comment)
+        comment = ChangesetComment.guess_instance(comment)
         Session().delete(comment)
 
         return comment
@@ -239,38 +244,59 @@
         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):
+    def get_inline_comments(self, repo_id, revision=None, pull_request=None,
+                f_path=None, line_no=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)
+                                      inline=True, f_path=f_path, line_no=line_no)
 
         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):
+    def _get_comments(self, repo_id, revision=None, pull_request=None,
+                inline=False, f_path=None, line_no=None):
         """
         Gets comments for either revision or pull_request_id, either inline or general.
+        If a file path and optionally line number are given, return only the matching inline comments.
         """
+        if f_path is None and line_no is not None:
+            raise Exception("line_no only makes sense if f_path is given.")
+
+        if inline is None and f_path is not None:
+            raise Exception("f_path only makes sense for inline comments.")
+
         q = Session().query(ChangesetComment)
 
         if inline:
-            q = q.filter(ChangesetComment.line_no != None)\
-                .filter(ChangesetComment.f_path != None)
+            if f_path is not None:
+                # inline comments for a given file...
+                q = q.filter(ChangesetComment.f_path == f_path)
+                if line_no is None:
+                    # ... on any line
+                    q = q.filter(ChangesetComment.line_no != None)
+                else:
+                    # ... on specific line
+                    q = q.filter(ChangesetComment.line_no == line_no)
+            else:
+                # all inline comments
+                q = q.filter(ChangesetComment.line_no != None) \
+                    .filter(ChangesetComment.f_path != None)
         else:
-            q = q.filter(ChangesetComment.line_no == None)\
+            # all general comments
+            q = q.filter(ChangesetComment.line_no == None) \
                 .filter(ChangesetComment.f_path == None)
 
-        if revision:
-            q = q.filter(ChangesetComment.revision == revision)\
+        if revision is not None:
+            q = q.filter(ChangesetComment.revision == revision) \
                 .filter(ChangesetComment.repo_id == repo_id)
-        elif pull_request:
-            pull_request = self.__get_pull_request(pull_request)
+        elif pull_request is not None:
+            pull_request = PullRequest.guess_instance(pull_request)
             q = q.filter(ChangesetComment.pull_request == pull_request)
         else:
             raise Exception('Please specify either revision or pull_request')
--- a/kallithea/model/db.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/db.py	Sun Mar 31 21:28:56 2019 +0200
@@ -41,9 +41,8 @@
 from beaker.cache import cache_region, region_invalidate
 from webob.exc import HTTPNotFound
 
-from pylons.i18n.translation import lazy_ugettext as _
-
-from kallithea import DB_PREFIX
+from tg.i18n import lazy_ugettext as _
+
 from kallithea.lib.exceptions import DefaultUserException
 from kallithea.lib.vcs import get_backend
 from kallithea.lib.vcs.utils.helpers import get_scm
@@ -68,7 +67,7 @@
 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
 
 
-class BaseModel(object):
+class BaseDbModel(object):
     """
     Base Model for all classes
     """
@@ -101,10 +100,10 @@
         """return list with keys and values tuples corresponding
         to this model data """
 
-        l = []
-        for k in self._get_keys():
-            l.append((k, getattr(self, k),))
-        return l
+        return [
+            (k, getattr(self, k))
+            for k in self._get_keys()
+        ]
 
     def populate_obj(self, populate_dict):
         """populate model with data from given populate_dict"""
@@ -123,6 +122,32 @@
             return cls.query().get(id_)
 
     @classmethod
+    def guess_instance(cls, value, callback=None):
+        """Haphazardly attempt to convert `value` to a `cls` instance.
+
+        If `value` is None or already a `cls` instance, return it. If `value`
+        is a number (or looks like one if you squint just right), assume it's
+        a database primary key and let SQLAlchemy sort things out. Otherwise,
+        fall back to resolving it using `callback` (if specified); this could
+        e.g. be a function that looks up instances by name (though that won't
+        work if the name begins with a digit). Otherwise, raise Exception.
+        """
+
+        if value is None:
+            return None
+        if isinstance(value, cls):
+            return value
+        if isinstance(value, (int, long)) or safe_str(value).isdigit():
+            return cls.get(value)
+        if callback is not None:
+            return callback(value)
+
+        raise Exception(
+            'given object must be int, long or Instance of %s '
+            'got %s, no callback provided' % (cls, type(value))
+        )
+
+    @classmethod
     def get_or_404(cls, id_):
         try:
             id_ = int(id_)
@@ -135,15 +160,6 @@
         return res
 
     @classmethod
-    def getAll(cls):
-        # deprecated and left for backward compatibility
-        return cls.get_all()
-
-    @classmethod
-    def get_all(cls):
-        return cls.query().all()
-
-    @classmethod
     def delete(cls, id_):
         obj = cls.query().get(id_)
         Session().delete(obj)
@@ -158,13 +174,16 @@
         return '<DB:%s>' % (self.__class__.__name__)
 
 
-class Setting(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'settings'
-
+_table_args_default_dict = {'extend_existing': True,
+                            'mysql_engine': 'InnoDB',
+                            'mysql_charset': 'utf8',
+                            'sqlite_autoincrement': True,
+                           }
+
+class Setting(Base, BaseDbModel):
+    __tablename__ = 'settings'
     __table_args__ = (
-        UniqueConstraint('app_settings_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
 
     SETTINGS_TYPES = {
@@ -176,10 +195,10 @@
     }
     DEFAULT_UPDATE_URL = ''
 
-    app_settings_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    app_settings_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False), nullable=True, unique=None, default=None)
-    _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    app_settings_id = Column(Integer(), primary_key=True)
+    app_settings_name = Column(String(255), nullable=False, unique=True)
+    _app_settings_value = Column("app_settings_value", Unicode(4096), nullable=False)
+    _app_settings_type = Column("app_settings_type", String(255), nullable=True) # FIXME: not nullable?
 
     def __init__(self, key='', val='', type='unicode'):
         self.app_settings_name = key
@@ -226,7 +245,7 @@
 
     @classmethod
     def get_by_name(cls, key):
-        return cls.query()\
+        return cls.query() \
             .filter(cls.app_settings_name == key).scalar()
 
     @classmethod
@@ -252,6 +271,7 @@
             val = Optional.extract(val)
             type = Optional.extract(type)
             res = cls(key, val, type)
+            Session().add(res)
         else:
             res.app_settings_name = key
             if not isinstance(val, Optional):
@@ -280,13 +300,8 @@
         return settings
 
     @classmethod
-    def get_auth_plugins(cls, cache=False):
-        auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
-        return auth_plugins
-
-    @classmethod
     def get_auth_settings(cls, cache=False):
-        ret = cls.query()\
+        ret = cls.query() \
                 .filter(cls.app_settings_name.startswith('auth_')).all()
         fd = {}
         for row in ret:
@@ -295,7 +310,7 @@
 
     @classmethod
     def get_default_repo_settings(cls, cache=False, strip_prefix=False):
-        ret = cls.query()\
+        ret = cls.query() \
                 .filter(cls.app_settings_name.startswith('default_')).all()
         fd = {}
         for row in ret:
@@ -324,99 +339,104 @@
         return info
 
 
-class Ui(Base, BaseModel):
-    __tablename__ = DB_PREFIX + 'ui'
+class Ui(Base, BaseDbModel):
+    __tablename__ = 'ui'
     __table_args__ = (
+        # FIXME: ui_key as key is wrong and should be removed when the corresponding
+        # Ui.get_by_key has been replaced by the composite key
         UniqueConstraint('ui_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        UniqueConstraint('ui_section', 'ui_key'),
+        _table_args_default_dict,
     )
 
     HOOK_UPDATE = 'changegroup.update'
     HOOK_REPO_SIZE = 'changegroup.repo_size'
-    HOOK_PUSH = 'changegroup.push_logger'
-    HOOK_PRE_PUSH = 'prechangegroup.pre_push'
-    HOOK_PULL = 'outgoing.pull_logger'
-    HOOK_PRE_PULL = 'preoutgoing.pre_pull'
-
-    ui_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    ui_section = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_key = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_value = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    ui_active = Column(Boolean(), nullable=True, unique=None, default=True)
-
-    # def __init__(self, section='', key='', value=''):
-    #     self.ui_section = section
-    #     self.ui_key = key
-    #     self.ui_value = value
+    HOOK_PUSH_LOG = 'changegroup.push_logger'
+    HOOK_PUSH_LOCK = 'prechangegroup.push_lock_handling'
+    HOOK_PULL_LOG = 'outgoing.pull_logger'
+    HOOK_PULL_LOCK = 'preoutgoing.pull_lock_handling'
+
+    ui_id = Column(Integer(), primary_key=True)
+    ui_section = Column(String(255), nullable=False)
+    ui_key = Column(String(255), nullable=False)
+    ui_value = Column(String(255), nullable=True) # FIXME: not nullable?
+    ui_active = Column(Boolean(), nullable=False, default=True)
 
     @classmethod
-    def get_by_key(cls, key):
-        return cls.query().filter(cls.ui_key == key).scalar()
+    def get_by_key(cls, section, key):
+        """ Return specified Ui object, or None if not found. """
+        return cls.query().filter_by(ui_section=section, ui_key=key).scalar()
+
+    @classmethod
+    def get_or_create(cls, section, key):
+        """ Return specified Ui object, creating it if necessary. """
+        setting = cls.get_by_key(section, key)
+        if setting is None:
+            setting = cls(ui_section=section, ui_key=key)
+            Session().add(setting)
+        return setting
 
     @classmethod
     def get_builtin_hooks(cls):
         q = cls.query()
         q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                     cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                     cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+                                     cls.HOOK_PUSH_LOG, cls.HOOK_PUSH_LOCK,
+                                     cls.HOOK_PULL_LOG, cls.HOOK_PULL_LOCK]))
+        q = q.filter(cls.ui_section == 'hooks')
         return q.all()
 
     @classmethod
     def get_custom_hooks(cls):
         q = cls.query()
         q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
-                                      cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
-                                      cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
+                                      cls.HOOK_PUSH_LOG, cls.HOOK_PUSH_LOCK,
+                                      cls.HOOK_PULL_LOG, cls.HOOK_PULL_LOCK]))
         q = q.filter(cls.ui_section == 'hooks')
         return q.all()
 
     @classmethod
     def get_repos_location(cls):
-        return cls.get_by_key('/').ui_value
+        return cls.get_by_key('paths', '/').ui_value
 
     @classmethod
     def create_or_update_hook(cls, key, val):
-        new_ui = cls.get_by_key(key) or cls()
-        new_ui.ui_section = 'hooks'
+        new_ui = cls.get_or_create('hooks', key)
         new_ui.ui_active = True
-        new_ui.ui_key = key
         new_ui.ui_value = val
 
-        Session().add(new_ui)
-
     def __repr__(self):
         return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
                                     self.ui_key, self.ui_value)
 
 
-class User(Base, BaseModel):
+class User(Base, BaseDbModel):
     __tablename__ = 'users'
     __table_args__ = (
-        UniqueConstraint('username'), UniqueConstraint('email'),
         Index('u_username_idx', 'username'),
         Index('u_email_idx', 'email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
+
     DEFAULT_USER = 'default'
     DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
-
-    user_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    password = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    active = Column(Boolean(), nullable=True, unique=None, default=True)
-    admin = Column(Boolean(), nullable=True, unique=None, default=False)
-    name = Column("firstname", String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    lastname = Column(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(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)
+    # The name of the default auth type in extern_type, 'internal' lives in auth_internal.py
+    DEFAULT_AUTH_TYPE = 'internal'
+
+    user_id = Column(Integer(), primary_key=True)
+    username = Column(String(255), nullable=False, unique=True)
+    password = Column(String(255), nullable=False)
+    active = Column(Boolean(), nullable=False, default=True)
+    admin = Column(Boolean(), nullable=False, default=False)
+    name = Column("firstname", Unicode(255), nullable=False)
+    lastname = Column(Unicode(255), nullable=False)
+    _email = Column("email", String(255), nullable=True, unique=True) # FIXME: not nullable?
+    last_login = Column(DateTime(timezone=False), nullable=True)
+    extern_type = Column(String(255), nullable=True) # FIXME: not nullable?
+    extern_name = Column(String(255), nullable=True) # FIXME: not nullable?
+    api_key = Column(String(255), nullable=False)
+    inherit_default_permissions = Column(Boolean(), nullable=False, 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_data = Column("user_data", LargeBinary(), nullable=True)  # JSON data # FIXME: not nullable?
 
     user_log = relationship('UserLog')
     user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
@@ -432,17 +452,13 @@
 
     group_member = relationship('UserGroupMember', cascade='all')
 
-    notifications = relationship('UserNotification', cascade='all')
-    # notifications assigned to this user
-    user_created_notifications = relationship('Notification', cascade='all')
     # comments created by this user
     user_comments = relationship('ChangesetComment', cascade='all')
-    #extra emails for this user
+    # extra emails for this user
     user_emails = relationship('UserEmailMap', cascade='all')
-    #extra API keys
+    # extra API keys
     user_api_keys = relationship('UserApiKeys', cascade='all')
 
-
     @hybrid_property
     def email(self):
         return self._email
@@ -458,12 +474,12 @@
 
     @property
     def emails(self):
-        other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
+        other = UserEmailMap.query().filter(UserEmailMap.user == self).all()
         return [self.email] + [x.email for x in other]
 
     @property
     def api_keys(self):
-        other = UserApiKeys.query().filter(UserApiKeys.user==self).all()
+        other = UserApiKeys.query().filter(UserApiKeys.user == self).all()
         return [self.api_key] + [x.api_key for x in other]
 
     @property
@@ -505,13 +521,9 @@
     def is_admin(self):
         return self.admin
 
-    @property
-    def AuthUser(self):
-        """
-        Returns instance of AuthUser for this user
-        """
-        from kallithea.lib.auth import AuthUser
-        return AuthUser(dbuser=self)
+    @hybrid_property
+    def is_default_user(self):
+        return self.username == User.DEFAULT_USER
 
     @hybrid_property
     def user_data(self):
@@ -535,21 +547,38 @@
                                       self.user_id, self.username)
 
     @classmethod
+    def guess_instance(cls, value):
+        return super(User, cls).guess_instance(value, User.get_by_username)
+
+    @classmethod
     def get_or_404(cls, id_, allow_default=True):
         '''
-        Overridden version of BaseModel.get_or_404, with an extra check on
+        Overridden version of BaseDbModel.get_or_404, with an extra check on
         the default user.
         '''
         user = super(User, cls).get_or_404(id_)
-        if allow_default == False:
-            if user.username == User.DEFAULT_USER:
-                raise DefaultUserException
+        if not allow_default and user.is_default_user:
+            raise DefaultUserException()
         return user
 
     @classmethod
+    def get_by_username_or_email(cls, username_or_email, case_insensitive=False, cache=False):
+        """
+        For anything that looks like an email address, look up by the email address (matching
+        case insensitively).
+        For anything else, try to look up by the user name.
+
+        This assumes no normal username can have '@' symbol.
+        """
+        if '@' in username_or_email:
+            return User.get_by_email(username_or_email, cache=cache)
+        else:
+            return User.get_by_username(username_or_email, case_insensitive=case_insensitive, cache=cache)
+
+    @classmethod
     def get_by_username(cls, username, case_insensitive=False, cache=False):
         if case_insensitive:
-            q = cls.query().filter(cls.username.ilike(username))
+            q = cls.query().filter(func.lower(cls.username) == func.lower(username))
         else:
             q = cls.query().filter(cls.username == username)
 
@@ -574,22 +603,15 @@
         res = q.scalar()
 
         if fallback and not res:
-            #fallback to additional keys
-            _res = UserApiKeys.query()\
-                .filter(UserApiKeys.api_key == api_key)\
-                .filter(or_(UserApiKeys.expires == -1,
-                            UserApiKeys.expires >= time.time()))\
-                .first()
+            # fallback to additional keys
+            _res = UserApiKeys.query().filter_by(api_key=api_key, is_expired=False).first()
             if _res:
                 res = _res.user
         return res
 
     @classmethod
-    def get_by_email(cls, email, case_insensitive=False, cache=False):
-        if case_insensitive:
-            q = cls.query().filter(cls.email.ilike(email))
-        else:
-            q = cls.query().filter(cls.email == email)
+    def get_by_email(cls, email, cache=False):
+        q = cls.query().filter(func.lower(cls.email) == func.lower(email))
 
         if cache:
             q = q.options(FromCache("sql_cache_short",
@@ -599,10 +621,7 @@
         if ret is None:
             q = UserEmailMap.query()
             # try fetching in alternate email map
-            if case_insensitive:
-                q = q.filter(UserEmailMap.email.ilike(email))
-            else:
-                q = q.filter(UserEmailMap.email == email)
+            q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
             q = q.options(joinedload(UserEmailMap.user))
             if cache:
                 q = q.options(FromCache("sql_cache_short",
@@ -622,7 +641,7 @@
         # Valid email in the attribute passed, see if they're in the system
         _email = email(author)
         if _email:
-            user = cls.get_by_email(_email, case_insensitive=True)
+            user = cls.get_by_email(_email)
             if user is not None:
                 return user
         # Maybe we can match by username?
@@ -634,7 +653,6 @@
     def update_lastlogin(self):
         """Update user lastlogin"""
         self.last_login = datetime.datetime.now()
-        Session().add(self)
         log.debug('updated user %s lastlogin', self.username)
 
     @classmethod
@@ -688,46 +706,40 @@
         return data
 
 
-class UserApiKeys(Base, BaseModel):
+class UserApiKeys(Base, BaseDbModel):
     __tablename__ = 'user_api_keys'
     __table_args__ = (
         Index('uak_api_key_idx', 'api_key'),
         Index('uak_api_key_expires_idx', 'api_key', 'expires'),
-        UniqueConstraint('api_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
     __mapper_args__ = {}
 
-    user_api_key_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    api_key = Column(String(255, convert_unicode=False), nullable=False, unique=True)
-    description = Column(UnicodeText(1024))
+    user_api_key_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    api_key = Column(String(255), nullable=False, unique=True)
+    description = Column(UnicodeText(), nullable=False)
     expires = Column(Float(53), nullable=False)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     user = relationship('User')
 
-    @property
-    def expired(self):
-        if self.expires == -1:
-            return False
-        return time.time() > self.expires
-
-
-class UserEmailMap(Base, BaseModel):
+    @hybrid_property
+    def is_expired(self):
+        return (self.expires != -1) & (time.time() > self.expires)
+
+
+class UserEmailMap(Base, BaseDbModel):
     __tablename__ = 'user_email_map'
     __table_args__ = (
         Index('uem_email_idx', 'email'),
-        UniqueConstraint('email'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
     __mapper_args__ = {}
 
-    email_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    _email = Column("email", String(255, convert_unicode=False), nullable=True, unique=False, default=None)
+    email_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    _email = Column("email", String(255), nullable=False, unique=True)
     user = relationship('User')
 
     @validates('_email')
@@ -747,19 +759,18 @@
         self._email = val.lower() if val else None
 
 
-class UserIpMap(Base, BaseModel):
+class UserIpMap(Base, BaseDbModel):
     __tablename__ = 'user_ip_map'
     __table_args__ = (
         UniqueConstraint('user_id', 'ip_addr'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
     __mapper_args__ = {}
 
-    ip_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    ip_addr = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    active = Column(Boolean(), nullable=True, unique=None, default=True)
+    ip_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    ip_addr = Column(String(255), nullable=False)
+    active = Column(Boolean(), nullable=False, default=True)
     user = relationship('User')
 
     @classmethod
@@ -778,20 +789,21 @@
         return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
                                             self.user_id, self.ip_addr)
 
-class UserLog(Base, BaseModel):
+
+class UserLog(Base, BaseDbModel):
     __tablename__ = 'user_logs'
     __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
-    user_log_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
-    username = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+
+    user_log_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
+    username = Column(String(255), nullable=False)
     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)
+    repository_name = Column(Unicode(255), nullable=False)
+    user_ip = Column(String(255), nullable=True)
+    action = Column(UnicodeText(), nullable=False)
+    action_date = Column(DateTime(timezone=False), nullable=False)
 
     def __unicode__(self):
         return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
@@ -806,21 +818,20 @@
     repository = relationship('Repository', cascade='')
 
 
-class UserGroup(Base, BaseModel):
+class UserGroup(Base, BaseDbModel):
     __tablename__ = 'users_groups'
     __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
 
-    users_group_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    users_group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    user_group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    users_group_active = Column(Boolean(), nullable=True, unique=None, default=None)
-    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    users_group_id = Column(Integer(), primary_key=True)
+    users_group_name = Column(Unicode(255), nullable=False, unique=True)
+    user_group_description = Column(Unicode(10000), nullable=True) # FIXME: not nullable?
+    users_group_active = Column(Boolean(), nullable=False)
+    inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, default=True)
+    owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data
+    _group_data = Column("group_data", LargeBinary(), nullable=True)  # JSON data # FIXME: not nullable?
 
     members = relationship('UserGroupMember', cascade="all, delete-orphan")
     users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
@@ -829,7 +840,7 @@
     user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
     user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
 
-    user = relationship('User')
+    owner = relationship('User')
 
     @hybrid_property
     def group_data(self):
@@ -854,10 +865,14 @@
                                       self.users_group_name)
 
     @classmethod
+    def guess_instance(cls, value):
+        return super(UserGroup, cls).guess_instance(value, UserGroup.get_by_group_name)
+
+    @classmethod
     def get_by_group_name(cls, group_name, cache=False,
                           case_insensitive=False):
         if case_insensitive:
-            q = cls.query().filter(cls.users_group_name.ilike(group_name))
+            q = cls.query().filter(func.lower(cls.users_group_name) == func.lower(group_name))
         else:
             q = cls.query().filter(cls.users_group_name == group_name)
         if cache:
@@ -884,28 +899,26 @@
             group_name=user_group.users_group_name,
             group_description=user_group.user_group_description,
             active=user_group.users_group_active,
-            owner=user_group.user.username,
+            owner=user_group.owner.username,
         )
         if with_members:
-            members = []
-            for user in user_group.members:
-                user = user.user
-                members.append(user.get_api_data())
-            data['members'] = members
+            data['members'] = [
+                ugm.user.get_api_data()
+                for ugm in user_group.members
+            ]
 
         return data
 
 
-class UserGroupMember(Base, BaseModel):
+class UserGroupMember(Base, BaseDbModel):
     __tablename__ = 'users_groups_members'
     __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
 
-    users_group_member_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
+    users_group_member_id = Column(Integer(), primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
 
     user = relationship('User')
     users_group = relationship('UserGroup')
@@ -915,22 +928,22 @@
         self.user_id = u_id
 
 
-class RepositoryField(Base, BaseModel):
+class RepositoryField(Base, BaseDbModel):
     __tablename__ = 'repositories_fields'
     __table_args__ = (
         UniqueConstraint('repository_id', 'field_key'),  # no-multi field
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
+
     PREFIX = 'ex_'  # prefix used in form to not conflict with already existing fields
 
-    repo_field_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
-    field_key = Column(String(250, convert_unicode=False))
-    field_label = Column(String(1024, convert_unicode=False), nullable=False)
-    field_value = Column(String(10000, convert_unicode=False), nullable=False)
-    field_desc = Column(String(1024, convert_unicode=False), nullable=False)
-    field_type = Column(String(255), nullable=False, unique=None)
+    repo_field_id = Column(Integer(), primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
+    field_key = Column(String(250), nullable=False)
+    field_label = Column(String(1024), nullable=False)
+    field_value = Column(String(10000), nullable=False)
+    field_desc = Column(String(1024), nullable=False)
+    field_type = Column(String(255), nullable=False)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     repository = relationship('Repository')
@@ -947,49 +960,48 @@
 
     @classmethod
     def get_by_key_name(cls, key, repo):
-        row = cls.query()\
-                .filter(cls.repository == repo)\
+        row = cls.query() \
+                .filter(cls.repository == repo) \
                 .filter(cls.field_key == key).scalar()
         return row
 
 
-class Repository(Base, BaseModel):
+class Repository(Base, BaseDbModel):
     __tablename__ = 'repositories'
     __table_args__ = (
-        UniqueConstraint('repo_name'),
         Index('r_repo_name_idx', 'repo_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
+
     DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
     DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
 
-    STATE_CREATED = 'repo_state_created'
-    STATE_PENDING = 'repo_state_pending'
-    STATE_ERROR = 'repo_state_error'
-
-    repo_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    repo_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    repo_state = Column(String(255), nullable=True)
-
-    clone_uri = Column(String(255, convert_unicode=False), nullable=True, unique=False, default=None)
-    repo_type = Column(String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
-    private = Column(Boolean(), nullable=True, unique=None, default=None)
-    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
-    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
-    description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    created_on = Column(DateTime(timezone=False), nullable=False, unique=None, default=datetime.datetime.now)
-    updated_on = Column(DateTime(timezone=False), nullable=False, unique=None, default=datetime.datetime.now)
-    _landing_revision = Column("landing_revision", String(255, convert_unicode=False), nullable=False, unique=False, default=None)
-    enable_locking = Column(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(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')
+    STATE_CREATED = u'repo_state_created'
+    STATE_PENDING = u'repo_state_pending'
+    STATE_ERROR = u'repo_state_error'
+
+    repo_id = Column(Integer(), primary_key=True)
+    repo_name = Column(Unicode(255), nullable=False, unique=True)
+    repo_state = Column(String(255), nullable=False)
+
+    clone_uri = Column(String(255), nullable=True) # FIXME: not nullable?
+    repo_type = Column(String(255), nullable=False)
+    owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    private = Column(Boolean(), nullable=False)
+    enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
+    description = Column(Unicode(10000), nullable=False)
+    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
+    _landing_revision = Column("landing_revision", String(255), nullable=False)
+    enable_locking = Column(Boolean(), nullable=False, default=False)
+    _locked = Column("locked", String(255), nullable=True) # FIXME: not nullable?
+    _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data # FIXME: not nullable?
+
+    fork_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True)
+
+    owner = relationship('User')
     fork = relationship('Repository', remote_side=repo_id)
     group = relationship('RepoGroup')
     repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
@@ -997,7 +1009,7 @@
     stats = relationship('Statistics', cascade='all', uselist=False)
 
     followers = relationship('UserFollowing',
-                             primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
+                             primaryjoin='UserFollowing.follows_repository_id==Repository.repo_id',
                              cascade='all')
     extra_fields = relationship('RepositoryField',
                                 cascade="all, delete-orphan")
@@ -1066,6 +1078,19 @@
             log.error(traceback.format_exc())
 
     @classmethod
+    def query(cls, sorted=False):
+        """Add Repository-specific helpers for common query constructs.
+
+        sorted: if True, apply the default ordering (name, case insensitive).
+        """
+        q = super(Repository, cls).query()
+
+        if sorted:
+            q = q.order_by(func.lower(Repository.repo_name))
+
+        return q
+
+    @classmethod
     def url_sep(cls):
         return URL_SEP
 
@@ -1081,16 +1106,28 @@
         return cls.url_sep().join(repo_name.split(os.sep))
 
     @classmethod
-    def get_by_repo_name(cls, repo_name):
-        q = Session().query(cls).filter(cls.repo_name == repo_name)
-        q = q.options(joinedload(Repository.fork))\
-                .options(joinedload(Repository.user))\
+    def guess_instance(cls, value):
+        return super(Repository, cls).guess_instance(value, Repository.get_by_repo_name)
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name, case_insensitive=False):
+        """Get the repo, defaulting to database case sensitivity.
+        case_insensitive will be slower and should only be specified if necessary."""
+        if case_insensitive:
+            q = Session().query(cls).filter(func.lower(cls.repo_name) == func.lower(repo_name))
+        else:
+            q = Session().query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork)) \
+                .options(joinedload(Repository.owner)) \
                 .options(joinedload(Repository.group))
         return q.scalar()
 
     @classmethod
     def get_by_full_path(cls, repo_full_path):
-        repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
+        base_full_path = os.path.realpath(cls.base_path())
+        repo_full_path = os.path.realpath(repo_full_path)
+        assert repo_full_path.startswith(base_full_path + os.path.sep)
+        repo_name = repo_full_path[len(base_full_path) + 1:]
         repo_name = cls.normalize_repo_name(repo_name)
         return cls.get_by_repo_name(repo_name.strip(URL_SEP))
 
@@ -1105,7 +1142,7 @@
 
         :param cls:
         """
-        q = Session().query(Ui)\
+        q = Session().query(Ui) \
             .filter(Ui.ui_key == cls.url_sep())
         q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return q.one().ui_value
@@ -1131,24 +1168,14 @@
     @property
     def groups_with_parents(self):
         groups = []
-        if self.group is None:
-            return groups
-
-        cur_gr = self.group
-        groups.insert(0, cur_gr)
-        while 1:
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            groups.insert(0, gr)
-
+        group = self.group
+        while group is not None:
+            groups.append(group)
+            group = group.parent_group
+            assert group not in groups, group # avoid recursion on bad db content
+        groups.reverse()
         return groups
 
-    @property
-    def groups_and_repo(self):
-        return self.groups_with_parents, self.just_name, self.repo_name
-
     @LazyProperty
     def repo_path(self):
         """
@@ -1174,9 +1201,9 @@
         """
         Returns associated cache keys for that repo
         """
-        return CacheInvalidation.query()\
-            .filter(CacheInvalidation.cache_args == self.repo_name)\
-            .order_by(CacheInvalidation.cache_key)\
+        return CacheInvalidation.query() \
+            .filter(CacheInvalidation.cache_args == self.repo_name) \
+            .order_by(CacheInvalidation.cache_key) \
             .all()
 
     def get_new_name(self, repo_name):
@@ -1208,10 +1235,11 @@
 
         return is_valid_repo(repo_name, cls.base_path())
 
-    def get_api_data(self):
+    def get_api_data(self, with_revision_names=False,
+                           with_pullrequests=False):
         """
-        Common function for generating repo api data
-
+        Common function for generating repo api data.
+        Optionally, also return tags, branches, bookmarks and PRs.
         """
         repo = self
         data = dict(
@@ -1223,7 +1251,7 @@
             created_on=repo.created_on,
             description=repo.description,
             landing_rev=repo.landing_rev,
-            owner=repo.user.username,
+            owner=repo.owner.username,
             fork_of=repo.fork.repo_name if repo.fork else None,
             enable_statistics=repo.enable_statistics,
             enable_locking=repo.enable_locking,
@@ -1234,6 +1262,15 @@
             locked_date=time_to_datetime(self.locked[1]) \
                 if self.locked[1] else None
         )
+        if with_revision_names:
+            scm_repo = repo.scm_instance_no_cache()
+            data.update(dict(
+                tags=scm_repo.tags,
+                branches=scm_repo.branches,
+                bookmarks=scm_repo.bookmarks,
+            ))
+        if with_pullrequests:
+            data['pull_requests'] = repo.pull_requests_other
         rc_config = Setting.get_app_settings()
         repository_fields = str2bool(rc_config.get('repository_fields'))
         if repository_fields:
@@ -1247,13 +1284,11 @@
         if lock_time is not None:
             lock_time = time.time()
         repo.locked = [user_id, lock_time]
-        Session().add(repo)
         Session().commit()
 
     @classmethod
     def unlock(cls, repo):
         repo.locked = None
-        Session().add(repo)
         Session().commit()
 
     @classmethod
@@ -1291,7 +1326,7 @@
         if not uri_tmpl:
             uri_tmpl = self.DEFAULT_CLONE_URI
             try:
-                from pylons import tmpl_context as c
+                from tg import tmpl_context as c
                 uri_tmpl = c.clone_uri_tmpl
             except AttributeError:
                 # in any case if we call this outside of request context,
@@ -1305,7 +1340,7 @@
 
     def set_state(self, state):
         self.repo_state = state
-        Session().add(self)
+
     #==========================================================================
     # SCM PROPERTIES
     #==========================================================================
@@ -1354,7 +1389,6 @@
                       self.repo_name, cs_cache)
             self.updated_on = last_change
             self.changeset_cache = cs_cache
-            Session().add(self)
             Session().commit()
         else:
             log.debug('changeset_cache for %s already up to date with %s',
@@ -1378,11 +1412,11 @@
 
         :param revisions: filter query by revisions only
         """
-        cmts = ChangesetComment.query()\
+        cmts = ChangesetComment.query() \
             .filter(ChangesetComment.repo == self)
         if revisions is not None:
             if not revisions:
-                return [] # don't use sql 'in' on empty set
+                return {} # don't use sql 'in' on empty set
             cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
         grouped = collections.defaultdict(list)
         for cmt in cmts.all():
@@ -1399,9 +1433,9 @@
         if not revisions:
             return {}
 
-        statuses = ChangesetStatus.query()\
-            .filter(ChangesetStatus.repo == self)\
-            .filter(ChangesetStatus.version == 0)\
+        statuses = ChangesetStatus.query() \
+            .filter(ChangesetStatus.repo == self) \
+            .filter(ChangesetStatus.version == 0) \
             .filter(ChangesetStatus.revision.in_(revisions))
 
         grouped = {}
@@ -1412,7 +1446,8 @@
                 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_nice_id]
+                                      pr_id, pr_repo, pr_nice_id,
+                                      stat.author]
         return grouped
 
     def _repo_size(self):
@@ -1430,41 +1465,38 @@
         """
         CacheInvalidation.set_invalidate(self.repo_name)
 
-    def scm_instance_no_cache(self):
-        return self.__get_instance()
+    _scm_instance = None
 
     @property
     def scm_instance(self):
-        import kallithea
-        full_cache = str2bool(kallithea.CONFIG.get('vcs_full_cache'))
-        if full_cache:
-            return self.scm_instance_cached()
-        return self.__get_instance()
+        if self._scm_instance is None:
+            self._scm_instance = self.scm_instance_cached()
+        return self._scm_instance
 
     def scm_instance_cached(self, valid_cache_keys=None):
-        @cache_region('long_term')
-        def _c(repo_name):
-            return self.__get_instance()
+        @cache_region('long_term', 'scm_instance_cached')
+        def _c(repo_name): # repo_name is just for the cache key
+            log.debug('Creating new %s scm_instance and populating cache', repo_name)
+            return self.scm_instance_no_cache()
         rn = self.repo_name
 
         valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
         if not valid:
             log.debug('Cache for %s invalidated, getting new object', rn)
-            region_invalidate(_c, None, rn)
+            region_invalidate(_c, None, 'scm_instance_cached', rn)
         else:
-            log.debug('Getting scm_instance of %s from cache', rn)
+            log.debug('Trying to get scm_instance of %s from cache', rn)
         return _c(rn)
 
-    def __get_instance(self):
-        repo_full_path = self.repo_full_path
-
-        alias = get_scm(safe_str(repo_full_path))[0]
+    def scm_instance_no_cache(self):
+        repo_full_path = safe_str(self.repo_full_path)
+        alias = get_scm(repo_full_path)[0]
         log.debug('Creating instance of %s repository from %s',
-                  alias, repo_full_path)
+                  alias, self.repo_full_path)
         backend = get_backend(alias)
 
         if alias == 'hg':
-            repo = backend(safe_str(repo_full_path), create=False,
+            repo = backend(repo_full_path, create=False,
                            baseui=self._ui)
         else:
             repo = backend(repo_full_path, create=False)
@@ -1472,31 +1504,47 @@
         return repo
 
     def __json__(self):
-        return dict(landing_rev = self.landing_rev)
-
-class RepoGroup(Base, BaseModel):
+        return dict(
+            repo_id=self.repo_id,
+            repo_name=self.repo_name,
+            landing_rev=self.landing_rev,
+        )
+
+
+class RepoGroup(Base, BaseDbModel):
     __tablename__ = 'groups'
     __table_args__ = (
-        UniqueConstraint('group_name', 'group_parent_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
-    __mapper_args__ = {'order_by': 'group_name'}
+    __mapper_args__ = {'order_by': 'group_name'} # TODO: Deprecated as of SQLAlchemy 1.1.
 
     SEP = ' &raquo; '
 
-    group_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    group_name = Column(String(255, convert_unicode=False), nullable=False, unique=True, default=None)
-    group_parent_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
-    group_description = Column(String(10000, convert_unicode=False), nullable=True, unique=None, default=None)
-    enable_locking = Column(Boolean(), nullable=False, unique=None, default=False)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    group_id = Column(Integer(), primary_key=True)
+    group_name = Column(Unicode(255), nullable=False, unique=True) # full path
+    parent_group_id = Column('group_parent_id', Integer(), ForeignKey('groups.group_id'), nullable=True)
+    group_description = Column(Unicode(10000), nullable=False)
+    enable_locking = Column(Boolean(), nullable=False, default=False)
+    owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
     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')
     parent_group = relationship('RepoGroup', remote_side=group_id)
-    user = relationship('User')
+    owner = relationship('User')
+
+    @classmethod
+    def query(cls, sorted=False):
+        """Add RepoGroup-specific helpers for common query constructs.
+
+        sorted: if True, apply the default ordering (name, case insensitive).
+        """
+        q = super(RepoGroup, cls).query()
+
+        if sorted:
+            q = q.order_by(func.lower(RepoGroup.group_name))
+
+        return q
 
     def __init__(self, group_name='', parent_group=None):
         self.group_name = group_name
@@ -1525,12 +1573,17 @@
         return URL_SEP
 
     @classmethod
+    def guess_instance(cls, value):
+        return super(RepoGroup, cls).guess_instance(value, RepoGroup.get_by_group_name)
+
+    @classmethod
     def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
+        group_name = group_name.rstrip('/')
         if case_insensitive:
-            gr = cls.query()\
-                .filter(cls.group_name.ilike(group_name))
+            gr = cls.query() \
+                .filter(func.lower(cls.group_name) == func.lower(group_name))
         else:
-            gr = cls.query()\
+            gr = cls.query() \
                 .filter(cls.group_name == group_name)
         if cache:
             gr = gr.options(FromCache(
@@ -1542,26 +1595,13 @@
 
     @property
     def parents(self):
-        parents_recursion_limit = 10
         groups = []
-        if self.parent_group is None:
-            return groups
-        cur_gr = self.parent_group
-        groups.insert(0, cur_gr)
-        cnt = 0
-        while 1:
-            cnt += 1
-            gr = getattr(cur_gr, 'parent_group', None)
-            cur_gr = cur_gr.parent_group
-            if gr is None:
-                break
-            if cnt == parents_recursion_limit:
-                # this will prevent accidental infinite loops
-                log.error(('more than %s parents found for group %s, stopping '
-                           'recursive parent fetching' % (parents_recursion_limit, self)))
-                break
-
-            groups.insert(0, gr)
+        group = self.parent_group
+        while group is not None:
+            groups.append(group)
+            group = group.parent_group
+            assert group not in groups, group # avoid recursion on bad db content
+        groups.reverse()
         return groups
 
     @property
@@ -1582,9 +1622,7 @@
 
     @property
     def repositories(self):
-        return Repository.query()\
-                .filter(Repository.group == self)\
-                .order_by(Repository.repo_name)
+        return Repository.query(sorted=True).filter_by(group=self)
 
     @property
     def repositories_recursive_count(self):
@@ -1649,18 +1687,18 @@
             group_description=group.group_description,
             parent_group=group.parent_group.group_name if group.parent_group else None,
             repositories=[x.repo_name for x in group.repositories],
-            owner=group.user.username
+            owner=group.owner.username
         )
         return data
 
 
-class Permission(Base, BaseModel):
+class Permission(Base, BaseDbModel):
     __tablename__ = 'permissions'
     __table_args__ = (
         Index('p_perm_name_idx', 'permission_name'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
+
     PERMS = [
         ('hg.admin', _('Kallithea Administrator')),
 
@@ -1702,7 +1740,7 @@
         ('hg.extern_activate.auto', _('Automatic activation of external account')),
     ]
 
-    #definition of system default permissions for DEFAULT user
+    # definition of system default permissions for DEFAULT user
     DEFAULT_USER_PERMISSIONS = [
         'repository.read',
         'group.read',
@@ -1746,9 +1784,8 @@
         'hg.create.repository': 1
     }
 
-    permission_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    permission_name = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
-    permission_longname = Column(String(255, convert_unicode=False), nullable=True, unique=None, default=None)
+    permission_id = Column(Integer(), primary_key=True)
+    permission_name = Column(String(255), nullable=False)
 
     def __unicode__(self):
         return u"<%s('%s:%s')>" % (
@@ -1756,48 +1793,52 @@
         )
 
     @classmethod
+    def guess_instance(cls, value):
+        return super(Permission, cls).guess_instance(value, Permission.get_by_key)
+
+    @classmethod
     def get_by_key(cls, key):
         return cls.query().filter(cls.permission_name == key).scalar()
 
     @classmethod
     def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls)\
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
+        q = Session().query(UserRepoToPerm, Repository, cls) \
+         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
+         .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
          .filter(UserRepoToPerm.user_id == default_user_id)
 
         return q.all()
 
     @classmethod
     def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
+        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
+         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
+         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
          .filter(UserRepoGroupToPerm.user_id == default_user_id)
 
         return q.all()
 
     @classmethod
     def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
+        q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
+         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
+         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
          .filter(UserUserGroupToPerm.user_id == default_user_id)
 
         return q.all()
 
 
-class UserRepoToPerm(Base, BaseModel):
+class UserRepoToPerm(Base, BaseDbModel):
     __tablename__ = 'repo_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'repository_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    repo_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    repo_to_perm_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
     user = relationship('User')
     repository = relationship('Repository')
@@ -1816,17 +1857,17 @@
         return u'<%s => %s >' % (self.user, self.repository)
 
 
-class UserUserGroupToPerm(Base, BaseModel):
+class UserUserGroupToPerm(Base, BaseDbModel):
     __tablename__ = 'user_user_group_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    user_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user_user_group_to_perm_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
 
     user = relationship('User')
     user_group = relationship('UserGroup')
@@ -1845,16 +1886,16 @@
         return u'<%s => %s >' % (self.user, self.user_group)
 
 
-class UserToPerm(Base, BaseModel):
+class UserToPerm(Base, BaseDbModel):
     __tablename__ = 'user_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    user_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    user_to_perm_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
 
     user = relationship('User')
     permission = relationship('Permission')
@@ -1863,17 +1904,17 @@
         return u'<%s => %s >' % (self.user, self.permission)
 
 
-class UserGroupRepoToPerm(Base, BaseModel):
+class UserGroupRepoToPerm(Base, BaseDbModel):
     __tablename__ = 'users_group_repo_to_perm'
     __table_args__ = (
         UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
+
+    users_group_to_perm_id = Column(Integer(), primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1892,17 +1933,17 @@
         return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
 
 
-class UserGroupUserGroupToPerm(Base, BaseModel):
+class UserGroupUserGroupToPerm(Base, BaseDbModel):
     __tablename__ = 'user_group_user_group_to_perm'
     __table_args__ = (
         UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    user_group_user_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
-    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
+
+    user_group_user_group_to_perm_id = Column(Integer(), primary_key=True)
+    target_user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
+    user_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
 
     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')
@@ -1921,33 +1962,32 @@
         return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
 
 
-class UserGroupToPerm(Base, BaseModel):
+class UserGroupToPerm(Base, BaseDbModel):
     __tablename__ = 'users_group_to_perm'
     __table_args__ = (
         UniqueConstraint('users_group_id', 'permission_id',),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
-    users_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+
+    users_group_to_perm_id = Column(Integer(), primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
 
 
-class UserRepoGroupToPerm(Base, BaseModel):
+class UserRepoGroupToPerm(Base, BaseDbModel):
     __tablename__ = 'user_repo_group_to_perm'
     __table_args__ = (
         UniqueConstraint('user_id', 'group_id', 'permission_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
 
-    group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    group_to_perm_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
 
     user = relationship('User')
     group = relationship('RepoGroup')
@@ -1963,18 +2003,17 @@
         return n
 
 
-class UserGroupRepoGroupToPerm(Base, BaseModel):
+class UserGroupRepoGroupToPerm(Base, BaseDbModel):
     __tablename__ = 'users_group_repo_group_to_perm'
     __table_args__ = (
         UniqueConstraint('users_group_id', 'group_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
 
-    users_group_repo_group_to_perm_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
-    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
-    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
+    users_group_repo_group_to_perm_id = Column(Integer(), primary_key=True)
+    users_group_id = Column(Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
+    group_id = Column(Integer(), ForeignKey('groups.group_id'), nullable=False)
+    permission_id = Column(Integer(), ForeignKey('permissions.permission_id'), nullable=False)
 
     users_group = relationship('UserGroup')
     permission = relationship('Permission')
@@ -1990,65 +2029,62 @@
         return n
 
 
-class Statistics(Base, BaseModel):
+class Statistics(Base, BaseDbModel):
     __tablename__ = 'statistics'
     __table_args__ = (
-         UniqueConstraint('repository_id'),
-         {'extend_existing': True, 'mysql_engine': 'InnoDB',
-          'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+         _table_args_default_dict,
     )
-    stat_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
+
+    stat_id = Column(Integer(), primary_key=True)
+    repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True)
     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
+    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)
 
 
-class UserFollowing(Base, BaseModel):
+class UserFollowing(Base, BaseDbModel):
     __tablename__ = 'user_followings'
     __table_args__ = (
-        UniqueConstraint('user_id', 'follows_repository_id'),
-        UniqueConstraint('user_id', 'follows_user_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        UniqueConstraint('user_id', 'follows_repository_id', name='uq_user_followings_user_repo'),
+        UniqueConstraint('user_id', 'follows_user_id', name='uq_user_followings_user_user'),
+        _table_args_default_dict,
     )
 
-    user_following_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
-    follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
-    follows_user_id = Column(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_following_id = Column(Integer(), primary_key=True)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    follows_repository_id = Column(Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    follows_user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
+    follows_from = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
     user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
 
     follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
-    follows_repository = relationship('Repository', order_by='Repository.repo_name')
+    follows_repository = relationship('Repository', order_by=lambda: func.lower(Repository.repo_name))
 
     @classmethod
     def get_repo_followers(cls, repo_id):
-        return cls.query().filter(cls.follows_repo_id == repo_id)
-
-
-class CacheInvalidation(Base, BaseModel):
+        return cls.query().filter(cls.follows_repository_id == repo_id)
+
+
+class CacheInvalidation(Base, BaseDbModel):
     __tablename__ = 'cache_invalidation'
     __table_args__ = (
-        UniqueConstraint('cache_key'),
         Index('key_idx', 'cache_key'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
+
     # cache_id, not used
-    cache_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    cache_id = Column(Integer(), primary_key=True)
     # cache_key as created by _get_cache_key
-    cache_key = Column(String(255, convert_unicode=False))
+    cache_key = Column(Unicode(255), nullable=False, unique=True)
     # cache_args is a repo_name
-    cache_args = Column(String(255, convert_unicode=False))
+    cache_args = Column(Unicode(255), nullable=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)
+    cache_active = Column(Boolean(), nullable=False, default=False)
 
     def __init__(self, cache_key, repo_name=''):
         self.cache_key = cache_key
@@ -2102,7 +2138,7 @@
         return "%s%s" % (prefix, key)
 
     @classmethod
-    def set_invalidate(cls, repo_name, delete=False):
+    def set_invalidate(cls, repo_name):
         """
         Mark all caches of a repo as invalid in the database.
         """
@@ -2113,11 +2149,7 @@
         for inv_obj in inv_objs:
             log.debug('marking %s key for invalidation based on repo_name=%s',
                       inv_obj, safe_str(repo_name))
-            if delete:
-                Session().delete(inv_obj)
-            else:
-                inv_obj.cache_active = False
-                Session().add(inv_obj)
+            Session().delete(inv_obj)
         Session().commit()
 
     @classmethod
@@ -2136,11 +2168,11 @@
 
         inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
         if inv_obj is None:
-            inv_obj = CacheInvalidation(cache_key, repo_name)
+            inv_obj = cls(cache_key, repo_name)
+            Session().add(inv_obj)
         elif inv_obj.cache_active:
             return True
         inv_obj.cache_active = True
-        Session().add(inv_obj)
         try:
             Session().commit()
         except sqlalchemy.exc.IntegrityError:
@@ -2162,22 +2194,22 @@
         return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
 
 
-class ChangesetComment(Base, BaseModel):
+class ChangesetComment(Base, BaseDbModel):
     __tablename__ = 'changeset_comments'
     __table_args__ = (
         Index('cc_revision_idx', 'revision'),
         Index('cc_pull_request_id_idx', 'pull_request_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
-    comment_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+
+    comment_id = Column(Integer(), 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))
-    f_path = Column(Unicode(1000))
-    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
-    text = Column(UnicodeText(25000), nullable=False)
+    revision = Column(String(40), nullable=True)
+    pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
+    line_no = Column(Unicode(10), nullable=True)
+    f_path = Column(Unicode(1000), nullable=True)
+    author_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    text = Column(UnicodeText(), 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)
 
@@ -2190,23 +2222,6 @@
                                  cascade="all, delete-orphan", lazy='joined')
     pull_request = relationship('PullRequest')
 
-    @classmethod
-    def get_users(cls, revision=None, pull_request_id=None):
-        """
-        Returns user associated with this ChangesetComment. ie those
-        who actually commented
-
-        :param cls:
-        :param revision:
-        """
-        q = Session().query(User)\
-                .join(ChangesetComment.author)
-        if revision is not None:
-            q = q.filter(cls.revision == revision)
-        elif pull_request_id is not None:
-            q = q.filter(cls.pull_request_id == pull_request_id)
-        return q.all()
-
     def url(self):
         anchor = "comment-%s" % self.comment_id
         import kallithea.lib.helpers as h
@@ -2215,7 +2230,18 @@
         elif self.pull_request_id is not None:
             return self.pull_request.url(anchor=anchor)
 
-class ChangesetStatus(Base, BaseModel):
+    def __json__(self):
+        return dict(
+            comment_id=self.comment_id,
+            username=self.author.username,
+            text=self.text,
+        )
+
+    def deletable(self):
+        return self.created_on > datetime.datetime.now() - datetime.timedelta(minutes=5)
+
+
+class ChangesetStatus(Base, BaseDbModel):
     __tablename__ = 'changeset_statuses'
     __table_args__ = (
         Index('cs_revision_idx', 'revision'),
@@ -2223,28 +2249,30 @@
         Index('cs_pull_request_id_idx', 'pull_request_id'),
         Index('cs_changeset_comment_id_idx', 'changeset_comment_id'),
         Index('cs_pull_request_id_user_id_version_idx', 'pull_request_id', 'user_id', 'version'),
+        Index('cs_repo_id_pull_request_id_idx', 'repo_id', 'pull_request_id'),
         UniqueConstraint('repo_id', 'revision', 'version'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
+
     STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
     STATUS_APPROVED = 'approved'
-    STATUS_REJECTED = 'rejected'
+    STATUS_REJECTED = 'rejected' # is shown as "Not approved" - TODO: change database content / scheme
     STATUS_UNDER_REVIEW = 'under_review'
 
     STATUSES = [
         (STATUS_NOT_REVIEWED, _("Not reviewed")),  # (no icon) and default
+        (STATUS_UNDER_REVIEW, _("Under review")),
+        (STATUS_REJECTED, _("Not approved")),
         (STATUS_APPROVED, _("Approved")),
-        (STATUS_REJECTED, _("Rejected")),
-        (STATUS_UNDER_REVIEW, _("Under review")),
     ]
-
-    changeset_status_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    STATUSES_DICT = dict(STATUSES)
+
+    changeset_status_id = Column(Integer(), 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)
+    user_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
+    revision = Column(String(40), nullable=True)
     status = Column(String(128), nullable=False, default=DEFAULT)
-    changeset_comment_id = Column(Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
+    comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
     modified_at = Column(DateTime(), nullable=False, default=datetime.datetime.now)
     version = Column(Integer(), nullable=False, default=0)
     pull_request_id = Column(Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
@@ -2262,34 +2290,40 @@
 
     @classmethod
     def get_status_lbl(cls, value):
-        return dict(cls.STATUSES).get(value)
+        return cls.STATUSES_DICT.get(value)
 
     @property
     def status_lbl(self):
         return ChangesetStatus.get_status_lbl(self.status)
 
-
-class PullRequest(Base, BaseModel):
+    def __json__(self):
+        return dict(
+            status=self.status,
+            modified_at=self.modified_at.replace(microsecond=0),
+            reviewer=self.author.username,
+            )
+
+
+class PullRequest(Base, BaseDbModel):
     __tablename__ = 'pull_requests'
     __table_args__ = (
         Index('pr_org_repo_id_idx', 'org_repo_id'),
         Index('pr_other_repo_id_idx', 'other_repo_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        _table_args_default_dict,
     )
 
     # values for .status
     STATUS_NEW = u'new'
     STATUS_CLOSED = u'closed'
 
-    pull_request_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    title = Column(Unicode(255), nullable=True)
-    description = Column(UnicodeText(10240))
+    pull_request_id = Column(Integer(), primary_key=True)
+    title = Column(Unicode(255), nullable=False)
+    description = Column(UnicodeText(), nullable=False)
     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
+    owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
+    _revisions = Column('revisions', UnicodeText(), 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)
@@ -2312,24 +2346,55 @@
         return self.other_ref.split(':')
 
     owner = relationship('User')
-    reviewers = relationship('PullRequestReviewers',
+    reviewers = relationship('PullRequestReviewer',
                              cascade="all, delete-orphan")
     org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
     other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
-    statuses = relationship('ChangesetStatus')
-    comments = relationship('ChangesetComment',
+    statuses = relationship('ChangesetStatus', order_by='ChangesetStatus.changeset_status_id')
+    comments = relationship('ChangesetComment', order_by='ChangesetComment.comment_id',
                              cascade="all, delete-orphan")
 
+    @classmethod
+    def query(cls, reviewer_id=None, include_closed=True, sorted=False):
+        """Add PullRequest-specific helpers for common query constructs.
+
+        reviewer_id: only PRs with the specified user added as reviewer.
+
+        include_closed: if False, do not include closed PRs.
+
+        sorted: if True, apply the default ordering (newest first).
+        """
+        q = super(PullRequest, cls).query()
+
+        if reviewer_id is not None:
+            q = q.join(PullRequestReviewer).filter(PullRequestReviewer.user_id == reviewer_id)
+
+        if not include_closed:
+            q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
+
+        if sorted:
+            q = q.order_by(PullRequest.created_on.desc())
+
+        return q
+
+    def get_reviewer_users(self):
+        """Like .reviewers, but actually returning the users"""
+        return User.query() \
+            .join(PullRequestReviewer) \
+            .filter(PullRequestReviewer.pull_request == self) \
+            .order_by(PullRequestReviewer.pull_request_reviewers_id) \
+            .all()
+
     def is_closed(self):
         return self.status == self.STATUS_CLOSED
 
     def user_review_status(self, user_id):
         """Return the user's latest status votes on PR"""
         # note: no filtering on repo - that would be redundant
-        status = ChangesetStatus.query()\
-            .filter(ChangesetStatus.pull_request == self)\
-            .filter(ChangesetStatus.user_id == user_id)\
-            .order_by(ChangesetStatus.version)\
+        status = ChangesetStatus.query() \
+            .filter(ChangesetStatus.pull_request == self) \
+            .filter(ChangesetStatus.user_id == user_id) \
+            .order_by(ChangesetStatus.version) \
             .first()
         return str(status.status) if status else ''
 
@@ -2342,9 +2407,24 @@
         '''Return the id of this pull request, nicely formatted for displaying'''
         return self.make_nice_id(self.pull_request_id)
 
+    def get_api_data(self):
+        return self.__json__()
+
     def __json__(self):
         return dict(
-            revisions=self.revisions
+            pull_request_id=self.pull_request_id,
+            url=self.url(),
+            reviewers=self.reviewers,
+            revisions=self.revisions,
+            owner=self.owner.username,
+            title=self.title,
+            description=self.description,
+            org_repo_url=self.org_repo.clone_url(),
+            org_ref_parts=self.org_ref_parts,
+            other_ref_parts=self.other_ref_parts,
+            status=self.status,
+            comments=self.comments,
+            statuses=self.statuses,
         )
 
     def url(self, **kwargs):
@@ -2362,119 +2442,54 @@
         return h.url('pullrequest_show', repo_name=self.other_repo.repo_name,
                      pull_request_id=self.pull_request_id, **kwargs)
 
-class PullRequestReviewers(Base, BaseModel):
+
+class PullRequestReviewer(Base, BaseDbModel):
     __tablename__ = 'pull_request_reviewers'
     __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
+        Index('pull_request_reviewers_user_id_idx', 'user_id'),
+        _table_args_default_dict,
     )
 
     def __init__(self, user=None, pull_request=None):
         self.user = user
         self.pull_request = pull_request
 
-    pull_requests_reviewers_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
+    pull_request_reviewers_id = Column('pull_requests_reviewers_id', Integer(), 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_id = Column(Integer(), ForeignKey('users.user_id'), nullable=False)
 
     user = relationship('User')
     pull_request = relationship('PullRequest')
 
-
-class Notification(Base, BaseModel):
+    def __json__(self):
+        return dict(
+            username=self.user.username if self.user else None,
+        )
+
+
+class Notification(object):
     __tablename__ = 'notifications'
-    __table_args__ = (
-        Index('notification_type_idx', 'type'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-
-    TYPE_CHANGESET_COMMENT = u'cs_comment'
-    TYPE_MESSAGE = u'message'
-    TYPE_MENTION = u'mention'
-    TYPE_REGISTRATION = u'registration'
-    TYPE_PULL_REQUEST = u'pull_request'
-    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
-
-    notification_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    subject = Column(Unicode(512), nullable=True)
-    body = Column(UnicodeText(50000), nullable=True)
-    created_by = Column(Integer(), ForeignKey('users.user_id'), nullable=True)
-    created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
-    type_ = Column('type', Unicode(255))
-
-    created_by_user = relationship('User')
-    notifications_to_users = relationship('UserNotification', cascade="all, delete-orphan")
-
-    @property
-    def recipients(self):
-        return [x.user for x in UserNotification.query()
-                .filter(UserNotification.notification == self)
-                .order_by(UserNotification.user_id.asc()).all()]
-
-    @classmethod
-    def create(cls, created_by, subject, body, recipients, type_=None):
-        if type_ is None:
-            type_ = Notification.TYPE_MESSAGE
-
-        notification = cls()
-        notification.created_by_user = created_by
-        notification.subject = subject
-        notification.body = body
-        notification.type_ = type_
-        notification.created_on = datetime.datetime.now()
-
-        for u in recipients:
-            assoc = UserNotification()
-            assoc.notification = notification
-            assoc.user_id = u.user_id
-            Session().add(assoc)
-        Session().add(notification)
-        Session().flush() # assign notificaiton.notification_id
-        return notification
-
-    @property
-    def description(self):
-        from kallithea.model.notification import NotificationModel
-        return NotificationModel().make_description(self)
-
-
-class UserNotification(Base, BaseModel):
+
+class UserNotification(object):
     __tablename__ = 'user_to_notification'
-    __table_args__ = (
-        UniqueConstraint('user_id', 'notification_id'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
-    )
-    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')
-
-    def mark_as_read(self):
-        self.read = True
-        Session().add(self)
-
-
-class Gist(Base, BaseModel):
+
+
+class Gist(Base, BaseDbModel):
     __tablename__ = 'gists'
     __table_args__ = (
         Index('g_gist_access_id_idx', 'gist_access_id'),
         Index('g_created_on_idx', 'created_on'),
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
+        _table_args_default_dict,
     )
+
     GIST_PUBLIC = u'public'
     GIST_PRIVATE = u'private'
     DEFAULT_FILENAME = u'gistfile1.txt'
 
-    gist_id = Column(Integer(), nullable=False, unique=True, primary_key=True)
-    gist_access_id = Column(Unicode(250))
-    gist_description = Column(UnicodeText(1024))
-    gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
+    gist_id = Column(Integer(), primary_key=True)
+    gist_access_id = Column(Unicode(250), nullable=False)
+    gist_description = Column(UnicodeText(), nullable=False)
+    owner_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
     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)
@@ -2482,10 +2497,18 @@
 
     owner = relationship('User')
 
+    @hybrid_property
+    def is_expired(self):
+        return (self.gist_expires != -1) & (time.time() > self.gist_expires)
+
     def __repr__(self):
         return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
 
     @classmethod
+    def guess_instance(cls, value):
+        return super(Gist, cls).guess_instance(value, Gist.get_by_access_id)
+
+    @classmethod
     def get_or_404(cls, id_):
         res = cls.query().filter(cls.gist_access_id == id_).scalar()
         if res is None:
@@ -2513,7 +2536,7 @@
         :param cls:
         """
         from kallithea.model.gist import GIST_STORE_LOC
-        q = Session().query(Ui)\
+        q = Session().query(Ui) \
             .filter(Ui.ui_key == URL_SEP)
         q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return os.path.join(q.one().ui_value, GIST_STORE_LOC)
@@ -2547,14 +2570,3 @@
         base_path = self.base_path()
         return get_repo(os.path.join(*map(safe_str,
                                           [base_path, self.gist_access_id])))
-
-
-class DbMigrateVersion(Base, BaseModel):
-    __tablename__ = 'db_migrate_version'
-    __table_args__ = (
-        {'extend_existing': True, 'mysql_engine': 'InnoDB',
-         'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
-    )
-    repository_id = Column(String(250), nullable=False, unique=True, primary_key=True)
-    repository_path = Column(Text)
-    version = Column(Integer)
--- a/kallithea/model/forms.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/forms.py	Sun Mar 31 21:28:56 2019 +0200
@@ -38,7 +38,7 @@
 import formencode
 from formencode import All
 
-from pylons.i18n.translation import _
+from tg.i18n import ugettext as _
 
 from kallithea import BACKENDS
 from kallithea.model import validators as v
@@ -46,30 +46,32 @@
 log = logging.getLogger(__name__)
 
 
-class LoginForm(formencode.Schema):
-    allow_extra_fields = True
-    filter_extra_fields = True
-    username = v.UnicodeString(
-        strip=True,
-        min=1,
-        not_empty=True,
-        messages={
-           'empty': _('Please enter a login'),
-           'tooShort': _('Enter a value %(min)i characters long or more')}
-    )
+def LoginForm():
+    class _LoginForm(formencode.Schema):
+        allow_extra_fields = True
+        filter_extra_fields = True
+        username = v.UnicodeString(
+            strip=True,
+            min=1,
+            not_empty=True,
+            messages={
+               'empty': _('Please enter a login'),
+               'tooShort': _('Enter a value %(min)i characters long or more')}
+        )
 
-    password = v.UnicodeString(
-        strip=False,
-        min=3,
-        not_empty=True,
-        messages={
-            'empty': _('Please enter a password'),
-            'tooShort': _('Enter %(min)i characters or more')}
-    )
+        password = v.UnicodeString(
+            strip=False,
+            min=3,
+            not_empty=True,
+            messages={
+                'empty': _('Please enter a password'),
+                'tooShort': _('Enter %(min)i characters or more')}
+        )
 
-    remember = v.StringBoolean(if_missing=False)
+        remember = v.StringBoolean(if_missing=False)
 
-    chained_validators = [v.ValidAuth()]
+        chained_validators = [v.ValidAuth()]
+    return _LoginForm
 
 
 def PasswordChangeForm(username):
@@ -86,7 +88,9 @@
     return _PasswordChangeForm
 
 
-def UserForm(edit=False, old_data={}):
+def UserForm(edit=False, old_data=None):
+    old_data = old_data or {}
+
     class _UserForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
@@ -125,7 +129,10 @@
     return _UserForm
 
 
-def UserGroupForm(edit=False, old_data={}, available_members=[]):
+def UserGroupForm(edit=False, old_data=None, available_members=None):
+    old_data = old_data or {}
+    available_members = available_members or []
+
     class _UserGroupForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
@@ -148,9 +155,12 @@
     return _UserGroupForm
 
 
-def RepoGroupForm(edit=False, old_data={}, repo_groups=[],
+def RepoGroupForm(edit=False, old_data=None, repo_groups=None,
                    can_create_in_root=False):
+    old_data = old_data or {}
+    repo_groups = repo_groups or []
     repo_group_ids = [rg[0] for rg in repo_groups]
+
     class _RepoGroupForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
@@ -163,11 +173,11 @@
         group_copy_permissions = v.StringBoolean(if_missing=False)
 
         if edit:
-            #FIXME: do a special check that we cannot move a group to one of
-            #its children
+            # FIXME: do a special check that we cannot move a group to one of
+            # its children
             pass
 
-        group_parent_id = All(v.CanCreateGroup(can_create_in_root),
+        parent_group_id = All(v.CanCreateGroup(can_create_in_root),
                               v.OneOf(repo_group_ids, hideList=False,
                                       testValueList=True,
                                       if_missing=None, not_empty=True),
@@ -178,7 +188,7 @@
     return _RepoGroupForm
 
 
-def RegisterForm(edit=False, old_data={}):
+def RegisterForm(edit=False, old_data=None):
     class _RegisterForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
@@ -212,6 +222,7 @@
         email = v.Email(not_empty=True)
     return _PasswordResetRequestForm
 
+
 def PasswordResetConfirmationForm():
     class _PasswordResetConfirmationForm(formencode.Schema):
         allow_extra_fields = True
@@ -227,9 +238,14 @@
                                                     'password_confirm')]
     return _PasswordResetConfirmationForm
 
-def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
-             repo_groups=[], landing_revs=[]):
+
+def RepoForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
+             repo_groups=None, landing_revs=None):
+    old_data = old_data or {}
+    repo_groups = repo_groups or []
+    landing_revs = landing_revs or []
     repo_group_ids = [rg[0] for rg in repo_groups]
+
     class _RepoForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
@@ -251,8 +267,7 @@
         repo_enable_locking = v.StringBoolean(if_missing=False)
 
         if edit:
-            #this is repo owner
-            user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
+            owner = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
             # Not a real field - just for reference for validation:
             # clone_uri_hidden = v.UnicodeString(if_missing='')
 
@@ -302,9 +317,13 @@
     return _RepoFieldForm
 
 
-def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
-                 repo_groups=[], landing_revs=[]):
+def RepoForkForm(edit=False, old_data=None, supported_backends=BACKENDS.keys(),
+                 repo_groups=None, landing_revs=None):
+    old_data = old_data or {}
+    repo_groups = repo_groups or []
+    landing_revs = landing_revs or []
     repo_group_ids = [rg[0] for rg in repo_groups]
+
     class _RepoForkForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
@@ -344,7 +363,7 @@
         filter_extra_fields = False
         show_public_icon = v.StringBoolean(if_missing=False)
         show_private_icon = v.StringBoolean(if_missing=False)
-        stylify_metatags = v.StringBoolean(if_missing=False)
+        stylify_metalabels = v.StringBoolean(if_missing=False)
 
         repository_fields = v.StringBoolean(if_missing=False)
         lightweight_journal = v.StringBoolean(if_missing=False)
@@ -362,7 +381,6 @@
     class _ApplicationUiSettingsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = False
-        web_push_ssl = v.StringBoolean(if_missing=False)
         paths_root_path = All(
             v.ValidPath(),
             v.UnicodeString(strip=True, min=1, not_empty=True)
@@ -421,7 +439,7 @@
     return _CustomDefaultPermissionsForm
 
 
-def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
+def DefaultsForm(edit=False, old_data=None, supported_backends=BACKENDS.keys()):
     class _DefaultsForm(formencode.Schema):
         allow_extra_fields = True
         filter_extra_fields = True
@@ -455,7 +473,7 @@
                         validator = sv["validator"]
                         if isinstance(validator, LazyFormencode):
                             validator = validator()
-                        #init all lazy validators from formencode.All
+                        # init all lazy validators from formencode.All
                         if isinstance(validator, All):
                             init_validators = []
                             for validator in validator.validators:
@@ -515,7 +533,6 @@
         org_ref = v.UnicodeString(strip=True, required=True)
         other_repo = v.UnicodeString(strip=True, required=True)
         other_ref = v.UnicodeString(strip=True, required=True)
-        review_members = v.Set()
 
         pullrequest_title = v.UnicodeString(strip=True, required=True)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
@@ -530,6 +547,7 @@
 
         pullrequest_title = v.UnicodeString(strip=True, required=True)
         pullrequest_desc = v.UnicodeString(strip=True, required=False)
+        org_review_members = v.Set()
         review_members = v.Set()
         updaterev = v.UnicodeString(strip=True, required=False, if_missing=None)
         owner = All(v.UnicodeString(strip=True, required=True),
--- a/kallithea/model/gist.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/gist.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,16 +26,16 @@
 """
 
 import os
+import random
 import time
 import logging
 import traceback
 import shutil
 
-from kallithea.lib.utils2 import safe_unicode, unique_id, safe_int, \
+from kallithea.lib.utils2 import safe_unicode, safe_int, \
     time_to_datetime, AttributeDict
 from kallithea.lib.compat import json
-from kallithea.model import BaseModel
-from kallithea.model.db import Gist
+from kallithea.model.db import Gist, Session, User
 from kallithea.model.repo import RepoModel
 from kallithea.model.scm import ScmModel
 
@@ -45,16 +45,15 @@
 GIST_METADATA_FILE = '.rc_gist_metadata'
 
 
-class GistModel(BaseModel):
-    cls = Gist
+def make_gist_id():
+    """Generate a random, URL safe, almost certainly unique gist identifier."""
+    rnd = random.SystemRandom() # use cryptographically secure system PRNG
+    alphabet = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz'
+    length = 20
+    return u''.join(rnd.choice(alphabet) for _ in xrange(length))
 
-    def _get_gist(self, gist):
-        """
-        Helper method to get gist by ID, or gist_access_id as a fallback
 
-        :param gist: GistID, gist_access_id, or Gist instance
-        """
-        return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
+class GistModel(object):
 
     def __delete_gist(self, gist):
         """
@@ -86,7 +85,7 @@
             f.write(json.dumps(metadata))
 
     def get_gist(self, gist):
-        return self._get_gist(gist)
+        return Gist.guess_instance(gist)
 
     def get_gist_files(self, gist_access_id, revision=None):
         """
@@ -108,27 +107,26 @@
         :param gist_type: type of gist private/public
         :param lifetime: in minutes, -1 == forever
         """
-        owner = self._get_user(owner)
-        gist_id = safe_unicode(unique_id(20))
+        owner = User.guess_instance(owner)
+        gist_id = make_gist_id()
         lifetime = safe_int(lifetime, -1)
         gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
         log.debug('set GIST expiration date to: %s',
                   time_to_datetime(gist_expires)
                    if gist_expires != -1 else 'forever')
-        #create the Database version
+        # create the Database version
         gist = Gist()
         gist.gist_description = description
         gist.gist_access_id = gist_id
-        gist.gist_owner = owner.user_id
+        gist.owner_id = owner.user_id
         gist.gist_expires = gist_expires
         gist.gist_type = safe_unicode(gist_type)
-        self.sa.add(gist)
-        self.sa.flush()
+        Session().add(gist)
+        Session().flush() # make database assign gist.gist_id
         if gist_type == Gist.GIST_PUBLIC:
             # use DB ID for easy to use GIST ID
             gist_id = safe_unicode(gist.gist_id)
             gist.gist_access_id = gist_id
-            self.sa.add(gist)
 
         gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
         log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
@@ -141,7 +139,7 @@
                 raise Exception('Filename cannot be inside a directory')
 
             content = gist_mapping[filename]['content']
-            #TODO: expand support for setting explicit lexers
+            # TODO: expand support for setting explicit lexers
 #             if lexer is None:
 #                 try:
 #                     guess_lexer = pygments.lexers.guess_lexer_for_filename
@@ -155,7 +153,7 @@
         message += 's: ' if len(processed_mapping) > 1 else ': '
         message += ', '.join([x for x in processed_mapping])
 
-        #fake Kallithea Repository object
+        # fake Kallithea Repository object
         fake_repo = AttributeDict(dict(
             repo_name=gist_repo_path,
             scm_instance_no_cache=lambda: repo,
@@ -172,9 +170,9 @@
         return gist
 
     def delete(self, gist, fs_remove=True):
-        gist = self._get_gist(gist)
+        gist = Gist.guess_instance(gist)
         try:
-            self.sa.delete(gist)
+            Session().delete(gist)
             if fs_remove:
                 self.__delete_gist(gist)
             else:
@@ -185,7 +183,7 @@
 
     def update(self, gist, description, owner, gist_mapping, gist_type,
                lifetime):
-        gist = self._get_gist(gist)
+        gist = Gist.guess_instance(gist)
         gist_repo = gist.scm_instance
 
         lifetime = safe_int(lifetime, -1)
@@ -194,7 +192,7 @@
         else:
             gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
 
-        #calculate operation type based on given data
+        # calculate operation type based on given data
         gist_mapping_op = {}
         for k, v in gist_mapping.items():
             # add, mod, del
@@ -212,14 +210,12 @@
         gist.gist_expires = gist_expires
         gist.owner = owner
         gist.gist_type = gist_type
-        self.sa.add(gist)
-        self.sa.flush()
 
         message = 'updated file'
         message += 's: ' if len(gist_mapping) > 1 else ': '
         message += ', '.join([x for x in gist_mapping])
 
-        #fake Kallithea Repository object
+        # fake Kallithea Repository object
         fake_repo = AttributeDict(dict(
             repo_name=gist_repo.path,
             scm_instance_no_cache=lambda: gist_repo,
--- a/kallithea/model/meta.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/meta.py	Sun Mar 31 21:28:56 2019 +0200
@@ -38,5 +38,20 @@
 # Engine is injected when model.__init__.init_model() sets meta.Base.metadata.bind
 Base = declarative_base()
 
-#to use cache use this in query
-#.options(FromCache("sqlalchemy_cache_type", "cachekey"))
+# to use cache use this in query:
+#   .options(FromCache("sqlalchemy_cache_type", "cachekey"))
+
+
+# Define naming conventions for foreign keys, primary keys, indexes,
+# check constraints, and unique constraints, respectively.
+Base.metadata.naming_convention = {
+    'fk': 'fk_%(table_name)s_%(column_0_name)s',
+    'pk': 'pk_%(table_name)s',
+    'ix': 'ix_%(column_0_label)s',
+    'ck': 'ck_%(table_name)s_%(column_0_name)s',
+    'uq': 'uq_%(table_name)s_%(column_0_name)s',
+}
+# For custom CheckConstraints (not those autogenerated e.g. for Boolean
+# types), a name should be given explicitly, since "column_0" is here a
+# rather vague notion. A custom name is also necesarry if the generated
+# name is very long, since MySQL limits identifiers to 64 characters.
--- a/kallithea/model/notification.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/notification.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,40 +26,35 @@
 :license: GPLv3, see LICENSE.md for more details.
 """
 
+import datetime
 import logging
 import traceback
 
-from pylons import tmpl_context as c
-from pylons.i18n.translation import _
+from tg import tmpl_context as c, app_globals
+from tg.i18n import ugettext as _
 from sqlalchemy.orm import joinedload, subqueryload
 
 import kallithea
 from kallithea.lib import helpers as h
 from kallithea.lib.utils2 import safe_unicode
-from kallithea.model import BaseModel
-from kallithea.model.db import Notification, User, UserNotification
+from kallithea.model.db import User
 from kallithea.model.meta import Session
 
 log = logging.getLogger(__name__)
 
 
-class NotificationModel(BaseModel):
-
-    cls = Notification
+class NotificationModel(object):
 
-    def __get_notification(self, notification):
-        if isinstance(notification, Notification):
-            return notification
-        elif isinstance(notification, (int, long)):
-            return Notification.get(notification)
-        else:
-            if notification is not None:
-                raise Exception('notification must be int, long or Instance'
-                                ' of Notification got %s' % type(notification))
+    TYPE_CHANGESET_COMMENT = u'cs_comment'
+    TYPE_MESSAGE = u'message'
+    TYPE_MENTION = u'mention' # not used
+    TYPE_REGISTRATION = u'registration'
+    TYPE_PULL_REQUEST = u'pull_request'
+    TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
 
     def create(self, created_by, subject, body, recipients=None,
-               type_=Notification.TYPE_MESSAGE, with_email=True,
-               email_kwargs={}):
+               type_=TYPE_MESSAGE, with_email=True,
+               email_kwargs=None, repo_name=None):
         """
 
         Creates notification of given type
@@ -74,23 +69,22 @@
         :param with_email: send email with this notification
         :param email_kwargs: additional dict to pass as args to email template
         """
-        from kallithea.lib.celerylib import tasks, run_task
-
+        from kallithea.lib.celerylib import tasks
+        email_kwargs = email_kwargs or {}
         if recipients and not getattr(recipients, '__iter__', False):
             raise Exception('recipients must be a list or iterable')
 
-        created_by_obj = self._get_user(created_by)
+        created_by_obj = User.guess_instance(created_by)
 
-        recipients_objs = []
+        recipients_objs = set()
         if recipients:
             for u in recipients:
-                obj = self._get_user(u)
+                obj = User.guess_instance(u)
                 if obj is not None:
-                    recipients_objs.append(obj)
+                    recipients_objs.add(obj)
                 else:
                     # TODO: inform user that requested operation couldn't be completed
                     log.error('cannot email unknown user %r', u)
-            recipients_objs = set(recipients_objs)
             log.debug('sending notifications %s to %s',
                 type_, recipients_objs
             )
@@ -102,195 +96,63 @@
             )
         #else: silently skip notification mails?
 
-        # TODO: inform user who are notified
-        notif = Notification.create(
-            created_by=created_by_obj, subject=subject,
-            body=body, recipients=recipients_objs, type_=type_
-        )
-
         if not with_email:
-            return notif
-
-        #don't send email to person who created this comment
-        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
+            return
 
         headers = {}
         headers['X-Kallithea-Notification-Type'] = type_
         if 'threading' in email_kwargs:
             headers['References'] = ' '.join('<%s>' % x for x in email_kwargs['threading'])
 
+        # this is passed into template
+        created_on = h.fmt_date(datetime.datetime.now())
+        html_kwargs = {
+                  'subject': subject,
+                  'body': h.render_w_mentions(body, repo_name),
+                  'when': created_on,
+                  'user': created_by_obj.username,
+                  }
+
+        txt_kwargs = {
+                  'subject': subject,
+                  'body': body,
+                  'when': created_on,
+                  'user': created_by_obj.username,
+                  }
+
+        html_kwargs.update(email_kwargs)
+        txt_kwargs.update(email_kwargs)
+        email_subject = EmailNotificationModel() \
+                            .get_email_description(type_, **txt_kwargs)
+        email_txt_body = EmailNotificationModel() \
+                            .get_email_tmpl(type_, 'txt', **txt_kwargs)
+        email_html_body = EmailNotificationModel() \
+                            .get_email_tmpl(type_, 'html', **html_kwargs)
+
+        # don't send email to person who created this comment
+        rec_objs = set(recipients_objs).difference(set([created_by_obj]))
+
         # send email with notification to all other participants
         for rec in rec_objs:
-            ## this is passed into template
-            html_kwargs = {
-                      'subject': subject,
-                      'body': h.rst_w_mentions(body),
-                      'when': h.fmt_date(notif.created_on),
-                      'user': notif.created_by_user.username,
-                      }
-
-            txt_kwargs = {
-                      'subject': subject,
-                      'body': body,
-                      'when': h.fmt_date(notif.created_on),
-                      'user': notif.created_by_user.username,
-                      }
-
-            html_kwargs.update(email_kwargs)
-            txt_kwargs.update(email_kwargs)
-            email_subject = EmailNotificationModel()\
-                                .get_email_description(type_, **txt_kwargs)
-            email_txt_body = EmailNotificationModel()\
-                                .get_email_tmpl(type_, 'txt', **txt_kwargs)
-            email_html_body = EmailNotificationModel()\
-                                .get_email_tmpl(type_, 'html', **html_kwargs)
-
-            run_task(tasks.send_email, [rec.email], email_subject, email_txt_body,
+            tasks.send_email([rec.email], email_subject, email_txt_body,
                      email_html_body, headers, author=created_by_obj)
 
-        return notif
 
-    def delete(self, user, notification):
-        # we don't want to remove actual notification just the assignment
-        try:
-            notification = self.__get_notification(notification)
-            user = self._get_user(user)
-            if notification and user:
-                obj = UserNotification.query()\
-                        .filter(UserNotification.user == user)\
-                        .filter(UserNotification.notification
-                                == notification)\
-                        .one()
-                Session().delete(obj)
-                return True
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def get_for_user(self, user, filter_=None):
-        """
-        Get notifications for given user, filter them if filter dict is given
-
-        :param user:
-        :param filter:
-        """
-        user = self._get_user(user)
-
-        q = UserNotification.query()\
-            .filter(UserNotification.user == user)\
-            .join((Notification, UserNotification.notification_id ==
-                                 Notification.notification_id))\
-            .options(joinedload('notification'))\
-            .options(subqueryload('notification.created_by_user'))\
-            .order_by(Notification.created_on.desc())
-
-        if filter_:
-            q = q.filter(Notification.type_.in_(filter_))
-
-        return q.all()
-
-    def mark_read(self, user, notification):
-        try:
-            notification = self.__get_notification(notification)
-            user = self._get_user(user)
-            if notification and user:
-                obj = UserNotification.query()\
-                        .filter(UserNotification.user == user)\
-                        .filter(UserNotification.notification
-                                == notification)\
-                        .one()
-                obj.read = True
-                Session().add(obj)
-                return True
-        except Exception:
-            log.error(traceback.format_exc())
-            raise
-
-    def mark_all_read_for_user(self, user, filter_=None):
-        user = self._get_user(user)
-        q = UserNotification.query()\
-            .filter(UserNotification.user == user)\
-            .filter(UserNotification.read == False)\
-            .join((Notification, UserNotification.notification_id ==
-                                 Notification.notification_id))
-        if filter_:
-            q = q.filter(Notification.type_.in_(filter_))
+class EmailNotificationModel(object):
 
-        # this is a little inefficient but sqlalchemy doesn't support
-        # update on joined tables :(
-        for obj in q.all():
-            obj.read = True
-            Session().add(obj)
-
-    def get_unread_cnt_for_user(self, user):
-        user = self._get_user(user)
-        return UserNotification.query()\
-                .filter(UserNotification.read == False)\
-                .filter(UserNotification.user == user).count()
-
-    def get_unread_for_user(self, user):
-        user = self._get_user(user)
-        return [x.notification for x in UserNotification.query()\
-                .filter(UserNotification.read == False)\
-                .filter(UserNotification.user == user).all()]
-
-    def get_user_notification(self, user, notification):
-        user = self._get_user(user)
-        notification = self.__get_notification(notification)
-
-        return UserNotification.query()\
-            .filter(UserNotification.notification == notification)\
-            .filter(UserNotification.user == user).scalar()
-
-    def make_description(self, notification, show_age=True):
-        """
-        Creates a human readable description based on properties
-        of notification object
-        """
-        #alias
-        _n = notification
-
-        if show_age:
-            return {
-                    _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset %(age)s'),
-                    _n.TYPE_MESSAGE: _('%(user)s sent message %(age)s'),
-                    _n.TYPE_MENTION: _('%(user)s mentioned you %(age)s'),
-                    _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea %(age)s'),
-                    _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request %(age)s'),
-                    _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request %(age)s'),
-                }[notification.type_] % dict(
-                    user=notification.created_by_user.username,
-                    age=h.age(notification.created_on),
-                )
-        else:
-            return {
-                    _n.TYPE_CHANGESET_COMMENT: _('%(user)s commented on changeset at %(when)s'),
-                    _n.TYPE_MESSAGE: _('%(user)s sent message at %(when)s'),
-                    _n.TYPE_MENTION: _('%(user)s mentioned you at %(when)s'),
-                    _n.TYPE_REGISTRATION: _('%(user)s registered in Kallithea at %(when)s'),
-                    _n.TYPE_PULL_REQUEST: _('%(user)s opened new pull request at %(when)s'),
-                    _n.TYPE_PULL_REQUEST_COMMENT: _('%(user)s commented on pull request at %(when)s'),
-                }[notification.type_] % dict(
-                    user=notification.created_by_user.username,
-                    when=h.fmt_date(notification.created_on),
-                )
-
-
-class EmailNotificationModel(BaseModel):
-
-    TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
-    TYPE_MESSAGE = Notification.TYPE_MESSAGE # only used for testing
-    # Notification.TYPE_MENTION is not used
+    TYPE_CHANGESET_COMMENT = NotificationModel.TYPE_CHANGESET_COMMENT
+    TYPE_MESSAGE = NotificationModel.TYPE_MESSAGE # only used for testing
+    # NotificationModel.TYPE_MENTION is not used
     TYPE_PASSWORD_RESET = 'password_link'
-    TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
-    TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
-    TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
+    TYPE_REGISTRATION = NotificationModel.TYPE_REGISTRATION
+    TYPE_PULL_REQUEST = NotificationModel.TYPE_PULL_REQUEST
+    TYPE_PULL_REQUEST_COMMENT = NotificationModel.TYPE_PULL_REQUEST_COMMENT
     TYPE_DEFAULT = 'default'
 
     def __init__(self):
         super(EmailNotificationModel, self).__init__()
-        self._template_root = kallithea.CONFIG['pylons.paths']['templates'][0]
-        self._tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
+        self._template_root = kallithea.CONFIG['paths']['templates'][0]
+        self._tmpl_lookup = app_globals.mako_lookup
         self.email_types = {
             self.TYPE_CHANGESET_COMMENT: 'changeset_comment',
             self.TYPE_PASSWORD_RESET: 'password_reset',
@@ -300,13 +162,13 @@
             self.TYPE_PULL_REQUEST_COMMENT: 'pull_request_comment',
         }
         self._subj_map = {
-            self.TYPE_CHANGESET_COMMENT: _('[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s'),
+            self.TYPE_CHANGESET_COMMENT: _('[Comment] %(repo_name)s changeset %(short_id)s "%(message_short)s" on %(branch)s'),
             self.TYPE_MESSAGE: 'Test Message',
             # self.TYPE_PASSWORD_RESET
             self.TYPE_REGISTRATION: _('New user %(new_username)s registered'),
             # self.TYPE_DEFAULT
-            self.TYPE_PULL_REQUEST: _('[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
-            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
+            self.TYPE_PULL_REQUEST: _('[Review] %(repo_name)s PR %(pr_nice_id)s "%(pr_title_short)s" from %(pr_source_branch)s by %(pr_owner_username)s'),
+            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment] %(repo_name)s PR %(pr_nice_id)s "%(pr_title_short)s" from %(pr_source_branch)s by %(pr_owner_username)s'),
         }
 
     def get_email_description(self, type_, **kwargs):
@@ -339,5 +201,25 @@
                    'h': h,
                    'c': c}
         _kwargs.update(kwargs)
+        if content_type == 'html':
+            _kwargs.update({
+                "color_text": "#202020",
+                "color_emph": "#395fa0",
+                "color_link": "#395fa0",
+                "color_border": "#ddd",
+                "color_background_grey": "#f9f9f9",
+                "color_button": "#395fa0",
+                "monospace_style": "font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace",
+                "sans_style": "font-family:Helvetica,Arial,sans-serif",
+                })
+            _kwargs.update({
+                "default_style": "%(sans_style)s;font-weight:200;font-size:14px;line-height:17px;color:%(color_text)s" % _kwargs,
+                "comment_style": "%(monospace_style)s;white-space:pre-wrap" % _kwargs,
+                "data_style": "border:%(color_border)s 1px solid;background:%(color_background_grey)s" % _kwargs,
+                "emph_style": "font-weight:600;color:%(color_emph)s" % _kwargs,
+                "link_style": "color:%(color_link)s;text-decoration:none" % _kwargs,
+                "link_text_style": "color:%(color_text)s;text-decoration:none;border:%(color_border)s 1px solid;background:%(color_background_grey)s" % _kwargs,
+                })
+
         log.debug('rendering tmpl %s with kwargs %s', base, _kwargs)
         return email_template.render_unicode(**_kwargs)
--- a/kallithea/model/permission.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/permission.py	Sun Mar 31 21:28:56 2019 +0200
@@ -31,21 +31,18 @@
 
 from sqlalchemy.exc import DatabaseError
 
-from kallithea.model import BaseModel
-from kallithea.model.db import User, Permission, UserToPerm, UserRepoToPerm,\
-    UserRepoGroupToPerm, UserUserGroupToPerm
+from kallithea.model.db import Permission, Session, User, \
+    UserToPerm, UserRepoToPerm, UserRepoGroupToPerm, UserUserGroupToPerm
 from kallithea.lib.utils2 import str2bool
 
 log = logging.getLogger(__name__)
 
 
-class PermissionModel(BaseModel):
+class PermissionModel(object):
     """
     Permissions model for Kallithea
     """
 
-    cls = Permission
-
     def create_permissions(self):
         """
         Create permissions for whole system
@@ -54,8 +51,7 @@
             if not Permission.get_by_key(p[0]):
                 new_perm = Permission()
                 new_perm.permission_name = p[0]
-                new_perm.permission_longname = p[0]  #translation err with p[1]
-                self.sa.add(new_perm)
+                Session().add(new_perm)
 
     def create_default_permissions(self, user, force=False):
         """
@@ -65,7 +61,7 @@
 
         :param user:
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
 
         def _make_perm(perm):
             new_perm = UserToPerm()
@@ -84,8 +80,8 @@
 
         if force:
             for perm in perms:
-                self.sa.delete(perm)
-            self.sa.commit()
+                Session().delete(perm)
+            Session().commit()
             defined_perms_groups = []
         # For every default permission that needs to be created, we check if
         # its group is already defined. If it's not, we create default permission.
@@ -95,16 +91,15 @@
                 log.debug('GR:%s not found, creating permission %s',
                           gr, perm_name)
                 new_perm = _make_perm(perm_name)
-                self.sa.add(new_perm)
+                Session().add(new_perm)
 
     def update(self, form_result):
         perm_user = User.get_by_username(username=form_result['perm_user_name'])
 
         try:
             # stage 1 set anonymous access
-            if perm_user.username == User.DEFAULT_USER:
+            if perm_user.is_default_user:
                 perm_user.active = str2bool(form_result['anonymous'])
-                self.sa.add(perm_user)
 
             # stage 2 reset defaults and set them from form data
             def _make_new(usr, perm_name):
@@ -116,61 +111,58 @@
             # clear current entries, to make this function idempotent
             # it will fix even if we define more permissions or permissions
             # are somehow missing
-            u2p = self.sa.query(UserToPerm)\
-                .filter(UserToPerm.user == perm_user)\
+            u2p = UserToPerm.query() \
+                .filter(UserToPerm.user == perm_user) \
                 .all()
             for p in u2p:
-                self.sa.delete(p)
-            #create fresh set of permissions
+                Session().delete(p)
+            # create fresh set of permissions
             for def_perm_key in ['default_repo_perm',
                                  'default_group_perm',
                                  'default_user_group_perm',
                                  'default_repo_create',
                                  'create_on_write', # special case for create repos on write access to group
-                                 #'default_repo_group_create', #not implemented yet
+                                 #'default_repo_group_create', # not implemented yet
                                  'default_user_group_create',
                                  'default_fork',
                                  'default_register',
                                  'default_extern_activate']:
                 p = _make_new(perm_user, form_result[def_perm_key])
-                self.sa.add(p)
+                Session().add(p)
 
-            #stage 3 update all default permissions for repos if checked
+            # stage 3 update all default permissions for repos if checked
             if form_result['overwrite_default_repo']:
                 _def_name = form_result['default_repo_perm'].split('repository.')[-1]
                 _def = Permission.get_by_key('repository.' + _def_name)
                 # repos
-                for r2p in self.sa.query(UserRepoToPerm)\
-                               .filter(UserRepoToPerm.user == perm_user)\
+                for r2p in UserRepoToPerm.query() \
+                               .filter(UserRepoToPerm.user == perm_user) \
                                .all():
 
-                    #don't reset PRIVATE repositories
+                    # don't reset PRIVATE repositories
                     if not r2p.repository.private:
                         r2p.permission = _def
-                        self.sa.add(r2p)
 
             if form_result['overwrite_default_group']:
                 _def_name = form_result['default_group_perm'].split('group.')[-1]
                 # groups
                 _def = Permission.get_by_key('group.' + _def_name)
-                for g2p in self.sa.query(UserRepoGroupToPerm)\
-                               .filter(UserRepoGroupToPerm.user == perm_user)\
+                for g2p in UserRepoGroupToPerm.query() \
+                               .filter(UserRepoGroupToPerm.user == perm_user) \
                                .all():
                     g2p.permission = _def
-                    self.sa.add(g2p)
 
             if form_result['overwrite_default_user_group']:
                 _def_name = form_result['default_user_group_perm'].split('usergroup.')[-1]
                 # groups
                 _def = Permission.get_by_key('usergroup.' + _def_name)
-                for g2p in self.sa.query(UserUserGroupToPerm)\
-                               .filter(UserUserGroupToPerm.user == perm_user)\
+                for g2p in UserUserGroupToPerm.query() \
+                               .filter(UserUserGroupToPerm.user == perm_user) \
                                .all():
                     g2p.permission = _def
-                    self.sa.add(g2p)
 
-            self.sa.commit()
+            Session().commit()
         except (DatabaseError,):
             log.error(traceback.format_exc())
-            self.sa.rollback()
+            Session().rollback()
             raise
--- a/kallithea/model/pull_request.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/pull_request.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,107 +27,46 @@
 
 import logging
 import datetime
+import re
 
-from pylons.i18n.translation import _
+from tg import request
+from tg.i18n import ugettext as _
+
+from sqlalchemy.orm import joinedload
 
 from kallithea.model.meta import Session
 from kallithea.lib import helpers as h
-from kallithea.lib.exceptions import UserInvalidException
-from kallithea.model import BaseModel
-from kallithea.model.db import PullRequest, PullRequestReviewers, Notification,\
+from kallithea.model.db import PullRequest, PullRequestReviewer, \
     ChangesetStatus, User
 from kallithea.model.notification import NotificationModel
-from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
+from kallithea.lib.utils2 import extract_mentioned_users, safe_str, safe_unicode
 
 
 log = logging.getLogger(__name__)
 
 
-class PullRequestModel(BaseModel):
-
-    cls = PullRequest
-
-    def __get_pull_request(self, pull_request):
-        return self._get_instance(PullRequest, pull_request)
-
-    def get_pullrequest_cnt_for_user(self, user):
-        return PullRequest.query()\
-                                .join(PullRequestReviewers)\
-                                .filter(PullRequestReviewers.user_id == user)\
-                                .filter(PullRequest.status != PullRequest.STATUS_CLOSED)\
-                                .count()
+def _assert_valid_reviewers(seq):
+    """Sanity check: elements are actual User objects, and not the default user."""
+    assert not any(user.is_default_user for user in seq)
 
-    def get_all(self, repo_name, from_=False, closed=False):
-        """Get all PRs for repo.
-        Default is all PRs to the repo, PRs from the repo if from_.
-        Closed PRs are only included if closed is true."""
-        repo = self._get_repo(repo_name)
-        q = PullRequest.query()
-        if from_:
-            q = q.filter(PullRequest.org_repo == repo)
-        else:
-            q = q.filter(PullRequest.other_repo == repo)
-        if not closed:
-            q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
-        return q.order_by(PullRequest.created_on.desc()).all()
 
-    def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
-               revisions, reviewers, title, description=None):
-        from kallithea.model.changeset_status import ChangesetStatusModel
-
-        created_by_user = self._get_user(created_by)
-        org_repo = self._get_repo(org_repo)
-        other_repo = self._get_repo(other_repo)
+class PullRequestModel(object):
 
-        new = PullRequest()
-        new.org_repo = org_repo
-        new.org_ref = org_ref
-        new.other_repo = other_repo
-        new.other_ref = other_ref
-        new.revisions = revisions
-        new.title = title
-        new.description = description
-        new.owner = created_by_user
-        Session().add(new)
-        Session().flush()
+    def add_reviewers(self, user, pr, reviewers, mention_recipients=None):
+        """Add reviewer and send notification to them.
+        """
+        reviewers = set(reviewers)
+        _assert_valid_reviewers(reviewers)
+        if mention_recipients is not None:
+            mention_recipients = set(mention_recipients) - reviewers
+            _assert_valid_reviewers(mention_recipients)
 
-        #reset state to under-review
-        from kallithea.model.comment import ChangesetCommentsModel
-        comment = ChangesetCommentsModel().create(
-            text=u'',
-            repo=org_repo,
-            user=new.owner,
-            pull_request=new,
-            send_email=False,
-            status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
-        )
-        ChangesetStatusModel().set_status(
-            org_repo,
-            ChangesetStatus.STATUS_UNDER_REVIEW,
-            new.owner,
-            comment,
-            pull_request=new
-        )
+        # members
+        for reviewer in reviewers:
+            prr = PullRequestReviewer(reviewer, pr)
+            Session().add(prr)
 
-        mention_recipients = set(User.get_by_username(username, case_insensitive=True)
-                                 for username in extract_mentioned_users(new.description))
-        self.__add_reviewers(created_by_user, new, reviewers, mention_recipients)
-
-        return new
-
-    def __add_reviewers(self, user, pr, reviewers, mention_recipients=None):
-        #members
-        for member in set(reviewers):
-            _usr = self._get_user(member)
-            if _usr is None:
-                raise UserInvalidException(member)
-            reviewer = PullRequestReviewers(_usr, pr)
-            Session().add(reviewer)
-
-        revision_data = [(x.raw_id, x.message)
-                         for x in map(pr.org_repo.get_changeset, pr.revisions)]
-
-        #notification to reviewers
+        # notification to reviewers
         pr_url = pr.url(canonical=True)
         threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
                                       pr.pull_request_id,
@@ -142,15 +81,27 @@
             )
         body = pr.description
         _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
+        _other_ref_type, other_ref_name, _other_rev = pr.other_ref.split(':')
+        revision_data = [(x.raw_id, x.message)
+                         for x in map(pr.org_repo.get_changeset, pr.revisions)]
         email_kwargs = {
             'pr_title': pr.title,
+            'pr_title_short': h.shorter(pr.title, 50),
             'pr_user_created': user.full_name_and_username,
             'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name),
             'pr_url': pr_url,
             'pr_revisions': revision_data,
             'repo_name': pr.other_repo.repo_name,
+            'org_repo_name': pr.org_repo.repo_name,
             'pr_nice_id': pr.nice_id(),
-            'ref': org_ref_name,
+            'pr_target_repo': h.canonical_url('summary_home',
+                               repo_name=pr.other_repo.repo_name),
+            'pr_target_branch': other_ref_name,
+            'pr_source_repo': h.canonical_url('summary_home',
+                               repo_name=pr.org_repo.repo_name),
+            'pr_source_branch': org_ref_name,
+            'pr_owner': pr.owner,
+            'pr_owner_username': pr.owner.username,
             'pr_username': user.username,
             'threading': threading,
             'is_mention': False,
@@ -158,59 +109,288 @@
         if reviewers:
             NotificationModel().create(created_by=user, subject=subject, body=body,
                                        recipients=reviewers,
-                                       type_=Notification.TYPE_PULL_REQUEST,
+                                       type_=NotificationModel.TYPE_PULL_REQUEST,
                                        email_kwargs=email_kwargs)
 
         if mention_recipients:
-            mention_recipients.discard(None)
-            mention_recipients.difference_update(reviewers)
-        if mention_recipients:
             email_kwargs['is_mention'] = True
             subject = _('[Mention]') + ' ' + subject
+            # FIXME: this subject is wrong and unused!
             NotificationModel().create(created_by=user, subject=subject, body=body,
                                        recipients=mention_recipients,
-                                       type_=Notification.TYPE_PULL_REQUEST,
+                                       type_=NotificationModel.TYPE_PULL_REQUEST,
                                        email_kwargs=email_kwargs)
 
     def mention_from_description(self, user, pr, old_description=''):
-        mention_recipients = set(User.get_by_username(username, case_insensitive=True)
-                                 for username in extract_mentioned_users(pr.description))
-        mention_recipients.difference_update(User.get_by_username(username, case_insensitive=True)
-                                             for username in extract_mentioned_users(old_description))
+        mention_recipients = (extract_mentioned_users(pr.description) -
+                              extract_mentioned_users(old_description))
 
         log.debug("Mentioning %s", mention_recipients)
-        self.__add_reviewers(user, pr, [], mention_recipients)
-
-    def update_reviewers(self, user, pull_request, reviewers_ids):
-        reviewers_ids = set(reviewers_ids)
-        pull_request = self.__get_pull_request(pull_request)
-        current_reviewers = PullRequestReviewers.query()\
-                            .filter(PullRequestReviewers.pull_request==
-                                   pull_request)\
-                            .all()
-        current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
+        self.add_reviewers(user, pr, set(), mention_recipients)
 
-        to_add = reviewers_ids.difference(current_reviewers_ids)
-        to_remove = current_reviewers_ids.difference(reviewers_ids)
-
-        log.debug("Adding %s reviewers", to_add)
-        self.__add_reviewers(user, pull_request, to_add)
+    def remove_reviewers(self, user, pull_request, reviewers):
+        """Remove specified users from being reviewers of the PR."""
+        if not reviewers:
+            return # avoid SQLAlchemy warning about empty sequence for IN-predicate
 
-        log.debug("Removing %s reviewers", to_remove)
-        for uid in to_remove:
-            reviewer = PullRequestReviewers.query()\
-                    .filter(PullRequestReviewers.user_id==uid,
-                            PullRequestReviewers.pull_request==pull_request)\
-                    .scalar()
-            if reviewer:
-                Session().delete(reviewer)
+        PullRequestReviewer.query() \
+            .filter_by(pull_request=pull_request) \
+            .filter(PullRequestReviewer.user_id.in_(r.user_id for r in reviewers)) \
+            .delete(synchronize_session='fetch') # the default of 'evaluate' is not available
 
     def delete(self, pull_request):
-        pull_request = self.__get_pull_request(pull_request)
+        pull_request = PullRequest.guess_instance(pull_request)
         Session().delete(pull_request)
+        if pull_request.org_repo.scm_instance.alias == 'git':
+            # remove a ref under refs/pull/ so that commits can be garbage-collected
+            try:
+                del pull_request.org_repo.scm_instance._repo["refs/pull/%d/head" % pull_request.pull_request_id]
+            except KeyError:
+                pass
 
     def close_pull_request(self, pull_request):
-        pull_request = self.__get_pull_request(pull_request)
+        pull_request = PullRequest.guess_instance(pull_request)
         pull_request.status = PullRequest.STATUS_CLOSED
         pull_request.updated_on = datetime.datetime.now()
-        Session().add(pull_request)
+
+
+class CreatePullRequestAction(object):
+
+    class ValidationError(Exception):
+        pass
+
+    class Empty(ValidationError):
+        pass
+
+    class AmbiguousAncestor(ValidationError):
+        pass
+
+    class Unauthorized(ValidationError):
+        pass
+
+    @staticmethod
+    def is_user_authorized(org_repo, other_repo):
+        """Performs authorization check with only the minimum amount of
+        information needed for such a check, rather than a full command
+        object.
+        """
+        if (h.HasRepoPermissionLevel('read')(org_repo.repo_name) and
+            h.HasRepoPermissionLevel('read')(other_repo.repo_name)):
+            return True
+
+        return False
+
+    def __init__(self, org_repo, other_repo, org_ref, other_ref, title, description, owner, reviewers):
+        from kallithea.controllers.compare import CompareController
+        reviewers = set(reviewers)
+        _assert_valid_reviewers(reviewers)
+
+        (org_ref_type,
+         org_ref_name,
+         org_rev) = org_ref.split(':')
+        org_display = h.short_ref(org_ref_type, org_ref_name)
+        if org_ref_type == 'rev':
+            cs = org_repo.scm_instance.get_changeset(org_rev)
+            org_ref = 'branch:%s:%s' % (cs.branch, cs.raw_id)
+
+        (other_ref_type,
+         other_ref_name,
+         other_rev) = other_ref.split(':')
+        if other_ref_type == 'rev':
+            cs = other_repo.scm_instance.get_changeset(other_rev)
+            other_ref_name = cs.raw_id[:12]
+            other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, cs.raw_id)
+        other_display = h.short_ref(other_ref_type, other_ref_name)
+
+        cs_ranges, _cs_ranges_not, ancestor_revs = \
+            CompareController._get_changesets(org_repo.scm_instance.alias,
+                                              other_repo.scm_instance, other_rev, # org and other "swapped"
+                                              org_repo.scm_instance, org_rev,
+                                              )
+        if not cs_ranges:
+            raise self.Empty(_('Cannot create empty pull request'))
+
+        if not ancestor_revs:
+            ancestor_rev = org_repo.scm_instance.EMPTY_CHANGESET
+        elif len(ancestor_revs) == 1:
+            ancestor_rev = ancestor_revs[0]
+        else:
+            raise self.AmbiguousAncestor(
+                _('Cannot create pull request - criss cross merge detected, please merge a later %s revision to %s')
+                % (other_ref_name, org_ref_name))
+
+        self.revisions = [cs_.raw_id for cs_ in cs_ranges]
+
+        # hack: ancestor_rev is not an other_rev but we want to show the
+        # requested destination and have the exact ancestor
+        other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, ancestor_rev)
+
+        if not title:
+            if org_repo == other_repo:
+                title = '%s to %s' % (org_display, other_display)
+            else:
+                title = '%s#%s to %s#%s' % (org_repo.repo_name, org_display,
+                                            other_repo.repo_name, other_display)
+        description = description or _('No description')
+
+        self.org_repo = org_repo
+        self.other_repo = other_repo
+        self.org_ref = org_ref
+        self.org_rev = org_rev
+        self.other_ref = other_ref
+        self.title = title
+        self.description = description
+        self.owner = owner
+        self.reviewers = reviewers
+
+        if not CreatePullRequestAction.is_user_authorized(self.org_repo, self.other_repo):
+            raise self.Unauthorized(_('You are not authorized to create the pull request'))
+
+    def execute(self):
+        created_by = User.get(request.authuser.user_id)
+
+        pr = PullRequest()
+        pr.org_repo = self.org_repo
+        pr.org_ref = self.org_ref
+        pr.other_repo = self.other_repo
+        pr.other_ref = self.other_ref
+        pr.revisions = self.revisions
+        pr.title = self.title
+        pr.description = self.description
+        pr.owner = self.owner
+        Session().add(pr)
+        Session().flush() # make database assign pull_request_id
+
+        if self.org_repo.scm_instance.alias == 'git':
+            # create a ref under refs/pull/ so that commits don't get garbage-collected
+            self.org_repo.scm_instance._repo["refs/pull/%d/head" % pr.pull_request_id] = safe_str(self.org_rev)
+
+        # reset state to under-review
+        from kallithea.model.changeset_status import ChangesetStatusModel
+        from kallithea.model.comment import ChangesetCommentsModel
+        comment = ChangesetCommentsModel().create(
+            text=u'',
+            repo=self.org_repo,
+            author=created_by,
+            pull_request=pr,
+            send_email=False,
+            status_change=ChangesetStatus.STATUS_UNDER_REVIEW,
+        )
+        ChangesetStatusModel().set_status(
+            self.org_repo,
+            ChangesetStatus.STATUS_UNDER_REVIEW,
+            created_by,
+            comment,
+            pull_request=pr,
+        )
+
+        mention_recipients = extract_mentioned_users(self.description)
+        PullRequestModel().add_reviewers(created_by, pr, self.reviewers, mention_recipients)
+
+        return pr
+
+
+class CreatePullRequestIterationAction(object):
+    @staticmethod
+    def is_user_authorized(old_pull_request):
+        """Performs authorization check with only the minimum amount of
+        information needed for such a check, rather than a full command
+        object.
+        """
+        if h.HasPermissionAny('hg.admin')():
+            return True
+
+        # Authorized to edit the old PR?
+        if request.authuser.user_id != old_pull_request.owner_id:
+            return False
+
+        # Authorized to create a new PR?
+        if not CreatePullRequestAction.is_user_authorized(old_pull_request.org_repo, old_pull_request.other_repo):
+            return False
+
+        return True
+
+    def __init__(self, old_pull_request, new_org_rev, new_other_rev, title, description, owner, reviewers):
+        self.old_pull_request = old_pull_request
+
+        org_repo = old_pull_request.org_repo
+        org_ref_type, org_ref_name, org_rev = old_pull_request.org_ref.split(':')
+
+        other_repo = old_pull_request.other_repo
+        other_ref_type, other_ref_name, other_rev = old_pull_request.other_ref.split(':') # other_rev is ancestor
+        #assert other_ref_type == 'branch', other_ref_type # TODO: what if not?
+
+        new_org_ref = '%s:%s:%s' % (org_ref_type, org_ref_name, new_org_rev)
+        new_other_ref = '%s:%s:%s' % (other_ref_type, other_ref_name, new_other_rev)
+
+        self.create_action = CreatePullRequestAction(org_repo, other_repo, new_org_ref, new_other_ref, None, None, owner, reviewers)
+
+        # Generate complete title/description
+
+        old_revisions = set(old_pull_request.revisions)
+        revisions = self.create_action.revisions
+        new_revisions = [r for r in revisions if r not in old_revisions]
+        lost = old_revisions.difference(revisions)
+
+        infos = ['This is a new iteration of %s "%s".' %
+                 (h.canonical_url('pullrequest_show', repo_name=old_pull_request.other_repo.repo_name,
+                      pull_request_id=old_pull_request.pull_request_id),
+                  old_pull_request.title)]
+
+        if lost:
+            infos.append(_('Missing changesets since the previous iteration:'))
+            for r in old_pull_request.revisions:
+                if r in lost:
+                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
+                    infos.append('  %s %s' % (h.short_id(r), rev_desc))
+
+        if new_revisions:
+            infos.append(_('New changesets on %s %s since the previous iteration:') % (org_ref_type, org_ref_name))
+            for r in reversed(revisions):
+                if r in new_revisions:
+                    rev_desc = org_repo.get_changeset(r).message.split('\n')[0]
+                    infos.append('  %s %s' % (h.short_id(r), h.shorter(rev_desc, 80)))
+
+            if self.create_action.other_ref == old_pull_request.other_ref:
+                infos.append(_("Ancestor didn't change - diff since previous iteration:"))
+                infos.append(h.canonical_url('compare_url',
+                                 repo_name=org_repo.repo_name, # other_repo is always same as repo_name
+                                 org_ref_type='rev', org_ref_name=h.short_id(org_rev), # use old org_rev as base
+                                 other_ref_type='rev', other_ref_name=h.short_id(new_org_rev),
+                                 )) # note: linear diff, merge or not doesn't matter
+            else:
+                infos.append(_('This iteration is based on another %s revision and there is no simple diff.') % other_ref_name)
+        else:
+           infos.append(_('No changes found on %s %s since previous iteration.') % (org_ref_type, org_ref_name))
+           # TODO: fail?
+
+        try:
+            title, old_v = re.match(r'(.*)\(v(\d+)\)\s*$', title).groups()
+            v = int(old_v) + 1
+        except (AttributeError, ValueError):
+            v = 2
+        self.create_action.title = '%s (v%s)' % (title.strip(), v)
+
+        # using a mail-like separator, insert new iteration info in description with latest first
+        descriptions = description.replace('\r\n', '\n').split('\n-- \n', 1)
+        description = descriptions[0].strip() + '\n\n-- \n' + '\n'.join(infos)
+        if len(descriptions) > 1:
+            description += '\n\n' + descriptions[1].strip()
+        self.create_action.description = description
+
+        if not CreatePullRequestIterationAction.is_user_authorized(self.old_pull_request):
+            raise CreatePullRequestAction.Unauthorized(_('You are not authorized to create the pull request'))
+
+    def execute(self):
+        pull_request = self.create_action.execute()
+
+        # Close old iteration
+        from kallithea.model.comment import ChangesetCommentsModel
+        ChangesetCommentsModel().create(
+            text=_('Closed, next iteration: %s .') % pull_request.url(canonical=True),
+            repo=self.old_pull_request.other_repo_id,
+            author=request.authuser.user_id,
+            pull_request=self.old_pull_request.pull_request_id,
+            closing_pr=True)
+        PullRequestModel().close_pull_request(self.old_pull_request.pull_request_id)
+        return pull_request
--- a/kallithea/model/repo.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/repo.py	Sun Mar 31 21:28:56 2019 +0200
@@ -33,40 +33,29 @@
 from datetime import datetime
 from sqlalchemy.orm import subqueryload
 
-import kallithea.lib.utils
+import kallithea.lib.utils2
 from kallithea.lib.utils import make_ui, is_valid_repo_uri
 from kallithea.lib.vcs.backends import get_backend
-from kallithea.lib.compat import json
 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
     remove_prefix, obfuscate_url_pw, get_current_authuser
 from kallithea.lib.caching_query import FromCache
 from kallithea.lib.hooks import log_delete_repository
 
-from kallithea.model import BaseModel
 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
-    UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \
+    UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, Session, \
     Statistics, UserGroup, Ui, RepoGroup, RepositoryField
 
 from kallithea.lib import helpers as h
-from kallithea.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
+from kallithea.lib.auth import HasRepoPermissionLevel, HasUserGroupPermissionLevel
 from kallithea.lib.exceptions import AttachedForksError
 from kallithea.model.scm import UserGroupList
 
 log = logging.getLogger(__name__)
 
 
-class RepoModel(BaseModel):
-
-    cls = Repository
-    URL_SEPARATOR = Repository.url_sep()
+class RepoModel(object):
 
-    def _get_user_group(self, users_group):
-        return self._get_instance(UserGroup, users_group,
-                                  callback=UserGroup.get_by_group_name)
-
-    def _get_repo_group(self, repo_group):
-        return self._get_instance(RepoGroup, repo_group,
-                                  callback=RepoGroup.get_by_group_name)
+    URL_SEPARATOR = Repository.url_sep()
 
     def _create_default_perms(self, repository, private):
         # create default permission
@@ -84,6 +73,7 @@
 
         repo_to_perm.repository = repository
         repo_to_perm.user_id = def_user.user_id
+        Session().add(repo_to_perm)
 
         return repo_to_perm
 
@@ -93,11 +83,11 @@
         Gets the repositories root path from database
         """
 
-        q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
+        q = Ui.query().filter(Ui.ui_key == '/').one()
         return q.ui_value
 
     def get(self, repo_id, cache=False):
-        repo = self.sa.query(Repository) \
+        repo = Repository.query() \
             .filter(Repository.repo_id == repo_id)
 
         if cache:
@@ -106,10 +96,10 @@
         return repo.scalar()
 
     def get_repo(self, repository):
-        return self._get_repo(repository)
+        return Repository.guess_instance(repository)
 
     def get_by_repo_name(self, repo_name, cache=False):
-        repo = self.sa.query(Repository) \
+        repo = Repository.query() \
             .filter(Repository.repo_name == repo_name)
 
         if cache:
@@ -124,74 +114,41 @@
         :param user:
         """
         from kallithea.lib.auth import AuthUser
-        user = self._get_user(user)
-        repos = AuthUser(user_id=user.user_id).permissions['repositories']
+        user = User.guess_instance(user)
+        repos = AuthUser(dbuser=user).permissions['repositories']
         access_check = lambda r: r[1] in ['repository.read',
                                           'repository.write',
                                           'repository.admin']
         repos = [x[0] for x in filter(access_check, repos.items())]
         return Repository.query().filter(Repository.repo_name.in_(repos))
 
-    def get_users_js(self):
-        users = self.sa.query(User).filter(User.active == True).all()
-        return json.dumps([
-            {
-                'id': u.user_id,
-                'fname': h.escape(u.name),
-                'lname': h.escape(u.lastname),
-                'nname': u.username,
-                'gravatar_lnk': h.gravatar_url(u.email, size=28),
-                'gravatar_size': 14,
-            } for u in users]
-        )
-
-    def get_user_groups_js(self):
-        user_groups = self.sa.query(UserGroup) \
-            .filter(UserGroup.users_group_active == True) \
-            .options(subqueryload(UserGroup.members)) \
-            .all()
-        user_groups = UserGroupList(user_groups, perm_set=['usergroup.read',
-                                                           'usergroup.write',
-                                                           'usergroup.admin'])
-        return json.dumps([
-            {
-                'id': gr.users_group_id,
-                'grname': gr.users_group_name,
-                'grmembers': len(gr.members),
-            } for gr in user_groups]
-        )
-
     @classmethod
     def _render_datatable(cls, tmpl, *args, **kwargs):
         import kallithea
-        from pylons import tmpl_context as c
-        from pylons.i18n.translation import _
+        from tg import tmpl_context as c, request, app_globals
+        from tg.i18n import ugettext as _
 
-        _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
+        _tmpl_lookup = app_globals.mako_lookup
         template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
 
         tmpl = template.get_def(tmpl)
-        kwargs.update(dict(_=_, h=h, c=c))
+        kwargs.update(dict(_=_, h=h, c=c, request=request))
         return tmpl.render(*args, **kwargs)
 
-    @classmethod
-    def update_repoinfo(cls, repositories=None):
-        if repositories is None:
-            repositories = Repository.getAll()
-        for repo in repositories:
-            repo.update_changeset_cache()
-
-    def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
-                          super_user_actions=False):
+    def get_repos_as_dict(self, repos_list, repo_groups_list=None,
+                          admin=False,
+                          short_name=False):
+        """Return repository list for use by DataTable.
+        repos_list: list of repositories - but will be filtered for read permission.
+        repo_groups_list: added at top of list without permission check.
+        admin: return data for action column.
+        """
         _render = self._render_datatable
-        from pylons import tmpl_context as c
-
-        def quick_menu(repo_name):
-            return _render('quick_menu', repo_name)
+        from tg import tmpl_context as c
 
         def repo_lnk(name, rtype, rstate, private, fork_of):
             return _render('repo_name', name, rtype, rstate, private, fork_of,
-                           short_name=not admin, admin=False)
+                           short_name=short_name)
 
         def last_change(last_change):
             return _render("last_change", last_change)
@@ -208,53 +165,54 @@
                            cs_cache.get('message'))
 
         def desc(desc):
-            return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
+            return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
 
         def state(repo_state):
             return _render("repo_state", repo_state)
 
         def repo_actions(repo_name):
-            return _render('repo_actions', repo_name, super_user_actions)
+            return _render('repo_actions', repo_name)
 
-        def owner_actions(user_id, username):
-            return _render('user_name', user_id, username)
+        def owner_actions(owner_id, username):
+            return _render('user_name', owner_id, username)
 
         repos_data = []
+
+        for gr in repo_groups_list or []:
+            repos_data.append(dict(
+                raw_name='\0' + gr.name, # sort before repositories
+                just_name=gr.name,
+                name=_render('group_name_html', group_name=gr.group_name, name=gr.name),
+                desc=gr.group_description))
+
         for repo in repos_list:
-            if perm_check:
-                # check permission at this level
-                if not HasRepoPermissionAny(
-                        'repository.read', 'repository.write',
-                        'repository.admin'
-                )(repo.repo_name, 'get_repos_as_dict check'):
-                    continue
+            if not HasRepoPermissionLevel('read')(repo.repo_name, 'get_repos_as_dict check'):
+                continue
             cs_cache = repo.changeset_cache
             row = {
-                "menu": quick_menu(repo.repo_name),
-                "raw_name": repo.repo_name.lower(),
+                "raw_name": repo.repo_name,
+                "just_name": repo.just_name,
                 "name": repo_lnk(repo.repo_name, repo.repo_type,
                                  repo.repo_state, repo.private, repo.fork),
+                "last_change_iso": repo.last_db_change.isoformat(),
                 "last_change": last_change(repo.last_db_change),
                 "last_changeset": last_rev(repo.repo_name, cs_cache),
                 "last_rev_raw": cs_cache.get('revision'),
                 "desc": desc(repo.description),
-                "owner": h.person(repo.user),
+                "owner": h.person(repo.owner),
                 "state": state(repo.repo_state),
                 "rss": rss_lnk(repo.repo_name),
                 "atom": atom_lnk(repo.repo_name),
-
             }
             if admin:
                 row.update({
                     "action": repo_actions(repo.repo_name),
-                    "owner": owner_actions(repo.user.user_id,
-                                           h.person(repo.user))
+                    "owner": owner_actions(repo.owner_id,
+                                           h.person(repo.owner))
                 })
             repos_data.append(row)
 
         return {
-            "totalRecords": len(repos_list),
-            "startIndex": 0,
             "sort": "name",
             "dir": "asc",
             "records": repos_data
@@ -274,10 +232,8 @@
             return None
 
         defaults = repo_info.get_dict()
-        group, repo_name, repo_name_full = repo_info.groups_and_repo
-        defaults['repo_name'] = repo_name
-        defaults['repo_group'] = getattr(group[-1] if group else None,
-                                         'group_id', None)
+        defaults['repo_name'] = repo_info.just_name
+        defaults['repo_group'] = repo_info.group_id
 
         for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
                          (1, 'repo_description'), (1, 'repo_enable_locking'),
@@ -295,12 +251,12 @@
                 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
 
         # fill owner
-        if repo_info.user:
-            defaults.update({'user': repo_info.user.username})
+        if repo_info.owner:
+            defaults.update({'owner': repo_info.owner.username})
         else:
             replacement_user = User.query().filter(User.admin ==
                                                    True).first().username
-            defaults.update({'user': replacement_user})
+            defaults.update({'owner': replacement_user})
 
         # fill repository users
         for p in repo_info.repo_to_perm:
@@ -316,12 +272,13 @@
 
     def update(self, repo, **kwargs):
         try:
-            cur_repo = self._get_repo(repo)
+            cur_repo = Repository.guess_instance(repo)
             org_repo_name = cur_repo.repo_name
-            if 'user' in kwargs:
-                cur_repo.user = User.get_by_username(kwargs['user'])
+            if 'owner' in kwargs:
+                cur_repo.owner = User.get_by_username(kwargs['owner'])
 
             if 'repo_group' in kwargs:
+                assert kwargs['repo_group'] != u'-1', kwargs # RepoForm should have converted to None
                 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
                 cur_repo.repo_name = cur_repo.get_new_name(cur_repo.just_name)
             log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
@@ -344,25 +301,23 @@
 
             if 'repo_name' in kwargs:
                 repo_name = kwargs['repo_name']
-                if kallithea.lib.utils.repo_name_slug(repo_name) != repo_name:
+                if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
                     raise Exception('invalid repo name %s' % repo_name)
                 cur_repo.repo_name = cur_repo.get_new_name(repo_name)
 
-            #if private flag is set, reset default permission to NONE
+            # if private flag is set, reset default permission to NONE
             if kwargs.get('repo_private'):
                 EMPTY_PERM = 'repository.none'
                 RepoModel().grant_user_permission(
                     repo=cur_repo, user='default', perm=EMPTY_PERM
                 )
-                #handle extra fields
+                # handle extra fields
             for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
                                 kwargs):
                 k = RepositoryField.un_prefix_key(field)
                 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
                 if ex_field:
                     ex_field.field_value = kwargs[field]
-                    self.sa.add(ex_field)
-            self.sa.add(cur_repo)
 
             if org_repo_name != cur_repo.repo_name:
                 # rename repository
@@ -386,9 +341,9 @@
         """
         from kallithea.model.scm import ScmModel
 
-        owner = self._get_user(owner)
-        fork_of = self._get_repo(fork_of)
-        repo_group = self._get_repo_group(repo_group)
+        owner = User.guess_instance(owner)
+        fork_of = Repository.guess_instance(fork_of)
+        repo_group = RepoGroup.guess_instance(repo_group)
         try:
             repo_name = safe_unicode(repo_name)
             description = safe_unicode(description)
@@ -397,7 +352,7 @@
             # with name and path of group
             repo_name_full = repo_name
             repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
-            if kallithea.lib.utils.repo_name_slug(repo_name) != repo_name:
+            if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
                 raise Exception('invalid repo name %s' % repo_name)
 
             new_repo = Repository()
@@ -405,7 +360,7 @@
             new_repo.enable_statistics = False
             new_repo.repo_name = repo_name_full
             new_repo.repo_type = repo_type
-            new_repo.user = owner
+            new_repo.owner = owner
             new_repo.group = repo_group
             new_repo.description = description or repo_name
             new_repo.private = private
@@ -426,7 +381,7 @@
                 parent_repo = fork_of
                 new_repo.fork = parent_repo
 
-            self.sa.add(new_repo)
+            Session().add(new_repo)
 
             if fork_of and copy_fork_permissions:
                 repo = fork_of
@@ -461,15 +416,13 @@
                     UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
 
             else:
-                perm_obj = self._create_default_perms(new_repo, private)
-                self.sa.add(perm_obj)
+                self._create_default_perms(new_repo, private)
 
             # now automatically start following this repository as owner
-            ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
-                                                    owner.user_id)
+            ScmModel().toggle_following_repo(new_repo.repo_id, owner.user_id)
             # we need to flush here, in order to check if database won't
             # throw any exceptions, create filesystem dirs at the very end
-            self.sa.flush()
+            Session().flush()
             return new_repo
         except Exception:
             log.error(traceback.format_exc())
@@ -482,8 +435,8 @@
         :param form_data:
         :param cur_user:
         """
-        from kallithea.lib.celerylib import tasks, run_task
-        return run_task(tasks.create_repo, form_data, cur_user)
+        from kallithea.lib.celerylib import tasks
+        return tasks.create_repo(form_data, cur_user)
 
     def _update_permissions(self, repo, perms_new=None, perms_updates=None,
                             check_perms=True):
@@ -500,11 +453,8 @@
                     repo=repo, user=member, perm=perm
                 )
             else:
-                #check if we have permissions to alter this usergroup
-                req_perms = (
-                    'usergroup.read', 'usergroup.write', 'usergroup.admin')
-                if not check_perms or HasUserGroupPermissionAny(*req_perms)(
-                        member):
+                # check if we have permissions to alter this usergroup's access
+                if not check_perms or HasUserGroupPermissionLevel('read')(member):
                     self.grant_user_group_permission(
                         repo=repo, group_name=member, perm=perm
                     )
@@ -515,11 +465,8 @@
                     repo=repo, user=member, perm=perm
                 )
             else:
-                #check if we have permissions to alter this usergroup
-                req_perms = (
-                    'usergroup.read', 'usergroup.write', 'usergroup.admin')
-                if not check_perms or HasUserGroupPermissionAny(*req_perms)(
-                        member):
+                # check if we have permissions to alter this usergroup's access
+                if not check_perms or HasUserGroupPermissionLevel('read')(member):
                     self.grant_user_group_permission(
                         repo=repo, group_name=member, perm=perm
                     )
@@ -531,8 +478,8 @@
         :param form_data:
         :param cur_user:
         """
-        from kallithea.lib.celerylib import tasks, run_task
-        return run_task(tasks.create_repo_fork, form_data, cur_user)
+        from kallithea.lib.celerylib import tasks
+        return tasks.create_repo_fork(form_data, cur_user)
 
     def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
         """
@@ -546,12 +493,11 @@
         """
         if not cur_user:
             cur_user = getattr(get_current_authuser(), 'username', None)
-        repo = self._get_repo(repo)
+        repo = Repository.guess_instance(repo)
         if repo is not None:
             if forks == 'detach':
                 for r in repo.forks:
                     r.fork = None
-                    self.sa.add(r)
             elif forks == 'delete':
                 for r in repo.forks:
                     self.delete(r, forks='delete')
@@ -560,7 +506,7 @@
 
             old_repo_dict = repo.get_dict()
             try:
-                self.sa.delete(repo)
+                Session().delete(repo)
                 if fs_remove:
                     self._delete_filesystem_repo(repo)
                 else:
@@ -580,22 +526,22 @@
         :param user: Instance of User, user_id or username
         :param perm: Instance of Permission, or permission_name
         """
-        user = self._get_user(user)
-        repo = self._get_repo(repo)
-        permission = self._get_perm(perm)
+        user = User.guess_instance(user)
+        repo = Repository.guess_instance(repo)
+        permission = Permission.guess_instance(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserRepoToPerm) \
+        obj = UserRepoToPerm.query() \
             .filter(UserRepoToPerm.user == user) \
             .filter(UserRepoToPerm.repository == repo) \
             .scalar()
         if obj is None:
             # create new !
             obj = UserRepoToPerm()
+            Session().add(obj)
         obj.repository = repo
         obj.user = user
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, user, repo)
         return obj
 
@@ -607,15 +553,15 @@
         :param user: Instance of User, user_id or username
         """
 
-        user = self._get_user(user)
-        repo = self._get_repo(repo)
+        user = User.guess_instance(user)
+        repo = Repository.guess_instance(repo)
 
-        obj = self.sa.query(UserRepoToPerm) \
+        obj = UserRepoToPerm.query() \
             .filter(UserRepoToPerm.repository == repo) \
             .filter(UserRepoToPerm.user == user) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm on %s on %s', repo, user)
 
     def grant_user_group_permission(self, repo, group_name, perm):
@@ -628,12 +574,12 @@
             or user group name
         :param perm: Instance of Permission, or permission_name
         """
-        repo = self._get_repo(repo)
-        group_name = self._get_user_group(group_name)
-        permission = self._get_perm(perm)
+        repo = Repository.guess_instance(repo)
+        group_name = UserGroup.guess_instance(group_name)
+        permission = Permission.guess_instance(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserGroupRepoToPerm) \
+        obj = UserGroupRepoToPerm.query() \
             .filter(UserGroupRepoToPerm.users_group == group_name) \
             .filter(UserGroupRepoToPerm.repository == repo) \
             .scalar()
@@ -641,11 +587,11 @@
         if obj is None:
             # create new
             obj = UserGroupRepoToPerm()
+            Session().add(obj)
 
         obj.repository = repo
         obj.users_group = group_name
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
         return obj
 
@@ -657,15 +603,15 @@
         :param group_name: Instance of UserGroup, users_group_id,
             or user group name
         """
-        repo = self._get_repo(repo)
-        group_name = self._get_user_group(group_name)
+        repo = Repository.guess_instance(repo)
+        group_name = UserGroup.guess_instance(group_name)
 
-        obj = self.sa.query(UserGroupRepoToPerm) \
+        obj = UserGroupRepoToPerm.query() \
             .filter(UserGroupRepoToPerm.repository == repo) \
             .filter(UserGroupRepoToPerm.users_group == group_name) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm to %s on %s', repo, group_name)
 
     def delete_stats(self, repo_name):
@@ -674,12 +620,12 @@
 
         :param repo_name:
         """
-        repo = self._get_repo(repo_name)
+        repo = Repository.guess_instance(repo_name)
         try:
-            obj = self.sa.query(Statistics) \
+            obj = Statistics.query() \
                 .filter(Statistics.repository == repo).scalar()
             if obj is not None:
-                self.sa.delete(obj)
+                Session().delete(obj)
         except Exception:
             log.error(traceback.format_exc())
             raise
@@ -778,4 +724,7 @@
         if repo.group:
             args = repo.group.full_path_splitted + [_d]
             _d = os.path.join(*args)
-        shutil.move(rm_path, safe_str(os.path.join(self.repos_path, _d)))
+        if os.path.exists(rm_path):
+            shutil.move(rm_path, safe_str(os.path.join(self.repos_path, _d)))
+        else:
+            log.error("Can't find repo to delete in %r", rm_path)
--- a/kallithea/model/repo_group.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/repo_group.py	Sun Mar 31 21:28:56 2019 +0200
@@ -32,27 +32,16 @@
 import shutil
 import datetime
 
-import kallithea.lib.utils
+import kallithea.lib.utils2
 from kallithea.lib.utils2 import LazyProperty
 
-from kallithea.model import BaseModel
-from kallithea.model.db import RepoGroup, Ui, UserRepoGroupToPerm, \
+from kallithea.model.db import RepoGroup, Session, Ui, UserRepoGroupToPerm, \
     User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
 
 log = logging.getLogger(__name__)
 
 
-class RepoGroupModel(BaseModel):
-
-    cls = RepoGroup
-
-    def _get_user_group(self, users_group):
-        return self._get_instance(UserGroup, users_group,
-                                  callback=UserGroup.get_by_group_name)
-
-    def _get_repo_group(self, repo_group):
-        return self._get_instance(RepoGroup, repo_group,
-                                  callback=RepoGroup.get_by_group_name)
+class RepoGroupModel(object):
 
     @LazyProperty
     def repos_path(self):
@@ -60,7 +49,7 @@
         Gets the repositories root path from database
         """
 
-        q = Ui.get_by_key('/')
+        q = Ui.get_by_key('paths', '/')
         return q.ui_value
 
     def _create_default_perms(self, new_group):
@@ -77,6 +66,7 @@
 
         repo_group_to_perm.group = new_group
         repo_group_to_perm.user_id = def_user.user_id
+        Session().add(repo_group_to_perm)
         return repo_group_to_perm
 
     def _create_group(self, group_name):
@@ -136,7 +126,7 @@
             if force_delete:
                 shutil.rmtree(rm_path)
             else:
-                #archive that group`
+                # archive that group
                 _now = datetime.datetime.now()
                 _ms = str(_now.microsecond).rjust(6, '0')
                 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
@@ -146,22 +136,22 @@
     def create(self, group_name, group_description, owner, parent=None,
                just_db=False, copy_permissions=False):
         try:
-            if kallithea.lib.utils.repo_name_slug(group_name) != group_name:
+            if kallithea.lib.utils2.repo_name_slug(group_name) != group_name:
                 raise Exception('invalid repo group name %s' % group_name)
 
-            user = self._get_user(owner)
-            parent_group = self._get_repo_group(parent)
+            owner = User.guess_instance(owner)
+            parent_group = RepoGroup.guess_instance(parent)
             new_repo_group = RepoGroup()
-            new_repo_group.user = user
+            new_repo_group.owner = owner
             new_repo_group.group_description = group_description or group_name
             new_repo_group.parent_group = parent_group
             new_repo_group.group_name = new_repo_group.get_new_name(group_name)
 
-            self.sa.add(new_repo_group)
+            Session().add(new_repo_group)
 
             # create an ADMIN permission for owner except if we're super admin,
             # later owner should go into the owner field of groups
-            if not user.is_admin:
+            if not owner.is_admin:
                 self.grant_user_permission(repo_group=new_repo_group,
                                            user=owner, perm='group.admin')
 
@@ -177,19 +167,18 @@
                     # don't copy over the permission for user who is creating
                     # this group, if he is not super admin he get's admin
                     # permission set above
-                    if perm.user != user or user.is_admin:
+                    if perm.user != owner or owner.is_admin:
                         UserRepoGroupToPerm.create(perm.user, new_repo_group, perm.permission)
 
                 for perm in group_perms:
                     UserGroupRepoGroupToPerm.create(perm.users_group, new_repo_group, perm.permission)
             else:
-                perm_obj = self._create_default_perms(new_repo_group)
-                self.sa.add(perm_obj)
+                self._create_default_perms(new_repo_group)
 
             if not just_db:
                 # we need to flush here, in order to check if database won't
                 # throw any exceptions, create filesystem dirs at the very end
-                self.sa.flush()
+                Session().flush()
                 self._create_group(new_repo_group.group_name)
 
             return new_repo_group
@@ -201,7 +190,7 @@
                             perms_updates=None, recursive=None,
                             check_perms=True):
         from kallithea.model.repo import RepoModel
-        from kallithea.lib.auth import HasUserGroupPermissionAny
+        from kallithea.lib.auth import HasUserGroupPermissionLevel
 
         if not perms_new:
             perms_new = []
@@ -212,9 +201,11 @@
             if isinstance(obj, RepoGroup):
                 self.grant_user_permission(repo_group=obj, user=user, perm=perm)
             elif isinstance(obj, Repository):
+                user = User.guess_instance(user)
+
                 # private repos will not allow to change the default permissions
                 # using recursive mode
-                if obj.private and user == User.DEFAULT_USER:
+                if obj.private and user.is_default_user:
                     return
 
                 # we set group permission but we have to switch to repo
@@ -246,7 +237,7 @@
             # iterated obj is an instance of a repos group or repository in
             # that group, recursive option can be: none, repos, groups, all
             if recursive == 'all':
-                obj = obj
+                pass
             elif recursive == 'repos':
                 # skip groups, other than this one
                 if isinstance(obj, RepoGroup) and not obj == repo_group:
@@ -267,18 +258,16 @@
                     _set_perm_user(obj, user=member, perm=perm)
                 ## set for user group
                 else:
-                    #check if we have permissions to alter this usergroup
-                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
+                    # check if we have permissions to alter this usergroup's access
+                    if not check_perms or HasUserGroupPermissionLevel('read')(member):
                         _set_perm_group(obj, users_group=member, perm=perm)
             # set new permissions
             for member, perm, member_type in perms_new:
                 if member_type == 'user':
                     _set_perm_user(obj, user=member, perm=perm)
                 else:
-                    #check if we have permissions to alter this usergroup
-                    req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
-                    if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
+                    # check if we have permissions to alter this usergroup's access
+                    if not check_perms or HasUserGroupPermissionLevel('read')(member):
                         _set_perm_group(obj, users_group=member, perm=perm)
             updates.append(obj)
             # if it's not recursive call for all,repos,groups
@@ -288,24 +277,29 @@
 
         return updates
 
-    def update(self, repo_group, form_data):
-
+    def update(self, repo_group, repo_group_args):
         try:
-            repo_group = self._get_repo_group(repo_group)
+            repo_group = RepoGroup.guess_instance(repo_group)
             old_path = repo_group.full_path
 
             # change properties
-            repo_group.group_description = form_data['group_description']
-            repo_group.group_parent_id = form_data['group_parent_id']
-            repo_group.enable_locking = form_data['enable_locking']
+            if 'group_description' in repo_group_args:
+                repo_group.group_description = repo_group_args['group_description']
+            if 'parent_group_id' in repo_group_args:
+                repo_group.parent_group_id = repo_group_args['parent_group_id']
+            if 'enable_locking' in repo_group_args:
+                repo_group.enable_locking = repo_group_args['enable_locking']
 
-            repo_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
-            group_name = form_data['group_name']
-            if kallithea.lib.utils.repo_name_slug(group_name) != group_name:
-                raise Exception('invalid repo group name %s' % group_name)
-            repo_group.group_name = repo_group.get_new_name(group_name)
+            if 'parent_group_id' in repo_group_args:
+                assert repo_group_args['parent_group_id'] != u'-1', repo_group_args  # RepoGroupForm should have converted to None
+                repo_group.parent_group = RepoGroup.get(repo_group_args['parent_group_id'])
+            if 'group_name' in repo_group_args:
+                group_name = repo_group_args['group_name']
+                if kallithea.lib.utils2.repo_name_slug(group_name) != group_name:
+                    raise Exception('invalid repo group name %s' % group_name)
+                repo_group.group_name = repo_group.get_new_name(group_name)
             new_path = repo_group.full_path
-            self.sa.add(repo_group)
+            Session().add(repo_group)
 
             # iterate over all members of this groups and do fixes
             # set locking if given
@@ -314,7 +308,7 @@
             # if obj is a Repo fix it's name
             # this can be potentially heavy operation
             for obj in repo_group.recursive_groups_and_repos():
-                #set the value from it's parent
+                # set the value from it's parent
                 obj.enable_locking = repo_group.enable_locking
                 if isinstance(obj, RepoGroup):
                     new_name = obj.get_new_name(obj.name)
@@ -328,7 +322,6 @@
                     log.debug('Fixing repo %s to new name %s' \
                                 % (obj.repo_name, new_name))
                     obj.repo_name = new_name
-                self.sa.add(obj)
 
             self._rename_group(old_path, new_path)
 
@@ -338,9 +331,9 @@
             raise
 
     def delete(self, repo_group, force_delete=False):
-        repo_group = self._get_repo_group(repo_group)
+        repo_group = RepoGroup.guess_instance(repo_group)
         try:
-            self.sa.delete(repo_group)
+            Session().delete(repo_group)
             self._delete_group(repo_group, force_delete)
         except Exception:
             log.error('Error removing repo_group %s', repo_group)
@@ -348,14 +341,14 @@
 
     def add_permission(self, repo_group, obj, obj_type, perm, recursive):
         from kallithea.model.repo import RepoModel
-        repo_group = self._get_repo_group(repo_group)
-        perm = self._get_perm(perm)
+        repo_group = RepoGroup.guess_instance(repo_group)
+        perm = Permission.guess_instance(perm)
 
         for el in repo_group.recursive_groups_and_repos():
             # iterated obj is an instance of a repos group or repository in
             # that group, recursive option can be: none, repos, groups, all
             if recursive == 'all':
-                el = el
+                pass
             elif recursive == 'repos':
                 # skip groups, other than this one
                 if isinstance(el, RepoGroup) and not el == repo_group:
@@ -404,13 +397,13 @@
         :param recursive: recurse to all children of group
         """
         from kallithea.model.repo import RepoModel
-        repo_group = self._get_repo_group(repo_group)
+        repo_group = RepoGroup.guess_instance(repo_group)
 
         for el in repo_group.recursive_groups_and_repos():
             # iterated obj is an instance of a repos group or repository in
             # that group, recursive option can be: none, repos, groups, all
             if recursive == 'all':
-                el = el
+                pass
             elif recursive == 'repos':
                 # skip groups, other than this one
                 if isinstance(el, RepoGroup) and not el == repo_group:
@@ -457,22 +450,22 @@
         :param perm: Instance of Permission, or permission_name
         """
 
-        repo_group = self._get_repo_group(repo_group)
-        user = self._get_user(user)
-        permission = self._get_perm(perm)
+        repo_group = RepoGroup.guess_instance(repo_group)
+        user = User.guess_instance(user)
+        permission = Permission.guess_instance(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserRepoGroupToPerm)\
-            .filter(UserRepoGroupToPerm.user == user)\
-            .filter(UserRepoGroupToPerm.group == repo_group)\
+        obj = UserRepoGroupToPerm.query() \
+            .filter(UserRepoGroupToPerm.user == user) \
+            .filter(UserRepoGroupToPerm.group == repo_group) \
             .scalar()
         if obj is None:
             # create new !
             obj = UserRepoGroupToPerm()
+            Session().add(obj)
         obj.group = repo_group
         obj.user = user
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
         return obj
 
@@ -485,15 +478,15 @@
         :param user: Instance of User, user_id or username
         """
 
-        repo_group = self._get_repo_group(repo_group)
-        user = self._get_user(user)
+        repo_group = RepoGroup.guess_instance(repo_group)
+        user = User.guess_instance(user)
 
-        obj = self.sa.query(UserRepoGroupToPerm)\
-            .filter(UserRepoGroupToPerm.user == user)\
-            .filter(UserRepoGroupToPerm.group == repo_group)\
+        obj = UserRepoGroupToPerm.query() \
+            .filter(UserRepoGroupToPerm.user == user) \
+            .filter(UserRepoGroupToPerm.group == repo_group) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm on %s on %s', repo_group, user)
 
     def grant_user_group_permission(self, repo_group, group_name, perm):
@@ -507,24 +500,24 @@
             or user group name
         :param perm: Instance of Permission, or permission_name
         """
-        repo_group = self._get_repo_group(repo_group)
-        group_name = self._get_user_group(group_name)
-        permission = self._get_perm(perm)
+        repo_group = RepoGroup.guess_instance(repo_group)
+        group_name = UserGroup.guess_instance(group_name)
+        permission = Permission.guess_instance(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserGroupRepoGroupToPerm)\
-            .filter(UserGroupRepoGroupToPerm.group == repo_group)\
-            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
+        obj = UserGroupRepoGroupToPerm.query() \
+            .filter(UserGroupRepoGroupToPerm.group == repo_group) \
+            .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
             .scalar()
 
         if obj is None:
             # create new
             obj = UserGroupRepoGroupToPerm()
+            Session().add(obj)
 
         obj.group = repo_group
         obj.users_group = group_name
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
         return obj
 
@@ -537,13 +530,13 @@
         :param group_name: Instance of UserGroup, users_group_id,
             or user group name
         """
-        repo_group = self._get_repo_group(repo_group)
-        group_name = self._get_user_group(group_name)
+        repo_group = RepoGroup.guess_instance(repo_group)
+        group_name = UserGroup.guess_instance(group_name)
 
-        obj = self.sa.query(UserGroupRepoGroupToPerm)\
-            .filter(UserGroupRepoGroupToPerm.group == repo_group)\
-            .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
+        obj = UserGroupRepoGroupToPerm.query() \
+            .filter(UserGroupRepoGroupToPerm.group == repo_group) \
+            .filter(UserGroupRepoGroupToPerm.users_group == group_name) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm to %s on %s', repo_group, group_name)
--- a/kallithea/model/repo_permission.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/repo_permission.py	Sun Mar 31 21:28:56 2019 +0200
@@ -24,20 +24,17 @@
 """
 
 import logging
-from kallithea.model import BaseModel
-from kallithea.model.db import UserRepoToPerm, UserGroupRepoToPerm, \
-    Permission
+from kallithea.model.db import User, UserRepoToPerm, UserGroupRepoToPerm, \
+    Permission, Repository, Session
 
 log = logging.getLogger(__name__)
 
 
-class RepositoryPermissionModel(BaseModel):
-
-    cls = UserRepoToPerm
+class RepositoryPermissionModel(object):
 
     def get_user_permission(self, repository, user):
-        repository = self._get_repo(repository)
-        user = self._get_user(user)
+        repository = Repository.guess_instance(repository)
+        user = User.guess_instance(user)
 
         return UserRepoToPerm.query() \
                 .filter(UserRepoToPerm.user == user) \
@@ -48,19 +45,19 @@
         permission = Permission.get_by_key(permission)
         current = self.get_user_permission(repository, user)
         if current:
-            if not current.permission is permission:
+            if current.permission is not permission:
                 current.permission = permission
         else:
             p = UserRepoToPerm()
             p.user = user
             p.repository = repository
             p.permission = permission
-            self.sa.add(p)
+            Session().add(p)
 
     def delete_user_permission(self, repository, user):
         current = self.get_user_permission(repository, user)
         if current:
-            self.sa.delete(current)
+            Session().delete(current)
 
     def get_users_group_permission(self, repository, users_group):
         return UserGroupRepoToPerm.query() \
@@ -73,19 +70,19 @@
         permission = Permission.get_by_key(permission)
         current = self.get_users_group_permission(repository, users_group)
         if current:
-            if not current.permission is permission:
+            if current.permission is not permission:
                 current.permission = permission
         else:
             p = UserGroupRepoToPerm()
             p.users_group = users_group
             p.repository = repository
             p.permission = permission
-            self.sa.add(p)
+            Session().add(p)
 
     def delete_users_group_permission(self, repository, users_group):
         current = self.get_users_group_permission(repository, users_group)
         if current:
-            self.sa.delete(current)
+            Session().delete(current)
 
     def update_or_delete_user_permission(self, repository, user, permission):
         if permission:
--- a/kallithea/model/scm.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/scm.py	Sun Mar 31 21:28:56 2019 +0200
@@ -26,16 +26,17 @@
 """
 
 import os
+import sys
+import posixpath
 import re
 import time
 import traceback
 import logging
 import cStringIO
 import pkg_resources
-from os.path import join as jn
 
 from sqlalchemy import func
-from pylons.i18n.translation import _
+from tg.i18n import ugettext as _
 
 import kallithea
 from kallithea.lib.vcs import get_backend
@@ -46,14 +47,13 @@
 
 from kallithea import BACKENDS
 from kallithea.lib import helpers as h
-from kallithea.lib.utils2 import safe_str, safe_unicode, get_server_url,\
+from kallithea.lib.utils2 import safe_str, safe_unicode, get_server_url, \
     _set_extras
-from kallithea.lib.auth import HasRepoPermissionAny, HasRepoGroupPermissionAny,\
-    HasUserGroupPermissionAny, HasPermissionAny, HasPermissionAll
+from kallithea.lib.auth import HasRepoPermissionLevel, HasRepoGroupPermissionLevel, \
+    HasUserGroupPermissionLevel, HasPermissionAny, HasPermissionAny
 from kallithea.lib.utils import get_filesystem_repos, make_ui, \
     action_logger
-from kallithea.model import BaseModel
-from kallithea.model.db import Repository, Ui, CacheInvalidation, \
+from kallithea.model.db import Repository, Session, Ui, CacheInvalidation, \
     UserFollowing, UserLog, User, RepoGroup, PullRequest
 from kallithea.lib.hooks import log_push_action
 from kallithea.lib.exceptions import NonRelativePathError, IMCCommitError
@@ -77,92 +77,6 @@
         return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
 
 
-class CachedRepoList(object):
-    """
-    Cached repo list. Uses super-fast in-memory cache after initialization.
-    """
-
-    def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
-        self.db_repo_list = db_repo_list
-        self.repos_path = repos_path
-        self.order_by = order_by
-        self.reversed = (order_by or '').startswith('-')
-        if not perm_set:
-            perm_set = ['repository.read', 'repository.write',
-                        'repository.admin']
-        self.perm_set = perm_set
-
-    def __len__(self):
-        return len(self.db_repo_list)
-
-    def __repr__(self):
-        return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
-
-    def __iter__(self):
-        # pre-propagated valid_cache_keys to save executing select statements
-        # for each repo
-        valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
-
-        for dbr in self.db_repo_list:
-            scmr = dbr.scm_instance_cached(valid_cache_keys)
-            # check permission at this level
-            if not HasRepoPermissionAny(
-                *self.perm_set)(dbr.repo_name, 'get repo check'):
-                continue
-
-            try:
-                last_change = scmr.last_change
-                tip = h.get_changeset_safe(scmr, 'tip')
-            except Exception:
-                log.error(
-                    '%s this repository is present in database but it '
-                    'cannot be created as an scm instance, org_exc:%s'
-                    % (dbr.repo_name, traceback.format_exc())
-                )
-                continue
-
-            tmp_d = {}
-            tmp_d['name'] = dbr.repo_name
-            tmp_d['name_sort'] = tmp_d['name'].lower()
-            tmp_d['raw_name'] = tmp_d['name'].lower()
-            tmp_d['description'] = dbr.description
-            tmp_d['description_sort'] = tmp_d['description'].lower()
-            tmp_d['last_change'] = last_change
-            tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
-            tmp_d['tip'] = tip.raw_id
-            tmp_d['tip_sort'] = tip.revision
-            tmp_d['rev'] = tip.revision
-            tmp_d['contact'] = dbr.user.full_contact
-            tmp_d['contact_sort'] = tmp_d['contact']
-            tmp_d['owner_sort'] = tmp_d['contact']
-            tmp_d['repo_archives'] = list(scmr._get_archives())
-            tmp_d['last_msg'] = tip.message
-            tmp_d['author'] = tip.author
-            tmp_d['dbrepo'] = dbr.get_dict()
-            tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
-            yield tmp_d
-
-
-class SimpleCachedRepoList(CachedRepoList):
-    """
-    Lighter version of CachedRepoList without the scm initialisation
-    """
-
-    def __iter__(self):
-        for dbr in self.db_repo_list:
-            # check permission at this level
-            if not HasRepoPermissionAny(
-                *self.perm_set)(dbr.repo_name, 'get repo check'):
-                continue
-
-            tmp_d = {
-                'name': dbr.repo_name,
-                'dbrepo': dbr.get_dict(),
-                'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
-            }
-            yield tmp_d
-
-
 class _PermCheckIterator(object):
     def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
         """
@@ -199,41 +113,32 @@
 
 class RepoList(_PermCheckIterator):
 
-    def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
-        if not perm_set:
-            perm_set = ['repository.read', 'repository.write', 'repository.admin']
-
+    def __init__(self, db_repo_list, perm_level, extra_kwargs=None):
         super(RepoList, self).__init__(obj_list=db_repo_list,
-                    obj_attr='repo_name', perm_set=perm_set,
-                    perm_checker=HasRepoPermissionAny,
+                    obj_attr='repo_name', perm_set=[perm_level],
+                    perm_checker=HasRepoPermissionLevel,
                     extra_kwargs=extra_kwargs)
 
 
 class RepoGroupList(_PermCheckIterator):
 
-    def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
-        if not perm_set:
-            perm_set = ['group.read', 'group.write', 'group.admin']
-
+    def __init__(self, db_repo_group_list, perm_level, extra_kwargs=None):
         super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
-                    obj_attr='group_name', perm_set=perm_set,
-                    perm_checker=HasRepoGroupPermissionAny,
+                    obj_attr='group_name', perm_set=[perm_level],
+                    perm_checker=HasRepoGroupPermissionLevel,
                     extra_kwargs=extra_kwargs)
 
 
 class UserGroupList(_PermCheckIterator):
 
-    def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
-        if not perm_set:
-            perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
-
+    def __init__(self, db_user_group_list, perm_level, extra_kwargs=None):
         super(UserGroupList, self).__init__(obj_list=db_user_group_list,
-                    obj_attr='users_group_name', perm_set=perm_set,
-                    perm_checker=HasUserGroupPermissionAny,
+                    obj_attr='users_group_name', perm_set=[perm_level],
+                    perm_checker=HasUserGroupPermissionLevel,
                     extra_kwargs=extra_kwargs)
 
 
-class ScmModel(BaseModel):
+class ScmModel(object):
     """
     Generic Scm Model
     """
@@ -256,7 +161,7 @@
         Gets the repositories root path from database
         """
 
-        q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
+        q = Ui.query().filter(Ui.ui_key == '/').one()
 
         return q.ui_value
 
@@ -299,58 +204,39 @@
         log.debug('found %s paths with repositories', len(repos))
         return repos
 
-    def get_repos(self, all_repos=None, sort_key=None, simple=False):
-        """
-        Get all repos from db and for each repo create its
-        backend instance and fill that backed with information from database
+    def get_repos(self, repos):
+        """Return the repos the user has access to"""
+        return RepoList(repos, perm_level='read')
 
-        :param all_repos: list of repository names as strings
-            give specific repositories list, good for filtering
-
-        :param sort_key: initial sorting of repos
-        :param simple: use SimpleCachedList - one without the SCM info
+    def get_repo_groups(self, groups=None):
+        """Return the repo groups the user has access to
+        If no groups are specified, use top level groups.
         """
-        if all_repos is None:
-            all_repos = self.sa.query(Repository)\
-                        .filter(Repository.group_id == None)\
-                        .order_by(func.lower(Repository.repo_name)).all()
-        if simple:
-            repo_iter = SimpleCachedRepoList(all_repos,
-                                             repos_path=self.repos_path,
-                                             order_by=sort_key)
-        else:
-            repo_iter = CachedRepoList(all_repos,
-                                       repos_path=self.repos_path,
-                                       order_by=sort_key)
+        if groups is None:
+            groups = RepoGroup.query() \
+                .filter(RepoGroup.parent_group_id == None).all()
+        return RepoGroupList(groups, perm_level='read')
 
-        return repo_iter
-
-    def get_repo_groups(self, all_groups=None):
-        if all_groups is None:
-            all_groups = RepoGroup.query()\
-                .filter(RepoGroup.group_parent_id == None).all()
-        return [x for x in RepoGroupList(all_groups)]
-
-    def mark_for_invalidation(self, repo_name, delete=False):
+    def mark_for_invalidation(self, repo_name):
         """
         Mark caches of this repo invalid in the database.
 
         :param repo_name: the repo for which caches should be marked invalid
         """
-        CacheInvalidation.set_invalidate(repo_name, delete=delete)
+        CacheInvalidation.set_invalidate(repo_name)
         repo = Repository.get_by_repo_name(repo_name)
         if repo is not None:
             repo.update_changeset_cache()
 
     def toggle_following_repo(self, follow_repo_id, user_id):
 
-        f = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.follows_repo_id == follow_repo_id)\
+        f = UserFollowing.query() \
+            .filter(UserFollowing.follows_repository_id == follow_repo_id) \
             .filter(UserFollowing.user_id == user_id).scalar()
 
         if f is not None:
             try:
-                self.sa.delete(f)
+                Session().delete(f)
                 action_logger(UserTemp(user_id),
                               'stopped_following_repo',
                               RepoTemp(follow_repo_id))
@@ -362,8 +248,8 @@
         try:
             f = UserFollowing()
             f.user_id = user_id
-            f.follows_repo_id = follow_repo_id
-            self.sa.add(f)
+            f.follows_repository_id = follow_repo_id
+            Session().add(f)
 
             action_logger(UserTemp(user_id),
                           'started_following_repo',
@@ -373,13 +259,13 @@
             raise
 
     def toggle_following_user(self, follow_user_id, user_id):
-        f = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.follows_user_id == follow_user_id)\
+        f = UserFollowing.query() \
+            .filter(UserFollowing.follows_user_id == follow_user_id) \
             .filter(UserFollowing.user_id == user_id).scalar()
 
         if f is not None:
             try:
-                self.sa.delete(f)
+                Session().delete(f)
                 return
             except Exception:
                 log.error(traceback.format_exc())
@@ -389,17 +275,17 @@
             f = UserFollowing()
             f.user_id = user_id
             f.follows_user_id = follow_user_id
-            self.sa.add(f)
+            Session().add(f)
         except Exception:
             log.error(traceback.format_exc())
             raise
 
     def is_following_repo(self, repo_name, user_id, cache=False):
-        r = self.sa.query(Repository)\
+        r = Repository.query() \
             .filter(Repository.repo_name == repo_name).scalar()
 
-        f = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.follows_repository == r)\
+        f = UserFollowing.query() \
+            .filter(UserFollowing.follows_repository == r) \
             .filter(UserFollowing.user_id == user_id).scalar()
 
         return f is not None
@@ -407,27 +293,27 @@
     def is_following_user(self, username, user_id, cache=False):
         u = User.get_by_username(username)
 
-        f = self.sa.query(UserFollowing)\
-            .filter(UserFollowing.follows_user == u)\
+        f = UserFollowing.query() \
+            .filter(UserFollowing.follows_user == u) \
             .filter(UserFollowing.user_id == user_id).scalar()
 
         return f is not None
 
     def get_followers(self, repo):
-        repo = self._get_repo(repo)
+        repo = Repository.guess_instance(repo)
 
-        return self.sa.query(UserFollowing)\
+        return UserFollowing.query() \
                 .filter(UserFollowing.follows_repository == repo).count()
 
     def get_forks(self, repo):
-        repo = self._get_repo(repo)
-        return self.sa.query(Repository)\
+        repo = Repository.guess_instance(repo)
+        return Repository.query() \
                 .filter(Repository.fork == repo).count()
 
     def get_pull_requests(self, repo):
-        repo = self._get_repo(repo)
-        return self.sa.query(PullRequest)\
-                .filter(PullRequest.other_repo == repo)\
+        repo = Repository.guess_instance(repo)
+        return PullRequest.query() \
+                .filter(PullRequest.other_repo == repo) \
                 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
 
     def mark_as_fork(self, repo, fork, user):
@@ -440,7 +326,6 @@
             raise RepositoryError("Cannot set repository as fork of repository with other type")
 
         repo.fork = fork
-        self.sa.add(repo)
         return repo
 
     def _handle_rc_scm_extras(self, username, repo_name, repo_alias,
@@ -448,7 +333,7 @@
         from kallithea import CONFIG
         from kallithea.lib.base import _get_ip_addr
         try:
-            from pylons import request
+            from tg import request
             environ = request.environ
         except TypeError:
             # we might use this outside of request context, let's fake the
@@ -478,7 +363,7 @@
         :param repo_name: name of repo
         :param revisions: list of revisions that we pushed
         """
-        self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
+        self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias, action=action)
         _scm_repo = repo._repo
         # trigger push hook
         if repo.alias == 'hg':
@@ -503,12 +388,13 @@
         raise Exception('Invalid scm_type, must be one of hg,git got %s'
                         % (scm_type,))
 
-    def pull_changes(self, repo, username):
+    def pull_changes(self, repo, username, clone_uri=None):
         """
-        Pull from "clone URL".
+        Pull from "clone URL" or fork origin.
         """
         dbrepo = self.__get_repo(repo)
-        clone_uri = dbrepo.clone_uri
+        if clone_uri is None:
+            clone_uri = dbrepo.clone_uri or dbrepo.fork and dbrepo.fork.repo_full_path
         if not clone_uri:
             raise Exception("This repository doesn't have a clone uri")
 
@@ -542,7 +428,7 @@
 
         :param repo: a db_repo.scm_instance
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         IMC = self._get_IMC_module(repo.alias)
 
         # decoding here will force that we have proper encoded values
@@ -575,7 +461,7 @@
         if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
             raise NonRelativePathError('%s is not an relative path' % f_path)
         if f_path:
-            f_path = os.path.normpath(f_path)
+            f_path = posixpath.normpath(f_path)
         return f_path
 
     def get_nodes(self, repo_name, revision, root_path='/', flat=True):
@@ -623,13 +509,13 @@
         :returns: new committed changeset
         """
 
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         scm_instance = repo.scm_instance_no_cache()
 
         processed_nodes = []
         for f_path in nodes:
+            content = nodes[f_path]['content']
             f_path = self._sanitize_path(f_path)
-            content = nodes[f_path]['content']
             f_path = safe_str(f_path)
             # decoding here will force that we have proper encoded values
             # in any other case this will throw exceptions and deny commit
@@ -681,7 +567,7 @@
         """
         Commits specified nodes to repo. Again.
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         scm_instance = repo.scm_instance_no_cache()
 
         message = safe_unicode(message)
@@ -715,7 +601,7 @@
                 imc.remove(filenode)
             elif op == 'mod':
                 if filename != old_filename:
-                    #TODO: handle renames, needs vcs lib changes
+                    # TODO: handle renames, needs vcs lib changes
                     imc.remove(filenode)
                     imc.add(FileNode(filename, content=content))
                 else:
@@ -751,7 +637,7 @@
         :returns: new committed changeset after deletion
         """
 
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         scm_instance = repo.scm_instance_no_cache()
 
         processed_nodes = []
@@ -796,7 +682,7 @@
         return tip
 
     def get_unread_journal(self):
-        return self.sa.query(UserLog).count()
+        return UserLog.query().count()
 
     def get_repo_landing_revs(self, repo=None):
         """
@@ -842,21 +728,23 @@
         :param force_create: Create even if same name hook exists
         """
 
-        loc = jn(repo.path, 'hooks')
+        loc = os.path.join(repo.path, 'hooks')
         if not repo.bare:
-            loc = jn(repo.path, '.git', 'hooks')
+            loc = os.path.join(repo.path, '.git', 'hooks')
         if not os.path.isdir(loc):
             os.makedirs(loc)
 
-        tmpl_post = pkg_resources.resource_string(
-            'kallithea', jn('config', 'post_receive_tmpl.py')
+        tmpl_post = "#!/usr/bin/env %s\n" % sys.executable or 'python2'
+        tmpl_post += pkg_resources.resource_string(
+            'kallithea', os.path.join('config', 'post_receive_tmpl.py')
         )
-        tmpl_pre = pkg_resources.resource_string(
-            'kallithea', jn('config', 'pre_receive_tmpl.py')
+        tmpl_pre = "#!/usr/bin/env %s\n" % sys.executable or 'python2'
+        tmpl_pre += pkg_resources.resource_string(
+            'kallithea', os.path.join('config', 'pre_receive_tmpl.py')
         )
 
         for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
-            _hook_file = jn(loc, '%s-receive' % h_type)
+            _hook_file = os.path.join(loc, '%s-receive' % h_type)
             has_hook = False
             log.debug('Installing git hook in repo %s', repo)
             if os.path.exists(_hook_file):
@@ -889,17 +777,18 @@
             else:
                 log.debug('skipping writing hook file')
 
-def AvailableRepoGroupChoices(top_perms, repo_group_perms, extras=()):
+
+def AvailableRepoGroupChoices(top_perms, repo_group_perm_level, extras=()):
     """Return group_id,string tuples with choices for all the repo groups where
     the user has the necessary permissions.
 
     Top level is -1.
     """
     groups = RepoGroup.query().all()
-    if HasPermissionAll('hg.admin')('available repo groups'):
+    if HasPermissionAny('hg.admin')('available repo groups'):
         groups.append(None)
     else:
-        groups = list(RepoGroupList(groups, perm_set=repo_group_perms))
+        groups = list(RepoGroupList(groups, perm_level=repo_group_perm_level))
         if top_perms and HasPermissionAny(*top_perms)('available repo groups'):
             groups.append(None)
         for extra in extras:
--- a/kallithea/model/user.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/user.py	Sun Mar 31 21:28:56 2019 +0200
@@ -32,16 +32,14 @@
 import time
 import traceback
 
-from pylons import config
-from pylons.i18n.translation import _
+from tg import config
+from tg.i18n import ugettext as _
 
 from sqlalchemy.exc import DatabaseError
 
-from kallithea import EXTERN_TYPE_INTERNAL
-from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
+from kallithea.lib.utils2 import safe_str, generate_api_key, get_current_authuser
 from kallithea.lib.caching_query import FromCache
-from kallithea.model import BaseModel
-from kallithea.model.db import User, UserToPerm, Notification, \
+from kallithea.model.db import Permission, User, UserToPerm, \
     UserEmailMap, UserIpMap
 from kallithea.lib.exceptions import DefaultUserException, \
     UserOwnsReposException
@@ -51,20 +49,18 @@
 log = logging.getLogger(__name__)
 
 
-class UserModel(BaseModel):
+class UserModel(object):
     password_reset_token_lifetime = 86400 # 24 hours
 
-    cls = User
-
     def get(self, user_id, cache=False):
-        user = self.sa.query(User)
+        user = User.query()
         if cache:
             user = user.options(FromCache("sql_cache_short",
                                           "get_user_%s" % user_id))
         return user.get(user_id)
 
     def get_user(self, user):
-        return self._get_user(user)
+        return User.guess_instance(user)
 
     def create(self, form_data, cur_user=None):
         if not cur_user:
@@ -95,13 +91,14 @@
             setattr(new_user, k, v)
 
         new_user.api_key = generate_api_key()
-        self.sa.add(new_user)
+        Session().add(new_user)
+        Session().flush() # make database assign new_user.user_id
 
         log_create_user(new_user.get_dict(), cur_user)
         return new_user
 
-    def create_or_update(self, username, password, email, firstname='',
-                         lastname='', active=True, admin=False,
+    def create_or_update(self, username, password, email, firstname=u'',
+                         lastname=u'', active=True, admin=False,
                          extern_type=None, extern_name=None, cur_user=None):
         """
         Creates a new instance if not found, or updates current one
@@ -148,9 +145,9 @@
             new_user.admin = admin
             new_user.email = email
             new_user.active = active
-            new_user.extern_name = safe_unicode(extern_name) \
+            new_user.extern_name = safe_str(extern_name) \
                 if extern_name else None
-            new_user.extern_type = safe_unicode(extern_type) \
+            new_user.extern_type = safe_str(extern_type) \
                 if extern_type else None
             new_user.name = firstname
             new_user.lastname = lastname
@@ -165,12 +162,15 @@
                 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
+                    if password else ''
 
-            self.sa.add(new_user)
+            if user is None:
+                Session().add(new_user)
+                Session().flush() # make database assign new_user.user_id
 
             if not edit:
                 log_create_user(new_user.get_dict(), cur_user)
+
             return new_user
         except (DatabaseError,):
             log.error(traceback.format_exc())
@@ -181,13 +181,10 @@
         import kallithea.lib.helpers as h
 
         form_data['admin'] = False
-        form_data['extern_name'] = EXTERN_TYPE_INTERNAL
-        form_data['extern_type'] = EXTERN_TYPE_INTERNAL
+        form_data['extern_type'] = User.DEFAULT_AUTH_TYPE
+        form_data['extern_name'] = ''
         new_user = self.create(form_data)
 
-        self.sa.add(new_user)
-        self.sa.flush()
-
         # notification to admins
         subject = _('New user registration')
         body = (
@@ -200,17 +197,19 @@
         edit_url = h.canonical_url('edit_user', id=new_user.user_id)
         email_kwargs = {
             'registered_user_url': edit_url,
-            'new_username': new_user.username}
+            'new_username': new_user.username,
+            'new_email': new_user.email,
+            'new_full_name': new_user.full_name}
         NotificationModel().create(created_by=new_user, subject=subject,
                                    body=body, recipients=None,
-                                   type_=Notification.TYPE_REGISTRATION,
+                                   type_=NotificationModel.TYPE_REGISTRATION,
                                    email_kwargs=email_kwargs)
 
-    def update(self, user_id, form_data, skip_attrs=[]):
+    def update(self, user_id, form_data, skip_attrs=None):
         from kallithea.lib.auth import get_crypt_password
-
+        skip_attrs = skip_attrs or []
         user = self.get(user_id, cache=False)
-        if user.username == User.DEFAULT_USER:
+        if user.is_default_user:
             raise DefaultUserException(
                             _("You can't edit this user since it's "
                               "crucial for entire application"))
@@ -226,13 +225,12 @@
                 if k == 'firstname':
                     k = 'name'
                 setattr(user, k, v)
-        self.sa.add(user)
 
     def update_user(self, user, **kwargs):
         from kallithea.lib.auth import get_crypt_password
 
-        user = self._get_user(user)
-        if user.username == User.DEFAULT_USER:
+        user = User.guess_instance(user)
+        if user.is_default_user:
             raise DefaultUserException(
                 _("You can't edit this user since it's"
                   " crucial for entire application")
@@ -243,15 +241,14 @@
                 v = get_crypt_password(v)
 
             setattr(user, k, v)
-        self.sa.add(user)
         return user
 
     def delete(self, user, cur_user=None):
         if cur_user is None:
             cur_user = getattr(get_current_authuser(), 'username', None)
-        user = self._get_user(user)
+        user = User.guess_instance(user)
 
-        if user.username == User.DEFAULT_USER:
+        if user.is_default_user:
             raise DefaultUserException(
                 _("You can't remove this user since it is"
                   " crucial for the entire application"))
@@ -273,11 +270,16 @@
                 _('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)))
-        self.sa.delete(user)
+        Session().delete(user)
 
         from kallithea.lib.hooks import log_delete_user
         log_delete_user(user.get_dict(), cur_user)
 
+    def can_change_password(self, user):
+        from kallithea.lib import auth_modules
+        managed_fields = auth_modules.get_managed_fields(user)
+        return 'password' not in managed_fields
+
     def get_reset_password_token(self, user, timestamp, session_id):
         """
         The token is a 40-digit hexstring, calculated as a HMAC-SHA1.
@@ -324,7 +326,7 @@
         allowing users to copy-paste or manually enter the token from the
         email.
         """
-        from kallithea.lib.celerylib import tasks, run_task
+        from kallithea.lib.celerylib import tasks
         from kallithea.model.notification import EmailNotificationModel
         import kallithea.lib.helpers as h
 
@@ -332,18 +334,21 @@
         user = User.get_by_email(user_email)
         timestamp = int(time.time())
         if user is not None:
-            log.debug('password reset user %s found', user)
-            token = self.get_reset_password_token(user,
-                                                  timestamp,
-                                                  h.authentication_token())
-            # URL must be fully qualified; but since the token is locked to
-            # the current browser session, we must provide a URL with the
-            # current scheme and hostname, rather than the canonical_url.
-            link = h.url('reset_password_confirmation', qualified=True,
-                         email=user_email,
-                         timestamp=timestamp,
-                         token=token)
-
+            if self.can_change_password(user):
+                log.debug('password reset user %s found', user)
+                token = self.get_reset_password_token(user,
+                                                      timestamp,
+                                                      h.authentication_token())
+                # URL must be fully qualified; but since the token is locked to
+                # the current browser session, we must provide a URL with the
+                # current scheme and hostname, rather than the canonical_url.
+                link = h.url('reset_password_confirmation', qualified=True,
+                             email=user_email,
+                             timestamp=timestamp,
+                             token=token)
+            else:
+                log.debug('password reset user %s found but was managed', user)
+                token = link = None
             reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
             body = EmailNotificationModel().get_email_tmpl(
                 reg_type, 'txt',
@@ -356,8 +361,7 @@
                 reset_token=token,
                 reset_url=link)
             log.debug('sending email')
-            run_task(tasks.send_email, [user_email],
-                     _("Password reset link"), body, html_body)
+            tasks.send_email([user_email], _("Password reset link"), body, html_body)
             log.info('send new password mail to %s', user_email)
         else:
             log.debug("password reset email %s not found", user_email)
@@ -367,7 +371,7 @@
                      timestamp=timestamp)
 
     def verify_reset_password_token(self, email, timestamp, token):
-        from kallithea.lib.celerylib import tasks, run_task
+        from kallithea.lib.celerylib import tasks
         from kallithea.lib import auth
         import kallithea.lib.helpers as h
         user = User.get_by_email(email)
@@ -393,18 +397,19 @@
         return expected_token == token
 
     def reset_password(self, user_email, new_passwd):
-        from kallithea.lib.celerylib import tasks, run_task
+        from kallithea.lib.celerylib import tasks
         from kallithea.lib import auth
         user = User.get_by_email(user_email)
         if user is not None:
+            if not self.can_change_password(user):
+                raise Exception('trying to change password for external user')
             user.password = auth.get_crypt_password(new_passwd)
-            Session().add(user)
             Session().commit()
             log.info('change password for %s', user_email)
         if new_passwd is None:
             raise Exception('unable to set new password')
 
-        run_task(tasks.send_email, [user_email],
+        tasks.send_email([user_email],
                  _('Password reset notification'),
                  _('The password to your account %s has been changed using password reset form.') % (user.username,))
         log.info('send password reset mail to %s', user_email)
@@ -412,10 +417,10 @@
         return True
 
     def has_perm(self, user, perm):
-        perm = self._get_perm(perm)
-        user = self._get_user(user)
+        perm = Permission.guess_instance(perm)
+        user = User.guess_instance(user)
 
-        return UserToPerm.query().filter(UserToPerm.user == user)\
+        return UserToPerm.query().filter(UserToPerm.user == user) \
             .filter(UserToPerm.permission == perm).scalar() is not None
 
     def grant_perm(self, user, perm):
@@ -425,19 +430,19 @@
         :param user:
         :param perm:
         """
-        user = self._get_user(user)
-        perm = self._get_perm(perm)
+        user = User.guess_instance(user)
+        perm = Permission.guess_instance(perm)
         # if this permission is already granted skip it
-        _perm = UserToPerm.query()\
-            .filter(UserToPerm.user == user)\
-            .filter(UserToPerm.permission == perm)\
+        _perm = UserToPerm.query() \
+            .filter(UserToPerm.user == user) \
+            .filter(UserToPerm.permission == perm) \
             .scalar()
         if _perm:
             return
         new = UserToPerm()
         new.user = user
         new.permission = perm
-        self.sa.add(new)
+        Session().add(new)
         return new
 
     def revoke_perm(self, user, perm):
@@ -447,8 +452,8 @@
         :param user:
         :param perm:
         """
-        user = self._get_user(user)
-        perm = self._get_perm(perm)
+        user = User.guess_instance(user)
+        perm = Permission.guess_instance(perm)
 
         UserToPerm.query().filter(
             UserToPerm.user == user,
@@ -465,12 +470,12 @@
         from kallithea.model import forms
         form = forms.UserExtraEmailForm()()
         data = form.to_python(dict(email=email))
-        user = self._get_user(user)
+        user = User.guess_instance(user)
 
         obj = UserEmailMap()
         obj.user = user
         obj.email = data['email']
-        self.sa.add(obj)
+        Session().add(obj)
         return obj
 
     def delete_extra_email(self, user, email_id):
@@ -480,10 +485,10 @@
         :param user:
         :param email_id:
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         obj = UserEmailMap.query().get(email_id)
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
 
     def add_extra_ip(self, user, ip):
         """
@@ -495,12 +500,12 @@
         from kallithea.model import forms
         form = forms.UserExtraIpForm()()
         data = form.to_python(dict(ip=ip))
-        user = self._get_user(user)
+        user = User.guess_instance(user)
 
         obj = UserIpMap()
         obj.user = user
         obj.ip_addr = data['ip']
-        self.sa.add(obj)
+        Session().add(obj)
         return obj
 
     def delete_extra_ip(self, user, ip_id):
@@ -510,7 +515,7 @@
         :param user:
         :param ip_id:
         """
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         obj = UserIpMap.query().get(ip_id)
         if obj:
-            self.sa.delete(obj)
+            Session().delete(obj)
--- a/kallithea/model/user_group.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/user_group.py	Sun Mar 31 21:28:56 2019 +0200
@@ -27,23 +27,16 @@
 import logging
 import traceback
 
-from kallithea.model import BaseModel
-from kallithea.model.db import UserGroupMember, UserGroup,\
-    UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
+from kallithea.model.db import Session, UserGroupMember, UserGroup, \
+    UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm, \
     UserGroupUserGroupToPerm
-from kallithea.lib.exceptions import UserGroupsAssignedException,\
+from kallithea.lib.exceptions import UserGroupsAssignedException, \
     RepoGroupAssignmentError
 
 log = logging.getLogger(__name__)
 
 
-class UserGroupModel(BaseModel):
-
-    cls = UserGroup
-
-    def _get_user_group(self, user_group):
-        return self._get_instance(UserGroup, user_group,
-                                  callback=UserGroup.get_by_group_name)
+class UserGroupModel(object):
 
     def _create_default_perms(self, user_group):
         # create default permission
@@ -59,11 +52,12 @@
 
         user_group_to_perm.user_group = user_group
         user_group_to_perm.user_id = def_user.user_id
+        Session().add(user_group_to_perm)
         return user_group_to_perm
 
     def _update_permissions(self, user_group, perms_new=None,
                             perms_updates=None):
-        from kallithea.lib.auth import HasUserGroupPermissionAny
+        from kallithea.lib.auth import HasUserGroupPermissionLevel
         if not perms_new:
             perms_new = []
         if not perms_updates:
@@ -77,9 +71,8 @@
                     user_group=user_group, user=member, perm=perm
                 )
             else:
-                #check if we have permissions to alter this usergroup
-                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
-                                             'usergroup.admin')(member):
+                # check if we have permissions to alter this usergroup's access
+                if HasUserGroupPermissionLevel('read')(member):
                     self.grant_user_group_permission(
                         target_user_group=user_group, user_group=member, perm=perm
                     )
@@ -90,9 +83,8 @@
                     user_group=user_group, user=member, perm=perm
                 )
             else:
-                #check if we have permissions to alter this usergroup
-                if HasUserGroupPermissionAny('usergroup.read', 'usergroup.write',
-                                             'usergroup.admin')(member):
+                # check if we have permissions to alter this usergroup's access
+                if HasUserGroupPermissionLevel('read')(member):
                     self.grant_user_group_permission(
                         target_user_group=user_group, user_group=member, perm=perm
                     )
@@ -101,7 +93,7 @@
         return UserGroup.get(user_group_id)
 
     def get_group(self, user_group):
-        return self._get_user_group(user_group)
+        return UserGroup.guess_instance(user_group)
 
     def get_by_name(self, name, cache=False, case_insensitive=False):
         return UserGroup.get_by_group_name(name, cache, case_insensitive)
@@ -109,15 +101,14 @@
     def create(self, name, description, owner, active=True, group_data=None):
         try:
             new_user_group = UserGroup()
-            new_user_group.user = self._get_user(owner)
+            new_user_group.owner = User.guess_instance(owner)
             new_user_group.users_group_name = name
             new_user_group.user_group_description = description
             new_user_group.users_group_active = active
             if group_data:
                 new_user_group.group_data = group_data
-            self.sa.add(new_user_group)
-            perm_obj = self._create_default_perms(new_user_group)
-            self.sa.add(perm_obj)
+            Session().add(new_user_group)
+            self._create_default_perms(new_user_group)
 
             self.grant_user_permission(user_group=new_user_group,
                                        user=owner, perm='usergroup.admin')
@@ -130,22 +121,23 @@
     def update(self, user_group, form_data):
 
         try:
-            user_group = self._get_user_group(user_group)
+            user_group = UserGroup.guess_instance(user_group)
 
             for k, v in form_data.items():
                 if k == 'users_group_members':
-                    user_group.members = []
-                    self.sa.flush()
                     members_list = []
                     if v:
                         v = [v] if isinstance(v, basestring) else v
                         for u_id in set(v):
                             member = UserGroupMember(user_group.users_group_id, u_id)
                             members_list.append(member)
-                    setattr(user_group, 'members', members_list)
+                            Session().add(member)
+                    user_group.members = members_list
                 setattr(user_group, k, v)
 
-            self.sa.add(user_group)
+            # Flush to make db assign users_group_member_id to newly
+            # created UserGroupMembers.
+            Session().flush()
         except Exception:
             log.error(traceback.format_exc())
             raise
@@ -159,24 +151,24 @@
         :param user_group:
         :param force:
         """
-        user_group = self._get_user_group(user_group)
+        user_group = UserGroup.guess_instance(user_group)
         try:
             # check if this group is not assigned to repo
-            assigned_groups = UserGroupRepoToPerm.query()\
+            assigned_groups = UserGroupRepoToPerm.query() \
                 .filter(UserGroupRepoToPerm.users_group == user_group).all()
             assigned_groups = [x.repository.repo_name for x in assigned_groups]
 
             if assigned_groups and not force:
                 raise UserGroupsAssignedException(
                     'User Group assigned to %s' % ", ".join(assigned_groups))
-            self.sa.delete(user_group)
+            Session().delete(user_group)
         except Exception:
             log.error(traceback.format_exc())
             raise
 
     def add_user_to_group(self, user_group, user):
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
+        user_group = UserGroup.guess_instance(user_group)
+        user = User.guess_instance(user)
 
         for m in user_group.members:
             u = m.user
@@ -192,26 +184,26 @@
             user_group.members.append(user_group_member)
             user.group_member.append(user_group_member)
 
-            self.sa.add(user_group_member)
+            Session().add(user_group_member)
             return user_group_member
         except Exception:
             log.error(traceback.format_exc())
             raise
 
     def remove_user_from_group(self, user_group, user):
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
+        user_group = UserGroup.guess_instance(user_group)
+        user = User.guess_instance(user)
 
         user_group_member = None
         for m in user_group.members:
-            if m.user.user_id == user.user_id:
+            if m.user_id == user.user_id:
                 # Found this user's membership row
                 user_group_member = m
                 break
 
         if user_group_member:
             try:
-                self.sa.delete(user_group_member)
+                Session().delete(user_group_member)
                 return True
             except Exception:
                 log.error(traceback.format_exc())
@@ -221,21 +213,21 @@
             return False
 
     def has_perm(self, user_group, perm):
-        user_group = self._get_user_group(user_group)
-        perm = self._get_perm(perm)
+        user_group = UserGroup.guess_instance(user_group)
+        perm = Permission.guess_instance(perm)
 
-        return UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == user_group)\
+        return UserGroupToPerm.query() \
+            .filter(UserGroupToPerm.users_group == user_group) \
             .filter(UserGroupToPerm.permission == perm).scalar() is not None
 
     def grant_perm(self, user_group, perm):
-        user_group = self._get_user_group(user_group)
-        perm = self._get_perm(perm)
+        user_group = UserGroup.guess_instance(user_group)
+        perm = Permission.guess_instance(perm)
 
         # if this permission is already granted skip it
-        _perm = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == user_group)\
-            .filter(UserGroupToPerm.permission == perm)\
+        _perm = UserGroupToPerm.query() \
+            .filter(UserGroupToPerm.users_group == user_group) \
+            .filter(UserGroupToPerm.permission == perm) \
             .scalar()
         if _perm:
             return
@@ -243,18 +235,18 @@
         new = UserGroupToPerm()
         new.users_group = user_group
         new.permission = perm
-        self.sa.add(new)
+        Session().add(new)
         return new
 
     def revoke_perm(self, user_group, perm):
-        user_group = self._get_user_group(user_group)
-        perm = self._get_perm(perm)
+        user_group = UserGroup.guess_instance(user_group)
+        perm = Permission.guess_instance(perm)
 
-        obj = UserGroupToPerm.query()\
-            .filter(UserGroupToPerm.users_group == user_group)\
+        obj = UserGroupToPerm.query() \
+            .filter(UserGroupToPerm.users_group == user_group) \
             .filter(UserGroupToPerm.permission == perm).scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
 
     def grant_user_permission(self, user_group, user, perm):
         """
@@ -267,22 +259,22 @@
         :param perm: Instance of Permission, or permission_name
         """
 
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
-        permission = self._get_perm(perm)
+        user_group = UserGroup.guess_instance(user_group)
+        user = User.guess_instance(user)
+        permission = Permission.guess_instance(perm)
 
         # check if we have that permission already
-        obj = self.sa.query(UserUserGroupToPerm)\
-            .filter(UserUserGroupToPerm.user == user)\
-            .filter(UserUserGroupToPerm.user_group == user_group)\
+        obj = UserUserGroupToPerm.query() \
+            .filter(UserUserGroupToPerm.user == user) \
+            .filter(UserUserGroupToPerm.user_group == user_group) \
             .scalar()
         if obj is None:
             # create new !
             obj = UserUserGroupToPerm()
+            Session().add(obj)
         obj.user_group = user_group
         obj.user = user
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, user, user_group)
         return obj
 
@@ -295,15 +287,15 @@
         :param user: Instance of User, user_id or username
         """
 
-        user_group = self._get_user_group(user_group)
-        user = self._get_user(user)
+        user_group = UserGroup.guess_instance(user_group)
+        user = User.guess_instance(user)
 
-        obj = self.sa.query(UserUserGroupToPerm)\
-            .filter(UserUserGroupToPerm.user == user)\
-            .filter(UserUserGroupToPerm.user_group == user_group)\
+        obj = UserUserGroupToPerm.query() \
+            .filter(UserUserGroupToPerm.user == user) \
+            .filter(UserUserGroupToPerm.user_group == user_group) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm on %s on %s', user_group, user)
 
     def grant_user_group_permission(self, target_user_group, user_group, perm):
@@ -314,26 +306,26 @@
         :param user_group:
         :param perm:
         """
-        target_user_group = self._get_user_group(target_user_group)
-        user_group = self._get_user_group(user_group)
-        permission = self._get_perm(perm)
+        target_user_group = UserGroup.guess_instance(target_user_group)
+        user_group = UserGroup.guess_instance(user_group)
+        permission = Permission.guess_instance(perm)
         # forbid assigning same user group to itself
         if target_user_group == user_group:
             raise RepoGroupAssignmentError('target repo:%s cannot be '
                                            'assigned to itself' % target_user_group)
 
         # check if we have that permission already
-        obj = self.sa.query(UserGroupUserGroupToPerm)\
-            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
-            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
+        obj = UserGroupUserGroupToPerm.query() \
+            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group) \
+            .filter(UserGroupUserGroupToPerm.user_group == user_group) \
             .scalar()
         if obj is None:
             # create new !
             obj = UserGroupUserGroupToPerm()
+            Session().add(obj)
         obj.user_group = user_group
         obj.target_user_group = target_user_group
         obj.permission = permission
-        self.sa.add(obj)
         log.debug('Granted perm %s to %s on %s', perm, target_user_group, user_group)
         return obj
 
@@ -344,19 +336,19 @@
         :param target_user_group:
         :param user_group:
         """
-        target_user_group = self._get_user_group(target_user_group)
-        user_group = self._get_user_group(user_group)
+        target_user_group = UserGroup.guess_instance(target_user_group)
+        user_group = UserGroup.guess_instance(user_group)
 
-        obj = self.sa.query(UserGroupUserGroupToPerm)\
-            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
-            .filter(UserGroupUserGroupToPerm.user_group == user_group)\
+        obj = UserGroupUserGroupToPerm.query() \
+            .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group) \
+            .filter(UserGroupUserGroupToPerm.user_group == user_group) \
             .scalar()
         if obj is not None:
-            self.sa.delete(obj)
+            Session().delete(obj)
             log.debug('Revoked perm on %s on %s', target_user_group, user_group)
 
     def enforce_groups(self, user, groups, extern_type=None):
-        user = self._get_user(user)
+        user = User.guess_instance(user)
         log.debug('Enforcing groups %s on user %s', user, groups)
         current_groups = user.group_member
         # find the external created groups
@@ -375,7 +367,7 @@
         for gr in set(groups):
             existing_group = UserGroup.get_by_group_name(gr)
             if not existing_group:
-                desc = 'Automatically created from plugin:%s' % extern_type
+                desc = u'Automatically created from plugin:%s' % extern_type
                 # we use first admin account to set the owner of the group
                 existing_group = UserGroupModel().create(gr, desc, owner,
                                         group_data={'extern_type': extern_type})
--- a/kallithea/model/validators.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/model/validators.py	Sun Mar 31 21:28:56 2019 +0200
@@ -20,7 +20,8 @@
 import formencode
 import logging
 from collections import defaultdict
-from pylons.i18n.translation import _
+from tg.i18n import ugettext as _
+from sqlalchemy import func
 from webhelpers.pylonslib.secure_form import authentication_token
 import sqlalchemy
 
@@ -30,12 +31,12 @@
 )
 from kallithea.lib.compat import OrderedSet
 from kallithea.lib import ipaddr
-from kallithea.lib.utils import repo_name_slug, is_valid_repo_uri
-from kallithea.lib.utils2 import str2bool, aslist
+from kallithea.lib.utils import is_valid_repo_uri
+from kallithea.lib.utils2 import str2bool, aslist, repo_name_slug
 from kallithea.model.db import RepoGroup, Repository, UserGroup, User
 from kallithea.lib.exceptions import LdapImportError
 from kallithea.config.routing import ADMIN_PREFIX
-from kallithea.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
+from kallithea.lib.auth import HasRepoGroupPermissionLevel, HasPermissionAny
 
 # silence warnings and pylint
 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
@@ -44,30 +45,6 @@
 log = logging.getLogger(__name__)
 
 
-class StateObj(object):
-    """
-    this is needed to translate the messages using _() in validators
-    """
-    _ = staticmethod(_)
-
-
-def M(self, key, state=None, **kwargs):
-    """
-    returns string from self.message based on given key,
-    passed kw params are used to substitute %(named)s params inside
-    translated strings
-
-    :param msg:
-    :param state:
-    """
-    if state is None:
-        state = StateObj()
-    else:
-        state._ = staticmethod(_)
-    #inject validator into state object
-    return self.message(key, state, **kwargs)
-
-
 def UniqueListFromString():
     class _UniqueListFromString(formencode.FancyValidator):
         """
@@ -89,7 +66,9 @@
     return _UniqueListFromString
 
 
-def ValidUsername(edit=False, old_data={}):
+def ValidUsername(edit=False, old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'username_exists': _('Username "%(username)s" already exists'),
@@ -103,20 +82,20 @@
 
         def validate_python(self, value, state):
             if value in ['default', 'new_user']:
-                msg = M(self, 'system_invalid_username', state, username=value)
+                msg = self.message('system_invalid_username', state, username=value)
                 raise formencode.Invalid(msg, value, state)
-            #check if user is unique
+            # check if user is unique
             old_un = None
             if edit:
                 old_un = User.get(old_data.get('user_id')).username
 
             if old_un != value or not edit:
                 if User.get_by_username(value, case_insensitive=True):
-                    msg = M(self, 'username_exists', state, username=value)
+                    msg = self.message('username_exists', state, username=value)
                     raise formencode.Invalid(msg, value, state)
 
             if re.match(r'^[a-zA-Z0-9\_]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
-                msg = M(self, 'invalid_username', state)
+                msg = self.message('invalid_username', state)
                 raise formencode.Invalid(msg, value, state)
     return _validator
 
@@ -135,10 +114,10 @@
 
         def validate_python(self, value, state):
             try:
-                User.query().filter(User.active == True)\
+                User.query().filter(User.active == True) \
                     .filter(User.username == value).one()
             except sqlalchemy.exc.InvalidRequestError: # NoResultFound/MultipleResultsFound
-                msg = M(self, 'invalid_username', state, username=value)
+                msg = self.message('invalid_username', state, username=value)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(username=msg)
                 )
@@ -146,7 +125,9 @@
     return _validator
 
 
-def ValidUserGroup(edit=False, old_data={}):
+def ValidUserGroup(edit=False, old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'invalid_group': _('Invalid user group name'),
@@ -159,11 +140,11 @@
 
         def validate_python(self, value, state):
             if value in ['default']:
-                msg = M(self, 'invalid_group', state)
+                msg = self.message('invalid_group', state)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(users_group_name=msg)
                 )
-            #check if group is unique
+            # check if group is unique
             old_ugname = None
             if edit:
                 old_id = old_data.get('users_group_id')
@@ -173,13 +154,13 @@
                 is_existing_group = UserGroup.get_by_group_name(value,
                                                         case_insensitive=True)
                 if is_existing_group:
-                    msg = M(self, 'group_exist', state, usergroup=value)
+                    msg = self.message('group_exist', state, usergroup=value)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(users_group_name=msg)
                     )
 
             if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
-                msg = M(self, 'invalid_usergroup_name', state)
+                msg = self.message('invalid_usergroup_name', state)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(users_group_name=msg)
                 )
@@ -187,10 +168,12 @@
     return _validator
 
 
-def ValidRepoGroup(edit=False, old_data={}):
+def ValidRepoGroup(edit=False, old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
-            'group_parent_id': _('Cannot assign this group as parent'),
+            'parent_group_id': _('Cannot assign this group as parent'),
             'group_exists': _('Group "%(group_name)s" already exists'),
             'repo_exists':
                 _('Repository with name "%(group_name)s" already exists')
@@ -199,20 +182,20 @@
         def validate_python(self, value, state):
             # TODO WRITE VALIDATIONS
             group_name = value.get('group_name')
-            group_parent_id = value.get('group_parent_id')
+            parent_group_id = value.get('parent_group_id')
 
             # slugify repo group just in case :)
             slug = repo_name_slug(group_name)
 
             # check for parent of self
             parent_of_self = lambda: (
-                old_data['group_id'] == group_parent_id
-                if group_parent_id else False
+                old_data['group_id'] == parent_group_id
+                if parent_group_id else False
             )
             if edit and parent_of_self():
-                msg = M(self, 'group_parent_id', state)
+                msg = self.message('parent_group_id', state)
                 raise formencode.Invalid(msg, value, state,
-                    error_dict=dict(group_parent_id=msg)
+                    error_dict=dict(parent_group_id=msg)
                 )
 
             old_gname = None
@@ -222,24 +205,22 @@
             if old_gname != group_name or not edit:
 
                 # check group
-                gr = RepoGroup.query()\
-                      .filter(RepoGroup.group_name == slug)\
-                      .filter(RepoGroup.group_parent_id == group_parent_id)\
+                gr = RepoGroup.query() \
+                      .filter(func.lower(RepoGroup.group_name) == func.lower(slug)) \
+                      .filter(RepoGroup.parent_group_id == parent_group_id) \
                       .scalar()
-
                 if gr is not None:
-                    msg = M(self, 'group_exists', state, group_name=slug)
+                    msg = self.message('group_exists', state, group_name=slug)
                     raise formencode.Invalid(msg, value, state,
                             error_dict=dict(group_name=msg)
                     )
 
                 # check for same repo
-                repo = Repository.query()\
-                      .filter(Repository.repo_name == slug)\
+                repo = Repository.query() \
+                      .filter(func.lower(Repository.repo_name) == func.lower(slug)) \
                       .scalar()
-
                 if repo is not None:
-                    msg = M(self, 'repo_exists', state, group_name=slug)
+                    msg = self.message('repo_exists', state, group_name=slug)
                     raise formencode.Invalid(msg, value, state,
                             error_dict=dict(group_name=msg)
                     )
@@ -258,7 +239,7 @@
             try:
                 (value or '').decode('ascii')
             except UnicodeError:
-                msg = M(self, 'invalid_password', state)
+                msg = self.message('invalid_password', state)
                 raise formencode.Invalid(msg, value, state,)
     return _validator
 
@@ -272,7 +253,7 @@
         def validate_python(self, value, state):
             from kallithea.lib import auth_modules
             if auth_modules.authenticate(username, value, '') is None:
-                msg = M(self, 'invalid_password', state)
+                msg = self.message('invalid_password', state)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(current_password=msg)
                 )
@@ -287,7 +268,7 @@
 
         def validate_python(self, value, state):
             if value.get(password_field) != value[password_confirmation_field]:
-                msg = M(self, 'password_mismatch', state)
+                msg = self.message('password_mismatch', state)
                 raise formencode.Invalid(msg, value, state,
                      error_dict={password_field:msg, password_confirmation_field: msg}
                 )
@@ -309,16 +290,16 @@
             # authenticate returns unused dict but has called
             # plugin._authenticate which has create_or_update'ed the username user in db
             if auth_modules.authenticate(username, password) is None:
-                user = User.get_by_username(username)
+                user = User.get_by_username_or_email(username)
                 if user and not user.active:
                     log.warning('user %s is disabled', username)
-                    msg = M(self, 'invalid_auth', state)
+                    msg = self.message('invalid_auth', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(username=' ', password=msg)
                     )
                 else:
                     log.warning('user %s failed to authenticate', username)
-                    msg = M(self, 'invalid_auth', state)
+                    msg = self.message('invalid_auth', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(username=' ', password=msg)
                     )
@@ -333,12 +314,14 @@
 
         def validate_python(self, value, state):
             if value != authentication_token():
-                msg = M(self, 'invalid_token', state)
+                msg = self.message('invalid_token', state)
                 raise formencode.Invalid(msg, value, state)
     return _validator
 
 
-def ValidRepoName(edit=False, old_data={}):
+def ValidRepoName(edit=False, old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'invalid_repo_name':
@@ -373,14 +356,13 @@
             return value
 
         def validate_python(self, value, state):
-
             repo_name = value.get('repo_name')
             repo_name_full = value.get('repo_name_full')
             group_path = value.get('group_path')
             group_name = value.get('group_name')
 
             if repo_name in [ADMIN_PREFIX, '']:
-                msg = M(self, 'invalid_repo_name', state, repo=repo_name)
+                msg = self.message('invalid_repo_name', state, repo=repo_name)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(repo_name=msg)
                 )
@@ -388,24 +370,24 @@
             rename = old_data.get('repo_name') != repo_name_full
             create = not edit
             if rename or create:
-
+                repo = Repository.get_by_repo_name(repo_name_full, case_insensitive=True)
+                repo_group = RepoGroup.get_by_group_name(repo_name_full, case_insensitive=True)
                 if group_path != '':
-                    if Repository.get_by_repo_name(repo_name_full):
-                        msg = M(self, 'repository_in_group_exists', state,
-                                repo=repo_name, group=group_name)
+                    if repo is not None:
+                        msg = self.message('repository_in_group_exists', state,
+                                repo=repo.repo_name, group=group_name)
                         raise formencode.Invalid(msg, value, state,
                             error_dict=dict(repo_name=msg)
                         )
-                elif RepoGroup.get_by_group_name(repo_name_full):
-                        msg = M(self, 'same_group_exists', state,
+                elif repo_group is not None:
+                        msg = self.message('same_group_exists', state,
                                 repo=repo_name)
                         raise formencode.Invalid(msg, value, state,
                             error_dict=dict(repo_name=msg)
                         )
-
-                elif Repository.get_by_repo_name(repo_name_full):
-                        msg = M(self, 'repository_exists', state,
-                                repo=repo_name)
+                elif repo is not None:
+                        msg = self.message('repository_exists', state,
+                                repo=repo.repo_name)
                         raise formencode.Invalid(msg, value, state,
                             error_dict=dict(repo_name=msg)
                         )
@@ -448,14 +430,16 @@
                     is_valid_repo_uri(repo_type, url, make_ui('db', clear_session=False))
                 except Exception:
                     log.exception('URL validation failed')
-                    msg = M(self, 'clone_uri')
+                    msg = self.message('clone_uri', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(clone_uri=msg)
                     )
     return _validator
 
 
-def ValidForkType(old_data={}):
+def ValidForkType(old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'invalid_fork_type': _('Fork has to be the same type as parent')
@@ -463,7 +447,7 @@
 
         def validate_python(self, value, state):
             if old_data['repo_type'] != value:
-                msg = M(self, 'invalid_fork_type', state)
+                msg = self.message('invalid_fork_type', state)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(repo_type=msg)
                 )
@@ -480,7 +464,7 @@
         }
 
         def _to_python(self, value, state):
-            #root location
+            # root location
             if value == -1:
                 return None
             return value
@@ -491,9 +475,9 @@
 
             # create repositories with write permission on group is set to true
             create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
-            group_admin = HasRepoGroupPermissionAny('group.admin')(gr_name,
+            group_admin = HasRepoGroupPermissionLevel('admin')(gr_name,
                                             'can write into group validator')
-            group_write = HasRepoGroupPermissionAny('group.write')(gr_name,
+            group_write = HasRepoGroupPermissionLevel('write')(gr_name,
                                             'can write into group validator')
             forbidden = not (group_admin or (group_write and create_on_write))
             can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
@@ -506,15 +490,15 @@
             # don't need to check permission if he didn't change the value of
             # groups in form box
             if value_changed or new:
-                #parent group need to be existing
+                # parent group need to be existing
                 if gr and forbidden:
-                    msg = M(self, 'permission_denied', state)
+                    msg = self.message('permission_denied', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(repo_type=msg)
                     )
                 ## check if we can write to root location !
                 elif gr is None and not can_create_repos():
-                    msg = M(self, 'permission_denied_root', state)
+                    msg = self.message('permission_denied_root', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(repo_type=msg)
                     )
@@ -530,7 +514,7 @@
         }
 
         def to_python(self, value, state):
-            #root location
+            # root location
             if value == -1:
                 return None
             return value
@@ -540,16 +524,15 @@
             gr_name = gr.group_name if gr is not None else None # None means ROOT location
 
             if can_create_in_root and gr is None:
-                #we can create in root, we're fine no validations required
+                # we can create in root, we're fine no validations required
                 return
 
             forbidden_in_root = gr is None and not can_create_in_root
-            val = HasRepoGroupPermissionAny('group.admin')
-            forbidden = not val(gr_name, 'can create group validator')
+            forbidden = not HasRepoGroupPermissionLevel('admin')(gr_name, 'can create group validator')
             if forbidden_in_root or forbidden:
-                msg = M(self, 'permission_denied', state)
+                msg = self.message('permission_denied', state)
                 raise formencode.Invalid(msg, value, state,
-                    error_dict=dict(group_parent_id=msg)
+                    error_dict=dict(parent_group_id=msg)
                 )
 
     return _validator
@@ -574,7 +557,7 @@
             perms_new = OrderedSet()
             # build a list of permission to update and new permission to create
 
-            #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
+            # CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
             new_perms_group = defaultdict(dict)
             for k, v in value.copy().iteritems():
                 if k.startswith('perm_new_member'):
@@ -616,17 +599,17 @@
             for k, v, t in perms_new:
                 try:
                     if t is 'user':
-                        self.user_db = User.query()\
-                            .filter(User.active == True)\
+                        self.user_db = User.query() \
+                            .filter(User.active == True) \
                             .filter(User.username == k).one()
                     if t is 'users_group':
-                        self.user_db = UserGroup.query()\
-                            .filter(UserGroup.users_group_active == True)\
+                        self.user_db = UserGroup.query() \
+                            .filter(UserGroup.users_group_active == True) \
                             .filter(UserGroup.users_group_name == k).one()
 
                 except Exception:
                     log.exception('Updated permission failed')
-                    msg = M(self, 'perm_new_member_type', state)
+                    msg = self.message('perm_new_member_type', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(perm_new_member_name=msg)
                     )
@@ -664,14 +647,16 @@
 
         def validate_python(self, value, state):
             if not os.path.isdir(value):
-                msg = M(self, 'invalid_path', state)
+                msg = self.message('invalid_path', state)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(paths_root_path=msg)
                 )
     return _validator
 
 
-def UniqSystemEmail(old_data={}):
+def UniqSystemEmail(old_data=None):
+    old_data = old_data or {}
+
     class _validator(formencode.validators.FancyValidator):
         messages = {
             'email_taken': _('This email address is already in use')
@@ -682,9 +667,9 @@
 
         def validate_python(self, value, state):
             if (old_data.get('email') or '').lower() != value:
-                user = User.get_by_email(value, case_insensitive=True)
+                user = User.get_by_email(value)
                 if user is not None:
-                    msg = M(self, 'email_taken', state)
+                    msg = self.message('email_taken', state)
                     raise formencode.Invalid(msg, value, state,
                         error_dict=dict(email=msg)
                     )
@@ -701,9 +686,9 @@
             return value.lower()
 
         def validate_python(self, value, state):
-            user = User.get_by_email(value, case_insensitive=True)
+            user = User.get_by_email(value)
             if user is None:
-                msg = M(self, 'non_existing_email', state, email=value)
+                msg = self.message('non_existing_email', state, email=value)
                 raise formencode.Invalid(msg, value, state,
                     error_dict=dict(email=msg)
                 )
@@ -753,11 +738,11 @@
             v = v.strip()
             net = ipaddr.IPNetwork(address=v)
             if isinstance(net, ipaddr.IPv4Network):
-                #if IPv4 doesn't end with a mask, add /32
+                # if IPv4 doesn't end with a mask, add /32
                 if '/' not in value:
                     v += '/32'
             if isinstance(net, ipaddr.IPv6Network):
-                #if IPv6 doesn't end with a mask, add /128
+                # if IPv6 doesn't end with a mask, add /128
                 if '/' not in value:
                     v += '/128'
             return v
@@ -765,7 +750,7 @@
         def validate_python(self, value, state):
             try:
                 addr = value.strip()
-                #this raises an ValueError if address is not IPv4 or IPv6
+                # this raises an ValueError if address is not IPv4 or IPv6
                 ipaddr.IPNetwork(address=addr)
             except ValueError:
                 raise formencode.Invalid(self.message('badFormat', state),
@@ -823,7 +808,7 @@
                     plugin = auth_modules.loadplugin(module)
                     plugin_name = plugin.name
                     if plugin_name in unique_names:
-                        msg = M(self, 'import_duplicate', state,
+                        msg = self.message('import_duplicate', state,
                                 loaded=unique_names[plugin_name],
                                 next_to_load=plugin_name)
                         raise formencode.Invalid(msg, value, state)
--- a/kallithea/public/codemirror/LICENSE	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-Copyright (C) 2014 by Marijn Haverbeke <marijnh@gmail.com> and others
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
--- a/kallithea/public/codemirror/lib/codemirror.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,309 +0,0 @@
-/* BASICS */
-
-.CodeMirror {
-  /* Set height, width, borders, and global font properties here */
-  font-family: monospace;
-  height: 300px;
-}
-.CodeMirror-scroll {
-  /* Set scrolling behaviour here */
-  overflow: auto;
-}
-
-/* PADDING */
-
-.CodeMirror-lines {
-  padding: 4px 0; /* Vertical padding around content */
-}
-.CodeMirror pre {
-  padding: 0 4px; /* Horizontal padding of content */
-}
-
-.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
-  background-color: white; /* The little square between H and V scrollbars */
-}
-
-/* GUTTER */
-
-.CodeMirror-gutters {
-  border-right: 1px solid #ddd;
-  background-color: #f7f7f7;
-  white-space: nowrap;
-}
-.CodeMirror-linenumbers {}
-.CodeMirror-linenumber {
-  padding: 0 3px 0 5px;
-  min-width: 20px;
-  text-align: right;
-  color: #999;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-
-.CodeMirror-guttermarker { color: black; }
-.CodeMirror-guttermarker-subtle { color: #999; }
-
-/* CURSOR */
-
-.CodeMirror div.CodeMirror-cursor {
-  border-left: 1px solid black;
-}
-/* Shown when moving in bi-directional text */
-.CodeMirror div.CodeMirror-secondarycursor {
-  border-left: 1px solid silver;
-}
-.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
-  width: auto;
-  border: 0;
-  background: #7e7;
-}
-.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors {
-  z-index: 1;
-}
-
-.cm-animate-fat-cursor {
-  width: auto;
-  border: 0;
-  -webkit-animation: blink 1.06s steps(1) infinite;
-  -moz-animation: blink 1.06s steps(1) infinite;
-  animation: blink 1.06s steps(1) infinite;
-}
-@-moz-keyframes blink {
-  0% { background: #7e7; }
-  50% { background: none; }
-  100% { background: #7e7; }
-}
-@-webkit-keyframes blink {
-  0% { background: #7e7; }
-  50% { background: none; }
-  100% { background: #7e7; }
-}
-@keyframes blink {
-  0% { background: #7e7; }
-  50% { background: none; }
-  100% { background: #7e7; }
-}
-
-/* Can style cursor different in overwrite (non-insert) mode */
-div.CodeMirror-overwrite div.CodeMirror-cursor {}
-
-.cm-tab { display: inline-block; text-decoration: inherit; }
-
-.CodeMirror-ruler {
-  border-left: 1px solid #ccc;
-  position: absolute;
-}
-
-/* DEFAULT THEME */
-
-.cm-s-default .cm-keyword {color: #708;}
-.cm-s-default .cm-atom {color: #219;}
-.cm-s-default .cm-number {color: #164;}
-.cm-s-default .cm-def {color: #00f;}
-.cm-s-default .cm-variable,
-.cm-s-default .cm-punctuation,
-.cm-s-default .cm-property,
-.cm-s-default .cm-operator {}
-.cm-s-default .cm-variable-2 {color: #05a;}
-.cm-s-default .cm-variable-3 {color: #085;}
-.cm-s-default .cm-comment {color: #a50;}
-.cm-s-default .cm-string {color: #a11;}
-.cm-s-default .cm-string-2 {color: #f50;}
-.cm-s-default .cm-meta {color: #555;}
-.cm-s-default .cm-qualifier {color: #555;}
-.cm-s-default .cm-builtin {color: #30a;}
-.cm-s-default .cm-bracket {color: #997;}
-.cm-s-default .cm-tag {color: #170;}
-.cm-s-default .cm-attribute {color: #00c;}
-.cm-s-default .cm-header {color: blue;}
-.cm-s-default .cm-quote {color: #090;}
-.cm-s-default .cm-hr {color: #999;}
-.cm-s-default .cm-link {color: #00c;}
-
-.cm-negative {color: #d44;}
-.cm-positive {color: #292;}
-.cm-header, .cm-strong {font-weight: bold;}
-.cm-em {font-style: italic;}
-.cm-link {text-decoration: underline;}
-
-.cm-s-default .cm-error {color: #f00;}
-.cm-invalidchar {color: #f00;}
-
-/* Default styles for common addons */
-
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
-.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
-.CodeMirror-activeline-background {background: #e8f2ff;}
-
-/* STOP */
-
-/* The rest of this file contains styles related to the mechanics of
-   the editor. You probably shouldn't touch them. */
-
-.CodeMirror {
-  line-height: 1;
-  position: relative;
-  overflow: hidden;
-  background: white;
-  color: black;
-}
-
-.CodeMirror-scroll {
-  /* 30px is the magic margin used to hide the element's real scrollbars */
-  /* See overflow: hidden in .CodeMirror */
-  margin-bottom: -30px; margin-right: -30px;
-  padding-bottom: 30px;
-  height: 100%;
-  outline: none; /* Prevent dragging from highlighting the element */
-  position: relative;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-.CodeMirror-sizer {
-  position: relative;
-  border-right: 30px solid transparent;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-}
-
-/* The fake, visible scrollbars. Used to force redraw during scrolling
-   before actuall scrolling happens, thus preventing shaking and
-   flickering artifacts. */
-.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
-  position: absolute;
-  z-index: 6;
-  display: none;
-}
-.CodeMirror-vscrollbar {
-  right: 0; top: 0;
-  overflow-x: hidden;
-  overflow-y: scroll;
-}
-.CodeMirror-hscrollbar {
-  bottom: 0; left: 0;
-  overflow-y: hidden;
-  overflow-x: scroll;
-}
-.CodeMirror-scrollbar-filler {
-  right: 0; bottom: 0;
-}
-.CodeMirror-gutter-filler {
-  left: 0; bottom: 0;
-}
-
-.CodeMirror-gutters {
-  position: absolute; left: 0; top: 0;
-  padding-bottom: 30px;
-  z-index: 3;
-}
-.CodeMirror-gutter {
-  white-space: normal;
-  height: 100%;
-  -moz-box-sizing: content-box;
-  box-sizing: content-box;
-  padding-bottom: 30px;
-  margin-bottom: -32px;
-  display: inline-block;
-  /* Hack to make IE7 behave */
-  *zoom:1;
-  *display:inline;
-}
-.CodeMirror-gutter-elt {
-  position: absolute;
-  cursor: default;
-  z-index: 4;
-}
-
-.CodeMirror-lines {
-  cursor: text;
-  min-height: 1px; /* prevents collapsing before first draw */
-}
-.CodeMirror pre {
-  /* Reset some styles that the rest of the page might have set */
-  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
-  border-width: 0;
-  background: transparent;
-  font-family: inherit;
-  font-size: inherit;
-  margin: 0;
-  white-space: pre;
-  word-wrap: normal;
-  line-height: inherit;
-  color: inherit;
-  z-index: 2;
-  position: relative;
-  overflow: visible;
-}
-.CodeMirror-wrap pre {
-  word-wrap: break-word;
-  white-space: pre-wrap;
-  word-break: normal;
-}
-
-.CodeMirror-linebackground {
-  position: absolute;
-  left: 0; right: 0; top: 0; bottom: 0;
-  z-index: 0;
-}
-
-.CodeMirror-linewidget {
-  position: relative;
-  z-index: 2;
-  overflow: auto;
-}
-
-.CodeMirror-widget {}
-
-.CodeMirror-wrap .CodeMirror-scroll {
-  overflow-x: hidden;
-}
-
-.CodeMirror-measure {
-  position: absolute;
-  width: 100%;
-  height: 0;
-  overflow: hidden;
-  visibility: hidden;
-}
-.CodeMirror-measure pre { position: static; }
-
-.CodeMirror div.CodeMirror-cursor {
-  position: absolute;
-  border-right: none;
-  width: 0;
-}
-
-div.CodeMirror-cursors {
-  visibility: hidden;
-  position: relative;
-  z-index: 3;
-}
-.CodeMirror-focused div.CodeMirror-cursors {
-  visibility: visible;
-}
-
-.CodeMirror-selected { background: #d9d9d9; }
-.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
-.CodeMirror-crosshair { cursor: crosshair; }
-
-.cm-searching {
-  background: #ffa;
-  background: rgba(255, 255, 0, .4);
-}
-
-/* IE7 hack to prevent it from returning funny offsetTops on the spans */
-.CodeMirror span { *vertical-align: text-bottom; }
-
-/* Used to force a border model for a node */
-.cm-force-border { padding-right: .1px; }
-
-@media print {
-  /* Hide the cursor when printing */
-  .CodeMirror div.CodeMirror-cursors {
-    visibility: hidden;
-  }
-}
-
-/* Help users use markselection to safely style text background */
-span.CodeMirror-selectedtext { background: none; }
--- a/kallithea/public/codemirror/lib/codemirror.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7830 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// This is CodeMirror (http://codemirror.net), a code editor
-// implemented in JavaScript on top of the browser's DOM.
-//
-// You can find some technical background for some of the code below
-// at http://marijnhaverbeke.nl/blog/#cm-internals .
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    module.exports = mod();
-  else if (typeof define == "function" && define.amd) // AMD
-    return define([], mod);
-  else // Plain browser env
-    this.CodeMirror = mod();
-})(function() {
-  "use strict";
-
-  // BROWSER SNIFFING
-
-  // Kludges for bugs and behavior differences that can't be feature
-  // detected are enabled based on userAgent etc sniffing.
-
-  var gecko = /gecko\/\d/i.test(navigator.userAgent);
-  // ie_uptoN means Internet Explorer version N or lower
-  var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
-  var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
-  var ie = ie_upto10 || ie_11up;
-  var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
-  var webkit = /WebKit\//.test(navigator.userAgent);
-  var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
-  var chrome = /Chrome\//.test(navigator.userAgent);
-  var presto = /Opera\//.test(navigator.userAgent);
-  var safari = /Apple Computer/.test(navigator.vendor);
-  var khtml = /KHTML\//.test(navigator.userAgent);
-  var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
-  var phantom = /PhantomJS/.test(navigator.userAgent);
-
-  var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
-  // This is woefully incomplete. Suggestions for alternative methods welcome.
-  var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
-  var mac = ios || /Mac/.test(navigator.platform);
-  var windows = /win/i.test(navigator.platform);
-
-  var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
-  if (presto_version) presto_version = Number(presto_version[1]);
-  if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
-  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
-  var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
-  var captureRightClick = gecko || (ie && ie_version >= 9);
-
-  // Optimize some code when these features are not used.
-  var sawReadOnlySpans = false, sawCollapsedSpans = false;
-
-  // EDITOR CONSTRUCTOR
-
-  // A CodeMirror instance represents an editor. This is the object
-  // that user code is usually dealing with.
-
-  function CodeMirror(place, options) {
-    if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
-
-    this.options = options = options ? copyObj(options) : {};
-    // Determine effective options based on given values and defaults.
-    copyObj(defaults, options, false);
-    setGuttersForLineNumbers(options);
-
-    var doc = options.value;
-    if (typeof doc == "string") doc = new Doc(doc, options.mode);
-    this.doc = doc;
-
-    var display = this.display = new Display(place, doc);
-    display.wrapper.CodeMirror = this;
-    updateGutters(this);
-    themeChanged(this);
-    if (options.lineWrapping)
-      this.display.wrapper.className += " CodeMirror-wrap";
-    if (options.autofocus && !mobile) focusInput(this);
-
-    this.state = {
-      keyMaps: [],  // stores maps added by addKeyMap
-      overlays: [], // highlighting overlays, as added by addOverlay
-      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info
-      overwrite: false, focused: false,
-      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
-      pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
-      draggingText: false,
-      highlight: new Delayed() // stores highlight worker timeout
-    };
-
-    // Override magic textarea content restore that IE sometimes does
-    // on our hidden textarea on reload
-    if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);
-
-    registerEventHandlers(this);
-    ensureGlobalHandlers();
-
-    startOperation(this);
-    this.curOp.forceUpdate = true;
-    attachDoc(this, doc);
-
-    if ((options.autofocus && !mobile) || activeElt() == display.input)
-      setTimeout(bind(onFocus, this), 20);
-    else
-      onBlur(this);
-
-    for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
-      optionHandlers[opt](this, options[opt], Init);
-    maybeUpdateLineNumberWidth(this);
-    for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
-    endOperation(this);
-  }
-
-  // DISPLAY CONSTRUCTOR
-
-  // The display handles the DOM integration, both for input reading
-  // and content drawing. It holds references to DOM nodes and
-  // display-related state.
-
-  function Display(place, doc) {
-    var d = this;
-
-    // The semihidden textarea that is focused when the editor is
-    // focused, and receives input.
-    var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
-    // The textarea is kept positioned near the cursor to prevent the
-    // fact that it'll be scrolled into view on input from scrolling
-    // our fake cursor out of view. On webkit, when wrap=off, paste is
-    // very slow. So make the area wide instead.
-    if (webkit) input.style.width = "1000px";
-    else input.setAttribute("wrap", "off");
-    // If border: 0; -- iOS fails to open keyboard (issue #1287)
-    if (ios) input.style.border = "1px solid black";
-    input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
-
-    // Wraps and hides input textarea
-    d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
-    // The fake scrollbar elements.
-    d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
-    d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
-    // Covers bottom-right square when both scrollbars are present.
-    d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
-    // Covers bottom of gutter when coverGutterNextToScrollbar is on
-    // and h scrollbar is present.
-    d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
-    // Will contain the actual code, positioned to cover the viewport.
-    d.lineDiv = elt("div", null, "CodeMirror-code");
-    // Elements are added to these to represent selection and cursors.
-    d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
-    d.cursorDiv = elt("div", null, "CodeMirror-cursors");
-    // A visibility: hidden element used to find the size of things.
-    d.measure = elt("div", null, "CodeMirror-measure");
-    // When lines outside of the viewport are measured, they are drawn in this.
-    d.lineMeasure = elt("div", null, "CodeMirror-measure");
-    // Wraps everything that needs to exist inside the vertically-padded coordinate system
-    d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
-                      null, "position: relative; outline: none");
-    // Moved around its parent to cover visible view.
-    d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
-    // Set to the height of the document, allowing scrolling.
-    d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
-    // Behavior of elts with overflow: auto and padding is
-    // inconsistent across browsers. This is used to ensure the
-    // scrollable area is big enough.
-    d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
-    // Will contain the gutters, if any.
-    d.gutters = elt("div", null, "CodeMirror-gutters");
-    d.lineGutter = null;
-    // Actual scrollable element.
-    d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
-    d.scroller.setAttribute("tabIndex", "-1");
-    // The element in which the editor lives.
-    d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
-                            d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
-
-    // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
-    if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
-    // Needed to hide big blue blinking cursor on Mobile Safari
-    if (ios) input.style.width = "0px";
-    if (!webkit) d.scroller.draggable = true;
-    // Needed to handle Tab key in KHTML
-    if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
-    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
-    if (ie && ie_version < 8) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
-
-    if (place.appendChild) place.appendChild(d.wrapper);
-    else place(d.wrapper);
-
-    // Current rendered range (may be bigger than the view window).
-    d.viewFrom = d.viewTo = doc.first;
-    // Information about the rendered lines.
-    d.view = [];
-    // Holds info about a single rendered line when it was rendered
-    // for measurement, while not in view.
-    d.externalMeasured = null;
-    // Empty space (in pixels) above the view
-    d.viewOffset = 0;
-    d.lastSizeC = 0;
-    d.updateLineNumbers = null;
-
-    // Used to only resize the line number gutter when necessary (when
-    // the amount of lines crosses a boundary that makes its width change)
-    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
-    // See readInput and resetInput
-    d.prevInput = "";
-    // Set to true when a non-horizontal-scrolling line widget is
-    // added. As an optimization, line widget aligning is skipped when
-    // this is false.
-    d.alignWidgets = false;
-    // Flag that indicates whether we expect input to appear real soon
-    // now (after some event like 'keypress' or 'input') and are
-    // polling intensively.
-    d.pollingFast = false;
-    // Self-resetting timeout for the poller
-    d.poll = new Delayed();
-
-    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
-
-    // Tracks when resetInput has punted to just putting a short
-    // string into the textarea instead of the full selection.
-    d.inaccurateSelection = false;
-
-    // Tracks the maximum line length so that the horizontal scrollbar
-    // can be kept static when scrolling.
-    d.maxLine = null;
-    d.maxLineLength = 0;
-    d.maxLineChanged = false;
-
-    // Used for measuring wheel scrolling granularity
-    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
-
-    // True when shift is held down.
-    d.shift = false;
-
-    // Used to track whether anything happened since the context menu
-    // was opened.
-    d.selForContextMenu = null;
-  }
-
-  // STATE UPDATES
-
-  // Used to get the editor into a consistent state again when options change.
-
-  function loadMode(cm) {
-    cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
-    resetModeState(cm);
-  }
-
-  function resetModeState(cm) {
-    cm.doc.iter(function(line) {
-      if (line.stateAfter) line.stateAfter = null;
-      if (line.styles) line.styles = null;
-    });
-    cm.doc.frontier = cm.doc.first;
-    startWorker(cm, 100);
-    cm.state.modeGen++;
-    if (cm.curOp) regChange(cm);
-  }
-
-  function wrappingChanged(cm) {
-    if (cm.options.lineWrapping) {
-      addClass(cm.display.wrapper, "CodeMirror-wrap");
-      cm.display.sizer.style.minWidth = "";
-    } else {
-      rmClass(cm.display.wrapper, "CodeMirror-wrap");
-      findMaxLine(cm);
-    }
-    estimateLineHeights(cm);
-    regChange(cm);
-    clearCaches(cm);
-    setTimeout(function(){updateScrollbars(cm);}, 100);
-  }
-
-  // Returns a function that estimates the height of a line, to use as
-  // first approximation until the line becomes visible (and is thus
-  // properly measurable).
-  function estimateHeight(cm) {
-    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
-    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
-    return function(line) {
-      if (lineIsHidden(cm.doc, line)) return 0;
-
-      var widgetsHeight = 0;
-      if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
-        if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
-      }
-
-      if (wrapping)
-        return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
-      else
-        return widgetsHeight + th;
-    };
-  }
-
-  function estimateLineHeights(cm) {
-    var doc = cm.doc, est = estimateHeight(cm);
-    doc.iter(function(line) {
-      var estHeight = est(line);
-      if (estHeight != line.height) updateLineHeight(line, estHeight);
-    });
-  }
-
-  function keyMapChanged(cm) {
-    var map = keyMap[cm.options.keyMap], style = map.style;
-    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
-      (style ? " cm-keymap-" + style : "");
-  }
-
-  function themeChanged(cm) {
-    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
-      cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
-    clearCaches(cm);
-  }
-
-  function guttersChanged(cm) {
-    updateGutters(cm);
-    regChange(cm);
-    setTimeout(function(){alignHorizontally(cm);}, 20);
-  }
-
-  // Rebuild the gutter elements, ensure the margin to the left of the
-  // code matches their width.
-  function updateGutters(cm) {
-    var gutters = cm.display.gutters, specs = cm.options.gutters;
-    removeChildren(gutters);
-    for (var i = 0; i < specs.length; ++i) {
-      var gutterClass = specs[i];
-      var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
-      if (gutterClass == "CodeMirror-linenumbers") {
-        cm.display.lineGutter = gElt;
-        gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
-      }
-    }
-    gutters.style.display = i ? "" : "none";
-    updateGutterSpace(cm);
-  }
-
-  function updateGutterSpace(cm) {
-    var width = cm.display.gutters.offsetWidth;
-    cm.display.sizer.style.marginLeft = width + "px";
-    cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
-  }
-
-  // Compute the character length of a line, taking into account
-  // collapsed ranges (see markText) that might hide parts, and join
-  // other lines onto it.
-  function lineLength(line) {
-    if (line.height == 0) return 0;
-    var len = line.text.length, merged, cur = line;
-    while (merged = collapsedSpanAtStart(cur)) {
-      var found = merged.find(0, true);
-      cur = found.from.line;
-      len += found.from.ch - found.to.ch;
-    }
-    cur = line;
-    while (merged = collapsedSpanAtEnd(cur)) {
-      var found = merged.find(0, true);
-      len -= cur.text.length - found.from.ch;
-      cur = found.to.line;
-      len += cur.text.length - found.to.ch;
-    }
-    return len;
-  }
-
-  // Find the longest line in the document.
-  function findMaxLine(cm) {
-    var d = cm.display, doc = cm.doc;
-    d.maxLine = getLine(doc, doc.first);
-    d.maxLineLength = lineLength(d.maxLine);
-    d.maxLineChanged = true;
-    doc.iter(function(line) {
-      var len = lineLength(line);
-      if (len > d.maxLineLength) {
-        d.maxLineLength = len;
-        d.maxLine = line;
-      }
-    });
-  }
-
-  // Make sure the gutters options contains the element
-  // "CodeMirror-linenumbers" when the lineNumbers option is true.
-  function setGuttersForLineNumbers(options) {
-    var found = indexOf(options.gutters, "CodeMirror-linenumbers");
-    if (found == -1 && options.lineNumbers) {
-      options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]);
-    } else if (found > -1 && !options.lineNumbers) {
-      options.gutters = options.gutters.slice(0);
-      options.gutters.splice(found, 1);
-    }
-  }
-
-  // SCROLLBARS
-
-  function hScrollbarTakesSpace(cm) {
-    return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3;
-  }
-
-  // Prepare DOM reads needed to update the scrollbars. Done in one
-  // shot to minimize update/measure roundtrips.
-  function measureForScrollbars(cm) {
-    var scroll = cm.display.scroller;
-    return {
-      clientHeight: scroll.clientHeight,
-      barHeight: cm.display.scrollbarV.clientHeight,
-      scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
-      hScrollbarTakesSpace: hScrollbarTakesSpace(cm),
-      barWidth: cm.display.scrollbarH.clientWidth,
-      docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
-    };
-  }
-
-  // Re-synchronize the fake scrollbars with the actual size of the
-  // content.
-  function updateScrollbars(cm, measure) {
-    if (!measure) measure = measureForScrollbars(cm);
-    var d = cm.display, sWidth = scrollbarWidth(d.measure);
-    var scrollHeight = measure.docHeight + scrollerCutOff;
-    var needsH = measure.scrollWidth > measure.clientWidth;
-    if (needsH && measure.scrollWidth <= measure.clientWidth + 1 &&
-        sWidth > 0 && !measure.hScrollbarTakesSpace)
-      needsH = false; // (Issue #2562)
-    var needsV = scrollHeight > measure.clientHeight;
-
-    if (needsV) {
-      d.scrollbarV.style.display = "block";
-      d.scrollbarV.style.bottom = needsH ? sWidth + "px" : "0";
-      // A bug in IE8 can cause this value to be negative, so guard it.
-      d.scrollbarV.firstChild.style.height =
-        Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
-    } else {
-      d.scrollbarV.style.display = "";
-      d.scrollbarV.firstChild.style.height = "0";
-    }
-    if (needsH) {
-      d.scrollbarH.style.display = "block";
-      d.scrollbarH.style.right = needsV ? sWidth + "px" : "0";
-      d.scrollbarH.firstChild.style.width =
-        (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
-    } else {
-      d.scrollbarH.style.display = "";
-      d.scrollbarH.firstChild.style.width = "0";
-    }
-    if (needsH && needsV) {
-      d.scrollbarFiller.style.display = "block";
-      d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px";
-    } else d.scrollbarFiller.style.display = "";
-    if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
-      d.gutterFiller.style.display = "block";
-      d.gutterFiller.style.height = sWidth + "px";
-      d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
-    } else d.gutterFiller.style.display = "";
-
-    if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
-      if (sWidth === 0) {
-        var w = mac && !mac_geMountainLion ? "12px" : "18px";
-        d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
-        var barMouseDown = function(e) {
-          if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
-            operation(cm, onMouseDown)(e);
-        };
-        on(d.scrollbarV, "mousedown", barMouseDown);
-        on(d.scrollbarH, "mousedown", barMouseDown);
-      }
-      cm.state.checkedOverlayScrollbar = true;
-    }
-  }
-
-  // Compute the lines that are visible in a given viewport (defaults
-  // the the current scroll position). viewport may contain top,
-  // height, and ensure (see op.scrollToPos) properties.
-  function visibleLines(display, doc, viewport) {
-    var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;
-    top = Math.floor(top - paddingTop(display));
-    var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;
-
-    var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
-    // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
-    // forces those lines into the viewport (if possible).
-    if (viewport && viewport.ensure) {
-      var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;
-      if (ensureFrom < from)
-        return {from: ensureFrom,
-                to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
-      if (Math.min(ensureTo, doc.lastLine()) >= to)
-        return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
-                to: ensureTo};
-    }
-    return {from: from, to: Math.max(to, from + 1)};
-  }
-
-  // LINE NUMBERS
-
-  // Re-align line numbers and gutter marks to compensate for
-  // horizontal scrolling.
-  function alignHorizontally(cm) {
-    var display = cm.display, view = display.view;
-    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
-    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
-    var gutterW = display.gutters.offsetWidth, left = comp + "px";
-    for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
-      if (cm.options.fixedGutter && view[i].gutter)
-        view[i].gutter.style.left = left;
-      var align = view[i].alignable;
-      if (align) for (var j = 0; j < align.length; j++)
-        align[j].style.left = left;
-    }
-    if (cm.options.fixedGutter)
-      display.gutters.style.left = (comp + gutterW) + "px";
-  }
-
-  // Used to ensure that the line number gutter is still the right
-  // size for the current document size. Returns true when an update
-  // is needed.
-  function maybeUpdateLineNumberWidth(cm) {
-    if (!cm.options.lineNumbers) return false;
-    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
-    if (last.length != display.lineNumChars) {
-      var test = display.measure.appendChild(elt("div", [elt("div", last)],
-                                                 "CodeMirror-linenumber CodeMirror-gutter-elt"));
-      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
-      display.lineGutter.style.width = "";
-      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
-      display.lineNumWidth = display.lineNumInnerWidth + padding;
-      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
-      display.lineGutter.style.width = display.lineNumWidth + "px";
-      updateGutterSpace(cm);
-      return true;
-    }
-    return false;
-  }
-
-  function lineNumberFor(options, i) {
-    return String(options.lineNumberFormatter(i + options.firstLineNumber));
-  }
-
-  // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
-  // but using getBoundingClientRect to get a sub-pixel-accurate
-  // result.
-  function compensateForHScroll(display) {
-    return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
-  }
-
-  // DISPLAY DRAWING
-
-  function DisplayUpdate(cm, viewport, force) {
-    var display = cm.display;
-
-    this.viewport = viewport;
-    // Store some values that we'll need later (but don't want to force a relayout for)
-    this.visible = visibleLines(display, cm.doc, viewport);
-    this.editorIsHidden = !display.wrapper.offsetWidth;
-    this.wrapperHeight = display.wrapper.clientHeight;
-    this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo;
-    this.oldScrollerWidth = display.scroller.clientWidth;
-    this.force = force;
-    this.dims = getDimensions(cm);
-  }
-
-  // Does the actual updating of the line display. Bails out
-  // (returning false) when there is nothing to be done and forced is
-  // false.
-  function updateDisplayIfNeeded(cm, update) {
-    var display = cm.display, doc = cm.doc;
-    if (update.editorIsHidden) {
-      resetView(cm);
-      return false;
-    }
-
-    // Bail out if the visible area is already rendered and nothing changed.
-    if (!update.force &&
-        update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
-        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
-        countDirtyView(cm) == 0)
-      return false;
-
-    if (maybeUpdateLineNumberWidth(cm)) {
-      resetView(cm);
-      update.dims = getDimensions(cm);
-    }
-
-    // Compute a suitable new viewport (from & to)
-    var end = doc.first + doc.size;
-    var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
-    var to = Math.min(end, update.visible.to + cm.options.viewportMargin);
-    if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
-    if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
-    if (sawCollapsedSpans) {
-      from = visualLineNo(cm.doc, from);
-      to = visualLineEndNo(cm.doc, to);
-    }
-
-    var different = from != display.viewFrom || to != display.viewTo ||
-      display.lastSizeC != update.wrapperHeight;
-    adjustView(cm, from, to);
-
-    display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
-    // Position the mover div to align with the current scroll position
-    cm.display.mover.style.top = display.viewOffset + "px";
-
-    var toUpdate = countDirtyView(cm);
-    if (!different && toUpdate == 0 && !update.force &&
-        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
-      return false;
-
-    // For big changes, we hide the enclosing element during the
-    // update, since that speeds up the operations on most browsers.
-    var focused = activeElt();
-    if (toUpdate > 4) display.lineDiv.style.display = "none";
-    patchDisplay(cm, display.updateLineNumbers, update.dims);
-    if (toUpdate > 4) display.lineDiv.style.display = "";
-    // There might have been a widget with a focused element that got
-    // hidden or updated, if so re-focus it.
-    if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
-
-    // Prevent selection and cursors from interfering with the scroll
-    // width.
-    removeChildren(display.cursorDiv);
-    removeChildren(display.selectionDiv);
-
-    if (different) {
-      display.lastSizeC = update.wrapperHeight;
-      startWorker(cm, 400);
-    }
-
-    display.updateLineNumbers = null;
-
-    return true;
-  }
-
-  function postUpdateDisplay(cm, update) {
-    var force = update.force, viewport = update.viewport;
-    for (var first = true;; first = false) {
-      if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) {
-        force = true;
-      } else {
-        force = false;
-        // Clip forced viewport to actual scrollable area.
-        if (viewport && viewport.top != null)
-          viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - scrollerCutOff -
-                                    cm.display.scroller.clientHeight, viewport.top)};
-        // Updated line heights might result in the drawn area not
-        // actually covering the viewport. Keep looping until it does.
-        update.visible = visibleLines(cm.display, cm.doc, viewport);
-        if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)
-          break;
-      }
-      if (!updateDisplayIfNeeded(cm, update)) break;
-      updateHeightsInViewport(cm);
-      var barMeasure = measureForScrollbars(cm);
-      updateSelection(cm);
-      setDocumentHeight(cm, barMeasure);
-      updateScrollbars(cm, barMeasure);
-    }
-
-    signalLater(cm, "update", cm);
-    if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo)
-      signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
-  }
-
-  function updateDisplaySimple(cm, viewport) {
-    var update = new DisplayUpdate(cm, viewport);
-    if (updateDisplayIfNeeded(cm, update)) {
-      updateHeightsInViewport(cm);
-      postUpdateDisplay(cm, update);
-      var barMeasure = measureForScrollbars(cm);
-      updateSelection(cm);
-      setDocumentHeight(cm, barMeasure);
-      updateScrollbars(cm, barMeasure);
-    }
-  }
-
-  function setDocumentHeight(cm, measure) {
-    cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
-    cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
-  }
-
-  function checkForWebkitWidthBug(cm, measure) {
-    // Work around Webkit bug where it sometimes reserves space for a
-    // non-existing phantom scrollbar in the scroller (Issue #2420)
-    if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) {
-      cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px";
-      cm.display.gutters.style.height = measure.docHeight + "px";
-    }
-  }
-
-  // Read the actual heights of the rendered lines, and update their
-  // stored heights to match.
-  function updateHeightsInViewport(cm) {
-    var display = cm.display;
-    var prevBottom = display.lineDiv.offsetTop;
-    for (var i = 0; i < display.view.length; i++) {
-      var cur = display.view[i], height;
-      if (cur.hidden) continue;
-      if (ie && ie_version < 8) {
-        var bot = cur.node.offsetTop + cur.node.offsetHeight;
-        height = bot - prevBottom;
-        prevBottom = bot;
-      } else {
-        var box = cur.node.getBoundingClientRect();
-        height = box.bottom - box.top;
-      }
-      var diff = cur.line.height - height;
-      if (height < 2) height = textHeight(display);
-      if (diff > .001 || diff < -.001) {
-        updateLineHeight(cur.line, height);
-        updateWidgetHeight(cur.line);
-        if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
-          updateWidgetHeight(cur.rest[j]);
-      }
-    }
-  }
-
-  // Read and store the height of line widgets associated with the
-  // given line.
-  function updateWidgetHeight(line) {
-    if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
-      line.widgets[i].height = line.widgets[i].node.offsetHeight;
-  }
-
-  // Do a bulk-read of the DOM positions and sizes needed to draw the
-  // view, so that we don't interleave reading and writing to the DOM.
-  function getDimensions(cm) {
-    var d = cm.display, left = {}, width = {};
-    var gutterLeft = d.gutters.clientLeft;
-    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
-      left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft;
-      width[cm.options.gutters[i]] = n.clientWidth;
-    }
-    return {fixedPos: compensateForHScroll(d),
-            gutterTotalWidth: d.gutters.offsetWidth,
-            gutterLeft: left,
-            gutterWidth: width,
-            wrapperWidth: d.wrapper.clientWidth};
-  }
-
-  // Sync the actual display DOM structure with display.view, removing
-  // nodes for lines that are no longer in view, and creating the ones
-  // that are not there yet, and updating the ones that are out of
-  // date.
-  function patchDisplay(cm, updateNumbersFrom, dims) {
-    var display = cm.display, lineNumbers = cm.options.lineNumbers;
-    var container = display.lineDiv, cur = container.firstChild;
-
-    function rm(node) {
-      var next = node.nextSibling;
-      // Works around a throw-scroll bug in OS X Webkit
-      if (webkit && mac && cm.display.currentWheelTarget == node)
-        node.style.display = "none";
-      else
-        node.parentNode.removeChild(node);
-      return next;
-    }
-
-    var view = display.view, lineN = display.viewFrom;
-    // Loop over the elements in the view, syncing cur (the DOM nodes
-    // in display.lineDiv) with the view as we go.
-    for (var i = 0; i < view.length; i++) {
-      var lineView = view[i];
-      if (lineView.hidden) {
-      } else if (!lineView.node) { // Not drawn yet
-        var node = buildLineElement(cm, lineView, lineN, dims);
-        container.insertBefore(node, cur);
-      } else { // Already drawn
-        while (cur != lineView.node) cur = rm(cur);
-        var updateNumber = lineNumbers && updateNumbersFrom != null &&
-          updateNumbersFrom <= lineN && lineView.lineNumber;
-        if (lineView.changes) {
-          if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
-          updateLineForChanges(cm, lineView, lineN, dims);
-        }
-        if (updateNumber) {
-          removeChildren(lineView.lineNumber);
-          lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
-        }
-        cur = lineView.node.nextSibling;
-      }
-      lineN += lineView.size;
-    }
-    while (cur) cur = rm(cur);
-  }
-
-  // When an aspect of a line changes, a string is added to
-  // lineView.changes. This updates the relevant part of the line's
-  // DOM structure.
-  function updateLineForChanges(cm, lineView, lineN, dims) {
-    for (var j = 0; j < lineView.changes.length; j++) {
-      var type = lineView.changes[j];
-      if (type == "text") updateLineText(cm, lineView);
-      else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
-      else if (type == "class") updateLineClasses(lineView);
-      else if (type == "widget") updateLineWidgets(lineView, dims);
-    }
-    lineView.changes = null;
-  }
-
-  // Lines with gutter elements, widgets or a background class need to
-  // be wrapped, and have the extra elements added to the wrapper div
-  function ensureLineWrapped(lineView) {
-    if (lineView.node == lineView.text) {
-      lineView.node = elt("div", null, null, "position: relative");
-      if (lineView.text.parentNode)
-        lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
-      lineView.node.appendChild(lineView.text);
-      if (ie && ie_version < 8) lineView.node.style.zIndex = 2;
-    }
-    return lineView.node;
-  }
-
-  function updateLineBackground(lineView) {
-    var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
-    if (cls) cls += " CodeMirror-linebackground";
-    if (lineView.background) {
-      if (cls) lineView.background.className = cls;
-      else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
-    } else if (cls) {
-      var wrap = ensureLineWrapped(lineView);
-      lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
-    }
-  }
-
-  // Wrapper around buildLineContent which will reuse the structure
-  // in display.externalMeasured when possible.
-  function getLineContent(cm, lineView) {
-    var ext = cm.display.externalMeasured;
-    if (ext && ext.line == lineView.line) {
-      cm.display.externalMeasured = null;
-      lineView.measure = ext.measure;
-      return ext.built;
-    }
-    return buildLineContent(cm, lineView);
-  }
-
-  // Redraw the line's text. Interacts with the background and text
-  // classes because the mode may output tokens that influence these
-  // classes.
-  function updateLineText(cm, lineView) {
-    var cls = lineView.text.className;
-    var built = getLineContent(cm, lineView);
-    if (lineView.text == lineView.node) lineView.node = built.pre;
-    lineView.text.parentNode.replaceChild(built.pre, lineView.text);
-    lineView.text = built.pre;
-    if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
-      lineView.bgClass = built.bgClass;
-      lineView.textClass = built.textClass;
-      updateLineClasses(lineView);
-    } else if (cls) {
-      lineView.text.className = cls;
-    }
-  }
-
-  function updateLineClasses(lineView) {
-    updateLineBackground(lineView);
-    if (lineView.line.wrapClass)
-      ensureLineWrapped(lineView).className = lineView.line.wrapClass;
-    else if (lineView.node != lineView.text)
-      lineView.node.className = "";
-    var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
-    lineView.text.className = textClass || "";
-  }
-
-  function updateLineGutter(cm, lineView, lineN, dims) {
-    if (lineView.gutter) {
-      lineView.node.removeChild(lineView.gutter);
-      lineView.gutter = null;
-    }
-    var markers = lineView.line.gutterMarkers;
-    if (cm.options.lineNumbers || markers) {
-      var wrap = ensureLineWrapped(lineView);
-      var gutterWrap = lineView.gutter =
-        wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
-                              (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
-                          lineView.text);
-      if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
-        lineView.lineNumber = gutterWrap.appendChild(
-          elt("div", lineNumberFor(cm.options, lineN),
-              "CodeMirror-linenumber CodeMirror-gutter-elt",
-              "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
-              + cm.display.lineNumInnerWidth + "px"));
-      if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
-        var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
-        if (found)
-          gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
-                                     dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
-      }
-    }
-  }
-
-  function updateLineWidgets(lineView, dims) {
-    if (lineView.alignable) lineView.alignable = null;
-    for (var node = lineView.node.firstChild, next; node; node = next) {
-      var next = node.nextSibling;
-      if (node.className == "CodeMirror-linewidget")
-        lineView.node.removeChild(node);
-    }
-    insertLineWidgets(lineView, dims);
-  }
-
-  // Build a line's DOM representation from scratch
-  function buildLineElement(cm, lineView, lineN, dims) {
-    var built = getLineContent(cm, lineView);
-    lineView.text = lineView.node = built.pre;
-    if (built.bgClass) lineView.bgClass = built.bgClass;
-    if (built.textClass) lineView.textClass = built.textClass;
-
-    updateLineClasses(lineView);
-    updateLineGutter(cm, lineView, lineN, dims);
-    insertLineWidgets(lineView, dims);
-    return lineView.node;
-  }
-
-  // A lineView may contain multiple logical lines (when merged by
-  // collapsed spans). The widgets for all of them need to be drawn.
-  function insertLineWidgets(lineView, dims) {
-    insertLineWidgetsFor(lineView.line, lineView, dims, true);
-    if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
-      insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
-  }
-
-  function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
-    if (!line.widgets) return;
-    var wrap = ensureLineWrapped(lineView);
-    for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
-      var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
-      if (!widget.handleMouseEvents) node.ignoreEvents = true;
-      positionLineWidget(widget, node, lineView, dims);
-      if (allowAbove && widget.above)
-        wrap.insertBefore(node, lineView.gutter || lineView.text);
-      else
-        wrap.appendChild(node);
-      signalLater(widget, "redraw");
-    }
-  }
-
-  function positionLineWidget(widget, node, lineView, dims) {
-    if (widget.noHScroll) {
-      (lineView.alignable || (lineView.alignable = [])).push(node);
-      var width = dims.wrapperWidth;
-      node.style.left = dims.fixedPos + "px";
-      if (!widget.coverGutter) {
-        width -= dims.gutterTotalWidth;
-        node.style.paddingLeft = dims.gutterTotalWidth + "px";
-      }
-      node.style.width = width + "px";
-    }
-    if (widget.coverGutter) {
-      node.style.zIndex = 5;
-      node.style.position = "relative";
-      if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
-    }
-  }
-
-  // POSITION OBJECT
-
-  // A Pos instance represents a position within the text.
-  var Pos = CodeMirror.Pos = function(line, ch) {
-    if (!(this instanceof Pos)) return new Pos(line, ch);
-    this.line = line; this.ch = ch;
-  };
-
-  // Compare two positions, return 0 if they are the same, a negative
-  // number when a is less, and a positive number otherwise.
-  var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
-
-  function copyPos(x) {return Pos(x.line, x.ch);}
-  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
-  function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
-
-  // SELECTION / CURSOR
-
-  // Selection objects are immutable. A new one is created every time
-  // the selection changes. A selection is one or more non-overlapping
-  // (and non-touching) ranges, sorted, and an integer that indicates
-  // which one is the primary selection (the one that's scrolled into
-  // view, that getCursor returns, etc).
-  function Selection(ranges, primIndex) {
-    this.ranges = ranges;
-    this.primIndex = primIndex;
-  }
-
-  Selection.prototype = {
-    primary: function() { return this.ranges[this.primIndex]; },
-    equals: function(other) {
-      if (other == this) return true;
-      if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
-      for (var i = 0; i < this.ranges.length; i++) {
-        var here = this.ranges[i], there = other.ranges[i];
-        if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
-      }
-      return true;
-    },
-    deepCopy: function() {
-      for (var out = [], i = 0; i < this.ranges.length; i++)
-        out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
-      return new Selection(out, this.primIndex);
-    },
-    somethingSelected: function() {
-      for (var i = 0; i < this.ranges.length; i++)
-        if (!this.ranges[i].empty()) return true;
-      return false;
-    },
-    contains: function(pos, end) {
-      if (!end) end = pos;
-      for (var i = 0; i < this.ranges.length; i++) {
-        var range = this.ranges[i];
-        if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
-          return i;
-      }
-      return -1;
-    }
-  };
-
-  function Range(anchor, head) {
-    this.anchor = anchor; this.head = head;
-  }
-
-  Range.prototype = {
-    from: function() { return minPos(this.anchor, this.head); },
-    to: function() { return maxPos(this.anchor, this.head); },
-    empty: function() {
-      return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
-    }
-  };
-
-  // Take an unsorted, potentially overlapping set of ranges, and
-  // build a selection out of it. 'Consumes' ranges array (modifying
-  // it).
-  function normalizeSelection(ranges, primIndex) {
-    var prim = ranges[primIndex];
-    ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
-    primIndex = indexOf(ranges, prim);
-    for (var i = 1; i < ranges.length; i++) {
-      var cur = ranges[i], prev = ranges[i - 1];
-      if (cmp(prev.to(), cur.from()) >= 0) {
-        var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
-        var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
-        if (i <= primIndex) --primIndex;
-        ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
-      }
-    }
-    return new Selection(ranges, primIndex);
-  }
-
-  function simpleSelection(anchor, head) {
-    return new Selection([new Range(anchor, head || anchor)], 0);
-  }
-
-  // Most of the external API clips given positions to make sure they
-  // actually exist within the document.
-  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
-  function clipPos(doc, pos) {
-    if (pos.line < doc.first) return Pos(doc.first, 0);
-    var last = doc.first + doc.size - 1;
-    if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
-    return clipToLen(pos, getLine(doc, pos.line).text.length);
-  }
-  function clipToLen(pos, linelen) {
-    var ch = pos.ch;
-    if (ch == null || ch > linelen) return Pos(pos.line, linelen);
-    else if (ch < 0) return Pos(pos.line, 0);
-    else return pos;
-  }
-  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
-  function clipPosArray(doc, array) {
-    for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
-    return out;
-  }
-
-  // SELECTION UPDATES
-
-  // The 'scroll' parameter given to many of these indicated whether
-  // the new cursor position should be scrolled into view after
-  // modifying the selection.
-
-  // If shift is held or the extend flag is set, extends a range to
-  // include a given position (and optionally a second position).
-  // Otherwise, simply returns the range between the given positions.
-  // Used for cursor motion and such.
-  function extendRange(doc, range, head, other) {
-    if (doc.cm && doc.cm.display.shift || doc.extend) {
-      var anchor = range.anchor;
-      if (other) {
-        var posBefore = cmp(head, anchor) < 0;
-        if (posBefore != (cmp(other, anchor) < 0)) {
-          anchor = head;
-          head = other;
-        } else if (posBefore != (cmp(head, other) < 0)) {
-          head = other;
-        }
-      }
-      return new Range(anchor, head);
-    } else {
-      return new Range(other || head, head);
-    }
-  }
-
-  // Extend the primary selection range, discard the rest.
-  function extendSelection(doc, head, other, options) {
-    setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
-  }
-
-  // Extend all selections (pos is an array of selections with length
-  // equal the number of selections)
-  function extendSelections(doc, heads, options) {
-    for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
-      out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
-    var newSel = normalizeSelection(out, doc.sel.primIndex);
-    setSelection(doc, newSel, options);
-  }
-
-  // Updates a single range in the selection.
-  function replaceOneSelection(doc, i, range, options) {
-    var ranges = doc.sel.ranges.slice(0);
-    ranges[i] = range;
-    setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
-  }
-
-  // Reset the selection to a single range.
-  function setSimpleSelection(doc, anchor, head, options) {
-    setSelection(doc, simpleSelection(anchor, head), options);
-  }
-
-  // Give beforeSelectionChange handlers a change to influence a
-  // selection update.
-  function filterSelectionChange(doc, sel) {
-    var obj = {
-      ranges: sel.ranges,
-      update: function(ranges) {
-        this.ranges = [];
-        for (var i = 0; i < ranges.length; i++)
-          this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
-                                     clipPos(doc, ranges[i].head));
-      }
-    };
-    signal(doc, "beforeSelectionChange", doc, obj);
-    if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
-    if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
-    else return sel;
-  }
-
-  function setSelectionReplaceHistory(doc, sel, options) {
-    var done = doc.history.done, last = lst(done);
-    if (last && last.ranges) {
-      done[done.length - 1] = sel;
-      setSelectionNoUndo(doc, sel, options);
-    } else {
-      setSelection(doc, sel, options);
-    }
-  }
-
-  // Set a new selection.
-  function setSelection(doc, sel, options) {
-    setSelectionNoUndo(doc, sel, options);
-    addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
-  }
-
-  function setSelectionNoUndo(doc, sel, options) {
-    if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
-      sel = filterSelectionChange(doc, sel);
-
-    var bias = options && options.bias ||
-      (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
-    setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
-
-    if (!(options && options.scroll === false) && doc.cm)
-      ensureCursorVisible(doc.cm);
-  }
-
-  function setSelectionInner(doc, sel) {
-    if (sel.equals(doc.sel)) return;
-
-    doc.sel = sel;
-
-    if (doc.cm) {
-      doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
-      signalCursorActivity(doc.cm);
-    }
-    signalLater(doc, "cursorActivity", doc);
-  }
-
-  // Verify that the selection does not partially select any atomic
-  // marked ranges.
-  function reCheckSelection(doc) {
-    setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
-  }
-
-  // Return a selection that does not partially select any atomic
-  // ranges.
-  function skipAtomicInSelection(doc, sel, bias, mayClear) {
-    var out;
-    for (var i = 0; i < sel.ranges.length; i++) {
-      var range = sel.ranges[i];
-      var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
-      var newHead = skipAtomic(doc, range.head, bias, mayClear);
-      if (out || newAnchor != range.anchor || newHead != range.head) {
-        if (!out) out = sel.ranges.slice(0, i);
-        out[i] = new Range(newAnchor, newHead);
-      }
-    }
-    return out ? normalizeSelection(out, sel.primIndex) : sel;
-  }
-
-  // Ensure a given position is not inside an atomic range.
-  function skipAtomic(doc, pos, bias, mayClear) {
-    var flipped = false, curPos = pos;
-    var dir = bias || 1;
-    doc.cantEdit = false;
-    search: for (;;) {
-      var line = getLine(doc, curPos.line);
-      if (line.markedSpans) {
-        for (var i = 0; i < line.markedSpans.length; ++i) {
-          var sp = line.markedSpans[i], m = sp.marker;
-          if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
-              (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
-            if (mayClear) {
-              signal(m, "beforeCursorEnter");
-              if (m.explicitlyCleared) {
-                if (!line.markedSpans) break;
-                else {--i; continue;}
-              }
-            }
-            if (!m.atomic) continue;
-            var newPos = m.find(dir < 0 ? -1 : 1);
-            if (cmp(newPos, curPos) == 0) {
-              newPos.ch += dir;
-              if (newPos.ch < 0) {
-                if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
-                else newPos = null;
-              } else if (newPos.ch > line.text.length) {
-                if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
-                else newPos = null;
-              }
-              if (!newPos) {
-                if (flipped) {
-                  // Driven in a corner -- no valid cursor position found at all
-                  // -- try again *with* clearing, if we didn't already
-                  if (!mayClear) return skipAtomic(doc, pos, bias, true);
-                  // Otherwise, turn off editing until further notice, and return the start of the doc
-                  doc.cantEdit = true;
-                  return Pos(doc.first, 0);
-                }
-                flipped = true; newPos = pos; dir = -dir;
-              }
-            }
-            curPos = newPos;
-            continue search;
-          }
-        }
-      }
-      return curPos;
-    }
-  }
-
-  // SELECTION DRAWING
-
-  // Redraw the selection and/or cursor
-  function drawSelection(cm) {
-    var display = cm.display, doc = cm.doc, result = {};
-    var curFragment = result.cursors = document.createDocumentFragment();
-    var selFragment = result.selection = document.createDocumentFragment();
-
-    for (var i = 0; i < doc.sel.ranges.length; i++) {
-      var range = doc.sel.ranges[i];
-      var collapsed = range.empty();
-      if (collapsed || cm.options.showCursorWhenSelecting)
-        drawSelectionCursor(cm, range, curFragment);
-      if (!collapsed)
-        drawSelectionRange(cm, range, selFragment);
-    }
-
-    // Move the hidden textarea near the cursor to prevent scrolling artifacts
-    if (cm.options.moveInputWithCursor) {
-      var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
-      var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
-      result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
-                                          headPos.top + lineOff.top - wrapOff.top));
-      result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
-                                           headPos.left + lineOff.left - wrapOff.left));
-    }
-
-    return result;
-  }
-
-  function showSelection(cm, drawn) {
-    removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
-    removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
-    if (drawn.teTop != null) {
-      cm.display.inputDiv.style.top = drawn.teTop + "px";
-      cm.display.inputDiv.style.left = drawn.teLeft + "px";
-    }
-  }
-
-  function updateSelection(cm) {
-    showSelection(cm, drawSelection(cm));
-  }
-
-  // Draws a cursor for the given range
-  function drawSelectionCursor(cm, range, output) {
-    var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
-
-    var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
-    cursor.style.left = pos.left + "px";
-    cursor.style.top = pos.top + "px";
-    cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
-
-    if (pos.other) {
-      // Secondary cursor, shown when on a 'jump' in bi-directional text
-      var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
-      otherCursor.style.display = "";
-      otherCursor.style.left = pos.other.left + "px";
-      otherCursor.style.top = pos.other.top + "px";
-      otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
-    }
-  }
-
-  // Draws the given range as a highlighted selection
-  function drawSelectionRange(cm, range, output) {
-    var display = cm.display, doc = cm.doc;
-    var fragment = document.createDocumentFragment();
-    var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
-
-    function add(left, top, width, bottom) {
-      if (top < 0) top = 0;
-      top = Math.round(top);
-      bottom = Math.round(bottom);
-      fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
-                               "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
-                               "px; height: " + (bottom - top) + "px"));
-    }
-
-    function drawForLine(line, fromArg, toArg) {
-      var lineObj = getLine(doc, line);
-      var lineLen = lineObj.text.length;
-      var start, end;
-      function coords(ch, bias) {
-        return charCoords(cm, Pos(line, ch), "div", lineObj, bias);
-      }
-
-      iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
-        var leftPos = coords(from, "left"), rightPos, left, right;
-        if (from == to) {
-          rightPos = leftPos;
-          left = right = leftPos.left;
-        } else {
-          rightPos = coords(to - 1, "right");
-          if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; }
-          left = leftPos.left;
-          right = rightPos.right;
-        }
-        if (fromArg == null && from == 0) left = leftSide;
-        if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
-          add(left, leftPos.top, null, leftPos.bottom);
-          left = leftSide;
-          if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
-        }
-        if (toArg == null && to == lineLen) right = rightSide;
-        if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
-          start = leftPos;
-        if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
-          end = rightPos;
-        if (left < leftSide + 1) left = leftSide;
-        add(left, rightPos.top, right - left, rightPos.bottom);
-      });
-      return {start: start, end: end};
-    }
-
-    var sFrom = range.from(), sTo = range.to();
-    if (sFrom.line == sTo.line) {
-      drawForLine(sFrom.line, sFrom.ch, sTo.ch);
-    } else {
-      var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
-      var singleVLine = visualLine(fromLine) == visualLine(toLine);
-      var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
-      var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
-      if (singleVLine) {
-        if (leftEnd.top < rightStart.top - 2) {
-          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
-          add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
-        } else {
-          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
-        }
-      }
-      if (leftEnd.bottom < rightStart.top)
-        add(leftSide, leftEnd.bottom, null, rightStart.top);
-    }
-
-    output.appendChild(fragment);
-  }
-
-  // Cursor-blinking
-  function restartBlink(cm) {
-    if (!cm.state.focused) return;
-    var display = cm.display;
-    clearInterval(display.blinker);
-    var on = true;
-    display.cursorDiv.style.visibility = "";
-    if (cm.options.cursorBlinkRate > 0)
-      display.blinker = setInterval(function() {
-        display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
-      }, cm.options.cursorBlinkRate);
-    else if (cm.options.cursorBlinkRate < 0)
-      display.cursorDiv.style.visibility = "hidden";
-  }
-
-  // HIGHLIGHT WORKER
-
-  function startWorker(cm, time) {
-    if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
-      cm.state.highlight.set(time, bind(highlightWorker, cm));
-  }
-
-  function highlightWorker(cm) {
-    var doc = cm.doc;
-    if (doc.frontier < doc.first) doc.frontier = doc.first;
-    if (doc.frontier >= cm.display.viewTo) return;
-    var end = +new Date + cm.options.workTime;
-    var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
-    var changedLines = [];
-
-    doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
-      if (doc.frontier >= cm.display.viewFrom) { // Visible
-        var oldStyles = line.styles;
-        var highlighted = highlightLine(cm, line, state, true);
-        line.styles = highlighted.styles;
-        var oldCls = line.styleClasses, newCls = highlighted.classes;
-        if (newCls) line.styleClasses = newCls;
-        else if (oldCls) line.styleClasses = null;
-        var ischange = !oldStyles || oldStyles.length != line.styles.length ||
-          oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
-        for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
-        if (ischange) changedLines.push(doc.frontier);
-        line.stateAfter = copyState(doc.mode, state);
-      } else {
-        processLine(cm, line.text, state);
-        line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
-      }
-      ++doc.frontier;
-      if (+new Date > end) {
-        startWorker(cm, cm.options.workDelay);
-        return true;
-      }
-    });
-    if (changedLines.length) runInOp(cm, function() {
-      for (var i = 0; i < changedLines.length; i++)
-        regLineChange(cm, changedLines[i], "text");
-    });
-  }
-
-  // Finds the line to start with when starting a parse. Tries to
-  // find a line with a stateAfter, so that it can start with a
-  // valid state. If that fails, it returns the line with the
-  // smallest indentation, which tends to need the least context to
-  // parse correctly.
-  function findStartLine(cm, n, precise) {
-    var minindent, minline, doc = cm.doc;
-    var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);
-    for (var search = n; search > lim; --search) {
-      if (search <= doc.first) return doc.first;
-      var line = getLine(doc, search - 1);
-      if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
-      var indented = countColumn(line.text, null, cm.options.tabSize);
-      if (minline == null || minindent > indented) {
-        minline = search - 1;
-        minindent = indented;
-      }
-    }
-    return minline;
-  }
-
-  function getStateBefore(cm, n, precise) {
-    var doc = cm.doc, display = cm.display;
-    if (!doc.mode.startState) return true;
-    var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
-    if (!state) state = startState(doc.mode);
-    else state = copyState(doc.mode, state);
-    doc.iter(pos, n, function(line) {
-      processLine(cm, line.text, state);
-      var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
-      line.stateAfter = save ? copyState(doc.mode, state) : null;
-      ++pos;
-    });
-    if (precise) doc.frontier = pos;
-    return state;
-  }
-
-  // POSITION MEASUREMENT
-
-  function paddingTop(display) {return display.lineSpace.offsetTop;}
-  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
-  function paddingH(display) {
-    if (display.cachedPaddingH) return display.cachedPaddingH;
-    var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
-    var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
-    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};
-    if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data;
-    return data;
-  }
-
-  // Ensure the lineView.wrapping.heights array is populated. This is
-  // an array of bottom offsets for the lines that make up a drawn
-  // line. When lineWrapping is on, there might be more than one
-  // height.
-  function ensureLineHeights(cm, lineView, rect) {
-    var wrapping = cm.options.lineWrapping;
-    var curWidth = wrapping && cm.display.scroller.clientWidth;
-    if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
-      var heights = lineView.measure.heights = [];
-      if (wrapping) {
-        lineView.measure.width = curWidth;
-        var rects = lineView.text.firstChild.getClientRects();
-        for (var i = 0; i < rects.length - 1; i++) {
-          var cur = rects[i], next = rects[i + 1];
-          if (Math.abs(cur.bottom - next.bottom) > 2)
-            heights.push((cur.bottom + next.top) / 2 - rect.top);
-        }
-      }
-      heights.push(rect.bottom - rect.top);
-    }
-  }
-
-  // Find a line map (mapping character offsets to text nodes) and a
-  // measurement cache for the given line number. (A line view might
-  // contain multiple lines when collapsed ranges are present.)
-  function mapFromLineView(lineView, line, lineN) {
-    if (lineView.line == line)
-      return {map: lineView.measure.map, cache: lineView.measure.cache};
-    for (var i = 0; i < lineView.rest.length; i++)
-      if (lineView.rest[i] == line)
-        return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
-    for (var i = 0; i < lineView.rest.length; i++)
-      if (lineNo(lineView.rest[i]) > lineN)
-        return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
-  }
-
-  // Render a line into the hidden node display.externalMeasured. Used
-  // when measurement is needed for a line that's not in the viewport.
-  function updateExternalMeasurement(cm, line) {
-    line = visualLine(line);
-    var lineN = lineNo(line);
-    var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
-    view.lineN = lineN;
-    var built = view.built = buildLineContent(cm, view);
-    view.text = built.pre;
-    removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
-    return view;
-  }
-
-  // Get a {top, bottom, left, right} box (in line-local coordinates)
-  // for a given character.
-  function measureChar(cm, line, ch, bias) {
-    return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
-  }
-
-  // Find a line view that corresponds to the given line number.
-  function findViewForLine(cm, lineN) {
-    if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
-      return cm.display.view[findViewIndex(cm, lineN)];
-    var ext = cm.display.externalMeasured;
-    if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
-      return ext;
-  }
-
-  // Measurement can be split in two steps, the set-up work that
-  // applies to the whole line, and the measurement of the actual
-  // character. Functions like coordsChar, that need to do a lot of
-  // measurements in a row, can thus ensure that the set-up work is
-  // only done once.
-  function prepareMeasureForLine(cm, line) {
-    var lineN = lineNo(line);
-    var view = findViewForLine(cm, lineN);
-    if (view && !view.text)
-      view = null;
-    else if (view && view.changes)
-      updateLineForChanges(cm, view, lineN, getDimensions(cm));
-    if (!view)
-      view = updateExternalMeasurement(cm, line);
-
-    var info = mapFromLineView(view, line, lineN);
-    return {
-      line: line, view: view, rect: null,
-      map: info.map, cache: info.cache, before: info.before,
-      hasHeights: false
-    };
-  }
-
-  // Given a prepared measurement object, measures the position of an
-  // actual character (or fetches it from the cache).
-  function measureCharPrepared(cm, prepared, ch, bias, varHeight) {
-    if (prepared.before) ch = -1;
-    var key = ch + (bias || ""), found;
-    if (prepared.cache.hasOwnProperty(key)) {
-      found = prepared.cache[key];
-    } else {
-      if (!prepared.rect)
-        prepared.rect = prepared.view.text.getBoundingClientRect();
-      if (!prepared.hasHeights) {
-        ensureLineHeights(cm, prepared.view, prepared.rect);
-        prepared.hasHeights = true;
-      }
-      found = measureCharInner(cm, prepared, ch, bias);
-      if (!found.bogus) prepared.cache[key] = found;
-    }
-    return {left: found.left, right: found.right,
-            top: varHeight ? found.rtop : found.top,
-            bottom: varHeight ? found.rbottom : found.bottom};
-  }
-
-  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
-
-  function measureCharInner(cm, prepared, ch, bias) {
-    var map = prepared.map;
-
-    var node, start, end, collapse;
-    // First, search the line map for the text node corresponding to,
-    // or closest to, the target character.
-    for (var i = 0; i < map.length; i += 3) {
-      var mStart = map[i], mEnd = map[i + 1];
-      if (ch < mStart) {
-        start = 0; end = 1;
-        collapse = "left";
-      } else if (ch < mEnd) {
-        start = ch - mStart;
-        end = start + 1;
-      } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
-        end = mEnd - mStart;
-        start = end - 1;
-        if (ch >= mEnd) collapse = "right";
-      }
-      if (start != null) {
-        node = map[i + 2];
-        if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
-          collapse = bias;
-        if (bias == "left" && start == 0)
-          while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
-            node = map[(i -= 3) + 2];
-            collapse = "left";
-          }
-        if (bias == "right" && start == mEnd - mStart)
-          while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
-            node = map[(i += 3) + 2];
-            collapse = "right";
-          }
-        break;
-      }
-    }
-
-    var rect;
-    if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
-      for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
-        while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
-        while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
-        if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
-          rect = node.parentNode.getBoundingClientRect();
-        } else if (ie && cm.options.lineWrapping) {
-          var rects = range(node, start, end).getClientRects();
-          if (rects.length)
-            rect = rects[bias == "right" ? rects.length - 1 : 0];
-          else
-            rect = nullRect;
-        } else {
-          rect = range(node, start, end).getBoundingClientRect() || nullRect;
-        }
-        if (rect.left || rect.right || start == 0) break;
-        end = start;
-        start = start - 1;
-        collapse = "right";
-      }
-      if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect);
-    } else { // If it is a widget, simply get the box for the whole widget.
-      if (start > 0) collapse = bias = "right";
-      var rects;
-      if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
-        rect = rects[bias == "right" ? rects.length - 1 : 0];
-      else
-        rect = node.getBoundingClientRect();
-    }
-    if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {
-      var rSpan = node.parentNode.getClientRects()[0];
-      if (rSpan)
-        rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
-      else
-        rect = nullRect;
-    }
-
-    var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;
-    var mid = (rtop + rbot) / 2;
-    var heights = prepared.view.measure.heights;
-    for (var i = 0; i < heights.length - 1; i++)
-      if (mid < heights[i]) break;
-    var top = i ? heights[i - 1] : 0, bot = heights[i];
-    var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
-                  right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
-                  top: top, bottom: bot};
-    if (!rect.left && !rect.right) result.bogus = true;
-    if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
-
-    return result;
-  }
-
-  // Work around problem with bounding client rects on ranges being
-  // returned incorrectly when zoomed on IE10 and below.
-  function maybeUpdateRectForZooming(measure, rect) {
-    if (!window.screen || screen.logicalXDPI == null ||
-        screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
-      return rect;
-    var scaleX = screen.logicalXDPI / screen.deviceXDPI;
-    var scaleY = screen.logicalYDPI / screen.deviceYDPI;
-    return {left: rect.left * scaleX, right: rect.right * scaleX,
-            top: rect.top * scaleY, bottom: rect.bottom * scaleY};
-  }
-
-  function clearLineMeasurementCacheFor(lineView) {
-    if (lineView.measure) {
-      lineView.measure.cache = {};
-      lineView.measure.heights = null;
-      if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
-        lineView.measure.caches[i] = {};
-    }
-  }
-
-  function clearLineMeasurementCache(cm) {
-    cm.display.externalMeasure = null;
-    removeChildren(cm.display.lineMeasure);
-    for (var i = 0; i < cm.display.view.length; i++)
-      clearLineMeasurementCacheFor(cm.display.view[i]);
-  }
-
-  function clearCaches(cm) {
-    clearLineMeasurementCache(cm);
-    cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
-    if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
-    cm.display.lineNumChars = null;
-  }
-
-  function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
-  function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
-
-  // Converts a {top, bottom, left, right} box from line-local
-  // coordinates into another coordinate system. Context may be one of
-  // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
-  function intoCoordSystem(cm, lineObj, rect, context) {
-    if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
-      var size = widgetHeight(lineObj.widgets[i]);
-      rect.top += size; rect.bottom += size;
-    }
-    if (context == "line") return rect;
-    if (!context) context = "local";
-    var yOff = heightAtLine(lineObj);
-    if (context == "local") yOff += paddingTop(cm.display);
-    else yOff -= cm.display.viewOffset;
-    if (context == "page" || context == "window") {
-      var lOff = cm.display.lineSpace.getBoundingClientRect();
-      yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
-      var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
-      rect.left += xOff; rect.right += xOff;
-    }
-    rect.top += yOff; rect.bottom += yOff;
-    return rect;
-  }
-
-  // Coverts a box from "div" coords to another coordinate system.
-  // Context may be "window", "page", "div", or "local"/null.
-  function fromCoordSystem(cm, coords, context) {
-    if (context == "div") return coords;
-    var left = coords.left, top = coords.top;
-    // First move into "page" coordinate system
-    if (context == "page") {
-      left -= pageScrollX();
-      top -= pageScrollY();
-    } else if (context == "local" || !context) {
-      var localBox = cm.display.sizer.getBoundingClientRect();
-      left += localBox.left;
-      top += localBox.top;
-    }
-
-    var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
-    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
-  }
-
-  function charCoords(cm, pos, context, lineObj, bias) {
-    if (!lineObj) lineObj = getLine(cm.doc, pos.line);
-    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
-  }
-
-  // Returns a box for a given cursor position, which may have an
-  // 'other' property containing the position of the secondary cursor
-  // on a bidi boundary.
-  function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {
-    lineObj = lineObj || getLine(cm.doc, pos.line);
-    if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
-    function get(ch, right) {
-      var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight);
-      if (right) m.left = m.right; else m.right = m.left;
-      return intoCoordSystem(cm, lineObj, m, context);
-    }
-    function getBidi(ch, partPos) {
-      var part = order[partPos], right = part.level % 2;
-      if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
-        part = order[--partPos];
-        ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
-        right = true;
-      } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
-        part = order[++partPos];
-        ch = bidiLeft(part) - part.level % 2;
-        right = false;
-      }
-      if (right && ch == part.to && ch > part.from) return get(ch - 1);
-      return get(ch, right);
-    }
-    var order = getOrder(lineObj), ch = pos.ch;
-    if (!order) return get(ch);
-    var partPos = getBidiPartAt(order, ch);
-    var val = getBidi(ch, partPos);
-    if (bidiOther != null) val.other = getBidi(ch, bidiOther);
-    return val;
-  }
-
-  // Used to cheaply estimate the coordinates for a position. Used for
-  // intermediate scroll updates.
-  function estimateCoords(cm, pos) {
-    var left = 0, pos = clipPos(cm.doc, pos);
-    if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
-    var lineObj = getLine(cm.doc, pos.line);
-    var top = heightAtLine(lineObj) + paddingTop(cm.display);
-    return {left: left, right: left, top: top, bottom: top + lineObj.height};
-  }
-
-  // Positions returned by coordsChar contain some extra information.
-  // xRel is the relative x position of the input coordinates compared
-  // to the found position (so xRel > 0 means the coordinates are to
-  // the right of the character position, for example). When outside
-  // is true, that means the coordinates lie outside the line's
-  // vertical range.
-  function PosWithInfo(line, ch, outside, xRel) {
-    var pos = Pos(line, ch);
-    pos.xRel = xRel;
-    if (outside) pos.outside = true;
-    return pos;
-  }
-
-  // Compute the character position closest to the given coordinates.
-  // Input must be lineSpace-local ("div" coordinate system).
-  function coordsChar(cm, x, y) {
-    var doc = cm.doc;
-    y += cm.display.viewOffset;
-    if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
-    var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
-    if (lineN > last)
-      return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
-    if (x < 0) x = 0;
-
-    var lineObj = getLine(doc, lineN);
-    for (;;) {
-      var found = coordsCharInner(cm, lineObj, lineN, x, y);
-      var merged = collapsedSpanAtEnd(lineObj);
-      var mergedPos = merged && merged.find(0, true);
-      if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
-        lineN = lineNo(lineObj = mergedPos.to.line);
-      else
-        return found;
-    }
-  }
-
-  function coordsCharInner(cm, lineObj, lineNo, x, y) {
-    var innerOff = y - heightAtLine(lineObj);
-    var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
-    var preparedMeasure = prepareMeasureForLine(cm, lineObj);
-
-    function getX(ch) {
-      var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
-      wrongLine = true;
-      if (innerOff > sp.bottom) return sp.left - adjust;
-      else if (innerOff < sp.top) return sp.left + adjust;
-      else wrongLine = false;
-      return sp.left;
-    }
-
-    var bidi = getOrder(lineObj), dist = lineObj.text.length;
-    var from = lineLeft(lineObj), to = lineRight(lineObj);
-    var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
-
-    if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
-    // Do a binary search between these bounds.
-    for (;;) {
-      if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
-        var ch = x < fromX || x - fromX <= toX - x ? from : to;
-        var xDiff = x - (ch == from ? fromX : toX);
-        while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
-        var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
-                              xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
-        return pos;
-      }
-      var step = Math.ceil(dist / 2), middle = from + step;
-      if (bidi) {
-        middle = from;
-        for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
-      }
-      var middleX = getX(middle);
-      if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
-      else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
-    }
-  }
-
-  var measureText;
-  // Compute the default text height.
-  function textHeight(display) {
-    if (display.cachedTextHeight != null) return display.cachedTextHeight;
-    if (measureText == null) {
-      measureText = elt("pre");
-      // Measure a bunch of lines, for browsers that compute
-      // fractional heights.
-      for (var i = 0; i < 49; ++i) {
-        measureText.appendChild(document.createTextNode("x"));
-        measureText.appendChild(elt("br"));
-      }
-      measureText.appendChild(document.createTextNode("x"));
-    }
-    removeChildrenAndAdd(display.measure, measureText);
-    var height = measureText.offsetHeight / 50;
-    if (height > 3) display.cachedTextHeight = height;
-    removeChildren(display.measure);
-    return height || 1;
-  }
-
-  // Compute the default character width.
-  function charWidth(display) {
-    if (display.cachedCharWidth != null) return display.cachedCharWidth;
-    var anchor = elt("span", "xxxxxxxxxx");
-    var pre = elt("pre", [anchor]);
-    removeChildrenAndAdd(display.measure, pre);
-    var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
-    if (width > 2) display.cachedCharWidth = width;
-    return width || 10;
-  }
-
-  // OPERATIONS
-
-  // Operations are used to wrap a series of changes to the editor
-  // state in such a way that each change won't have to update the
-  // cursor and display (which would be awkward, slow, and
-  // error-prone). Instead, display updates are batched and then all
-  // combined and executed at once.
-
-  var operationGroup = null;
-
-  var nextOpId = 0;
-  // Start a new operation.
-  function startOperation(cm) {
-    cm.curOp = {
-      cm: cm,
-      viewChanged: false,      // Flag that indicates that lines might need to be redrawn
-      startHeight: cm.doc.height, // Used to detect need to update scrollbar
-      forceUpdate: false,      // Used to force a redraw
-      updateInput: null,       // Whether to reset the input textarea
-      typing: false,           // Whether this reset should be careful to leave existing text (for compositing)
-      changeObjs: null,        // Accumulated changes, for firing change events
-      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on
-      cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already
-      selectionChanged: false, // Whether the selection needs to be redrawn
-      updateMaxLine: false,    // Set when the widest line needs to be determined anew
-      scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
-      scrollToPos: null,       // Used to scroll to a specific position
-      id: ++nextOpId           // Unique ID
-    };
-    if (operationGroup) {
-      operationGroup.ops.push(cm.curOp);
-    } else {
-      cm.curOp.ownsGroup = operationGroup = {
-        ops: [cm.curOp],
-        delayedCallbacks: []
-      };
-    }
-  }
-
-  function fireCallbacksForOps(group) {
-    // Calls delayed callbacks and cursorActivity handlers until no
-    // new ones appear
-    var callbacks = group.delayedCallbacks, i = 0;
-    do {
-      for (; i < callbacks.length; i++)
-        callbacks[i]();
-      for (var j = 0; j < group.ops.length; j++) {
-        var op = group.ops[j];
-        if (op.cursorActivityHandlers)
-          while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
-            op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm);
-      }
-    } while (i < callbacks.length);
-  }
-
-  // Finish an operation, updating the display and signalling delayed events
-  function endOperation(cm) {
-    var op = cm.curOp, group = op.ownsGroup;
-    if (!group) return;
-
-    try { fireCallbacksForOps(group); }
-    finally {
-      operationGroup = null;
-      for (var i = 0; i < group.ops.length; i++)
-        group.ops[i].cm.curOp = null;
-      endOperations(group);
-    }
-  }
-
-  // The DOM updates done when an operation finishes are batched so
-  // that the minimum number of relayouts are required.
-  function endOperations(group) {
-    var ops = group.ops;
-    for (var i = 0; i < ops.length; i++) // Read DOM
-      endOperation_R1(ops[i]);
-    for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
-      endOperation_W1(ops[i]);
-    for (var i = 0; i < ops.length; i++) // Read DOM
-      endOperation_R2(ops[i]);
-    for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
-      endOperation_W2(ops[i]);
-    for (var i = 0; i < ops.length; i++) // Read DOM
-      endOperation_finish(ops[i]);
-  }
-
-  function endOperation_R1(op) {
-    var cm = op.cm, display = cm.display;
-    if (op.updateMaxLine) findMaxLine(cm);
-
-    op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||
-      op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
-                         op.scrollToPos.to.line >= display.viewTo) ||
-      display.maxLineChanged && cm.options.lineWrapping;
-    op.update = op.mustUpdate &&
-      new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
-  }
-
-  function endOperation_W1(op) {
-    op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
-  }
-
-  function endOperation_R2(op) {
-    var cm = op.cm, display = cm.display;
-    if (op.updatedDisplay) updateHeightsInViewport(cm);
-
-    op.barMeasure = measureForScrollbars(cm);
-
-    // If the max line changed since it was last measured, measure it,
-    // and ensure the document's width matches it.
-    // updateDisplay_W2 will use these properties to do the actual resizing
-    if (display.maxLineChanged && !cm.options.lineWrapping) {
-      op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;
-      op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo +
-                                  scrollerCutOff - display.scroller.clientWidth);
-    }
-
-    if (op.updatedDisplay || op.selectionChanged)
-      op.newSelectionNodes = drawSelection(cm);
-  }
-
-  function endOperation_W2(op) {
-    var cm = op.cm;
-
-    if (op.adjustWidthTo != null) {
-      cm.display.sizer.style.minWidth = op.adjustWidthTo + "px";
-      if (op.maxScrollLeft < cm.doc.scrollLeft)
-        setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true);
-      cm.display.maxLineChanged = false;
-    }
-
-    if (op.newSelectionNodes)
-      showSelection(cm, op.newSelectionNodes);
-    if (op.updatedDisplay)
-      setDocumentHeight(cm, op.barMeasure);
-    if (op.updatedDisplay || op.startHeight != cm.doc.height)
-      updateScrollbars(cm, op.barMeasure);
-
-    if (op.selectionChanged) restartBlink(cm);
-
-    if (cm.state.focused && op.updateInput)
-      resetInput(cm, op.typing);
-  }
-
-  function endOperation_finish(op) {
-    var cm = op.cm, display = cm.display, doc = cm.doc;
-
-    if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1)
-      updateScrollbars(cm);
-
-    if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
-
-    // Abort mouse wheel delta measurement, when scrolling explicitly
-    if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))
-      display.wheelStartX = display.wheelStartY = null;
-
-    // Propagate the scroll position to the actual DOM scroller
-    if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
-      var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
-      display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
-    }
-    if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
-      var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
-      display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
-      alignHorizontally(cm);
-    }
-    // If we need to scroll a specific position into view, do so.
-    if (op.scrollToPos) {
-      var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
-                                     clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
-      if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
-    }
-
-    // Fire events for markers that are hidden/unidden by editing or
-    // undoing
-    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
-    if (hidden) for (var i = 0; i < hidden.length; ++i)
-      if (!hidden[i].lines.length) signal(hidden[i], "hide");
-    if (unhidden) for (var i = 0; i < unhidden.length; ++i)
-      if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
-
-    if (display.wrapper.offsetHeight)
-      doc.scrollTop = cm.display.scroller.scrollTop;
-
-    // Apply workaround for two webkit bugs
-    if (op.updatedDisplay && webkit) {
-      if (cm.options.lineWrapping)
-        checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420)
-      if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth &&
-          op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 &&
-          !hScrollbarTakesSpace(cm))
-        updateScrollbars(cm); // (Issue #2562)
-    }
-
-    // Fire change events, and delayed event handlers
-    if (op.changeObjs)
-      signal(cm, "changes", cm, op.changeObjs);
-  }
-
-  // Run the given function in an operation
-  function runInOp(cm, f) {
-    if (cm.curOp) return f();
-    startOperation(cm);
-    try { return f(); }
-    finally { endOperation(cm); }
-  }
-  // Wraps a function in an operation. Returns the wrapped function.
-  function operation(cm, f) {
-    return function() {
-      if (cm.curOp) return f.apply(cm, arguments);
-      startOperation(cm);
-      try { return f.apply(cm, arguments); }
-      finally { endOperation(cm); }
-    };
-  }
-  // Used to add methods to editor and doc instances, wrapping them in
-  // operations.
-  function methodOp(f) {
-    return function() {
-      if (this.curOp) return f.apply(this, arguments);
-      startOperation(this);
-      try { return f.apply(this, arguments); }
-      finally { endOperation(this); }
-    };
-  }
-  function docMethodOp(f) {
-    return function() {
-      var cm = this.cm;
-      if (!cm || cm.curOp) return f.apply(this, arguments);
-      startOperation(cm);
-      try { return f.apply(this, arguments); }
-      finally { endOperation(cm); }
-    };
-  }
-
-  // VIEW TRACKING
-
-  // These objects are used to represent the visible (currently drawn)
-  // part of the document. A LineView may correspond to multiple
-  // logical lines, if those are connected by collapsed ranges.
-  function LineView(doc, line, lineN) {
-    // The starting line
-    this.line = line;
-    // Continuing lines, if any
-    this.rest = visualLineContinued(line);
-    // Number of logical lines in this visual line
-    this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
-    this.node = this.text = null;
-    this.hidden = lineIsHidden(doc, line);
-  }
-
-  // Create a range of LineView objects for the given lines.
-  function buildViewArray(cm, from, to) {
-    var array = [], nextPos;
-    for (var pos = from; pos < to; pos = nextPos) {
-      var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
-      nextPos = pos + view.size;
-      array.push(view);
-    }
-    return array;
-  }
-
-  // Updates the display.view data structure for a given change to the
-  // document. From and to are in pre-change coordinates. Lendiff is
-  // the amount of lines added or subtracted by the change. This is
-  // used for changes that span multiple lines, or change the way
-  // lines are divided into visual lines. regLineChange (below)
-  // registers single-line changes.
-  function regChange(cm, from, to, lendiff) {
-    if (from == null) from = cm.doc.first;
-    if (to == null) to = cm.doc.first + cm.doc.size;
-    if (!lendiff) lendiff = 0;
-
-    var display = cm.display;
-    if (lendiff && to < display.viewTo &&
-        (display.updateLineNumbers == null || display.updateLineNumbers > from))
-      display.updateLineNumbers = from;
-
-    cm.curOp.viewChanged = true;
-
-    if (from >= display.viewTo) { // Change after
-      if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
-        resetView(cm);
-    } else if (to <= display.viewFrom) { // Change before
-      if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
-        resetView(cm);
-      } else {
-        display.viewFrom += lendiff;
-        display.viewTo += lendiff;
-      }
-    } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
-      resetView(cm);
-    } else if (from <= display.viewFrom) { // Top overlap
-      var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
-      if (cut) {
-        display.view = display.view.slice(cut.index);
-        display.viewFrom = cut.lineN;
-        display.viewTo += lendiff;
-      } else {
-        resetView(cm);
-      }
-    } else if (to >= display.viewTo) { // Bottom overlap
-      var cut = viewCuttingPoint(cm, from, from, -1);
-      if (cut) {
-        display.view = display.view.slice(0, cut.index);
-        display.viewTo = cut.lineN;
-      } else {
-        resetView(cm);
-      }
-    } else { // Gap in the middle
-      var cutTop = viewCuttingPoint(cm, from, from, -1);
-      var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
-      if (cutTop && cutBot) {
-        display.view = display.view.slice(0, cutTop.index)
-          .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
-          .concat(display.view.slice(cutBot.index));
-        display.viewTo += lendiff;
-      } else {
-        resetView(cm);
-      }
-    }
-
-    var ext = display.externalMeasured;
-    if (ext) {
-      if (to < ext.lineN)
-        ext.lineN += lendiff;
-      else if (from < ext.lineN + ext.size)
-        display.externalMeasured = null;
-    }
-  }
-
-  // Register a change to a single line. Type must be one of "text",
-  // "gutter", "class", "widget"
-  function regLineChange(cm, line, type) {
-    cm.curOp.viewChanged = true;
-    var display = cm.display, ext = cm.display.externalMeasured;
-    if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
-      display.externalMeasured = null;
-
-    if (line < display.viewFrom || line >= display.viewTo) return;
-    var lineView = display.view[findViewIndex(cm, line)];
-    if (lineView.node == null) return;
-    var arr = lineView.changes || (lineView.changes = []);
-    if (indexOf(arr, type) == -1) arr.push(type);
-  }
-
-  // Clear the view.
-  function resetView(cm) {
-    cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
-    cm.display.view = [];
-    cm.display.viewOffset = 0;
-  }
-
-  // Find the view element corresponding to a given line. Return null
-  // when the line isn't visible.
-  function findViewIndex(cm, n) {
-    if (n >= cm.display.viewTo) return null;
-    n -= cm.display.viewFrom;
-    if (n < 0) return null;
-    var view = cm.display.view;
-    for (var i = 0; i < view.length; i++) {
-      n -= view[i].size;
-      if (n < 0) return i;
-    }
-  }
-
-  function viewCuttingPoint(cm, oldN, newN, dir) {
-    var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
-    if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)
-      return {index: index, lineN: newN};
-    for (var i = 0, n = cm.display.viewFrom; i < index; i++)
-      n += view[i].size;
-    if (n != oldN) {
-      if (dir > 0) {
-        if (index == view.length - 1) return null;
-        diff = (n + view[index].size) - oldN;
-        index++;
-      } else {
-        diff = n - oldN;
-      }
-      oldN += diff; newN += diff;
-    }
-    while (visualLineNo(cm.doc, newN) != newN) {
-      if (index == (dir < 0 ? 0 : view.length - 1)) return null;
-      newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
-      index += dir;
-    }
-    return {index: index, lineN: newN};
-  }
-
-  // Force the view to cover a given range, adding empty view element
-  // or clipping off existing ones as needed.
-  function adjustView(cm, from, to) {
-    var display = cm.display, view = display.view;
-    if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
-      display.view = buildViewArray(cm, from, to);
-      display.viewFrom = from;
-    } else {
-      if (display.viewFrom > from)
-        display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
-      else if (display.viewFrom < from)
-        display.view = display.view.slice(findViewIndex(cm, from));
-      display.viewFrom = from;
-      if (display.viewTo < to)
-        display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
-      else if (display.viewTo > to)
-        display.view = display.view.slice(0, findViewIndex(cm, to));
-    }
-    display.viewTo = to;
-  }
-
-  // Count the number of lines in the view whose DOM representation is
-  // out of date (or nonexistent).
-  function countDirtyView(cm) {
-    var view = cm.display.view, dirty = 0;
-    for (var i = 0; i < view.length; i++) {
-      var lineView = view[i];
-      if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
-    }
-    return dirty;
-  }
-
-  // INPUT HANDLING
-
-  // Poll for input changes, using the normal rate of polling. This
-  // runs as long as the editor is focused.
-  function slowPoll(cm) {
-    if (cm.display.pollingFast) return;
-    cm.display.poll.set(cm.options.pollInterval, function() {
-      readInput(cm);
-      if (cm.state.focused) slowPoll(cm);
-    });
-  }
-
-  // When an event has just come in that is likely to add or change
-  // something in the input textarea, we poll faster, to ensure that
-  // the change appears on the screen quickly.
-  function fastPoll(cm) {
-    var missed = false;
-    cm.display.pollingFast = true;
-    function p() {
-      var changed = readInput(cm);
-      if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
-      else {cm.display.pollingFast = false; slowPoll(cm);}
-    }
-    cm.display.poll.set(20, p);
-  }
-
-  // This will be set to an array of strings when copying, so that,
-  // when pasting, we know what kind of selections the copied text
-  // was made out of.
-  var lastCopied = null;
-
-  // Read input from the textarea, and update the document to match.
-  // When something is selected, it is present in the textarea, and
-  // selected (unless it is huge, in which case a placeholder is
-  // used). When nothing is selected, the cursor sits after previously
-  // seen text (can be empty), which is stored in prevInput (we must
-  // not reset the textarea when typing, because that breaks IME).
-  function readInput(cm) {
-    var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
-    // Since this is called a *lot*, try to bail out as cheaply as
-    // possible when it is clear that nothing happened. hasSelection
-    // will be the case when there is a lot of text in the textarea,
-    // in which case reading its value would be expensive.
-    if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput)
-      return false;
-    // See paste handler for more on the fakedLastChar kludge
-    if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
-      input.value = input.value.substring(0, input.value.length - 1);
-      cm.state.fakedLastChar = false;
-    }
-    var text = input.value;
-    // If nothing changed, bail.
-    if (text == prevInput && !cm.somethingSelected()) return false;
-    // Work around nonsensical selection resetting in IE9/10, and
-    // inexplicable appearance of private area unicode characters on
-    // some key combos in Mac (#2689).
-    if (ie && ie_version >= 9 && cm.display.inputHasSelection === text ||
-        mac && /[\uf700-\uf7ff]/.test(text)) {
-      resetInput(cm);
-      return false;
-    }
-
-    var withOp = !cm.curOp;
-    if (withOp) startOperation(cm);
-    cm.display.shift = false;
-
-    if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput)
-      prevInput = "\u200b";
-    // Find the part of the input that is actually new
-    var same = 0, l = Math.min(prevInput.length, text.length);
-    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
-    var inserted = text.slice(same), textLines = splitLines(inserted);
-
-    // When pasing N lines into N selections, insert one line per selection
-    var multiPaste = null;
-    if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
-      if (lastCopied && lastCopied.join("\n") == inserted)
-        multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
-      else if (textLines.length == doc.sel.ranges.length)
-        multiPaste = map(textLines, function(l) { return [l]; });
-    }
-
-    // Normal behavior is to insert the new text into every selection
-    for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
-      var range = doc.sel.ranges[i];
-      var from = range.from(), to = range.to();
-      // Handle deletion
-      if (same < prevInput.length)
-        from = Pos(from.line, from.ch - (prevInput.length - same));
-      // Handle overwrite
-      else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
-        to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
-      var updateInput = cm.curOp.updateInput;
-      var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
-                         origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
-      makeChange(cm.doc, changeEvent);
-      signalLater(cm, "inputRead", cm, changeEvent);
-      // When an 'electric' character is inserted, immediately trigger a reindent
-      if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
-          cm.options.smartIndent && range.head.ch < 100 &&
-          (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
-        var mode = cm.getModeAt(range.head);
-        var end = changeEnd(changeEvent);
-        if (mode.electricChars) {
-          for (var j = 0; j < mode.electricChars.length; j++)
-            if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
-              indentLine(cm, end.line, "smart");
-              break;
-            }
-        } else if (mode.electricInput) {
-          if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
-            indentLine(cm, end.line, "smart");
-        }
-      }
-    }
-    ensureCursorVisible(cm);
-    cm.curOp.updateInput = updateInput;
-    cm.curOp.typing = true;
-
-    // Don't leave long text in the textarea, since it makes further polling slow
-    if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
-    else cm.display.prevInput = text;
-    if (withOp) endOperation(cm);
-    cm.state.pasteIncoming = cm.state.cutIncoming = false;
-    return true;
-  }
-
-  // Reset the input to correspond to the selection (or to be empty,
-  // when not typing and nothing is selected)
-  function resetInput(cm, typing) {
-    var minimal, selected, doc = cm.doc;
-    if (cm.somethingSelected()) {
-      cm.display.prevInput = "";
-      var range = doc.sel.primary();
-      minimal = hasCopyEvent &&
-        (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
-      var content = minimal ? "-" : selected || cm.getSelection();
-      cm.display.input.value = content;
-      if (cm.state.focused) selectInput(cm.display.input);
-      if (ie && ie_version >= 9) cm.display.inputHasSelection = content;
-    } else if (!typing) {
-      cm.display.prevInput = cm.display.input.value = "";
-      if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
-    }
-    cm.display.inaccurateSelection = minimal;
-  }
-
-  function focusInput(cm) {
-    if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
-      cm.display.input.focus();
-  }
-
-  function ensureFocus(cm) {
-    if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
-  }
-
-  function isReadOnly(cm) {
-    return cm.options.readOnly || cm.doc.cantEdit;
-  }
-
-  // EVENT HANDLERS
-
-  // Attach the necessary event handlers when initializing the editor
-  function registerEventHandlers(cm) {
-    var d = cm.display;
-    on(d.scroller, "mousedown", operation(cm, onMouseDown));
-    // Older IE's will not fire a second mousedown for a double click
-    if (ie && ie_version < 11)
-      on(d.scroller, "dblclick", operation(cm, function(e) {
-        if (signalDOMEvent(cm, e)) return;
-        var pos = posFromMouse(cm, e);
-        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
-        e_preventDefault(e);
-        var word = cm.findWordAt(pos);
-        extendSelection(cm.doc, word.anchor, word.head);
-      }));
-    else
-      on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
-    // Prevent normal selection in the editor (we handle our own)
-    on(d.lineSpace, "selectstart", function(e) {
-      if (!eventInWidget(d, e)) e_preventDefault(e);
-    });
-    // Some browsers fire contextmenu *after* opening the menu, at
-    // which point we can't mess with it anymore. Context menu is
-    // handled in onMouseDown for these browsers.
-    if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
-
-    // Sync scrolling between fake scrollbars and real scrollable
-    // area, ensure viewport is updated when scrolling.
-    on(d.scroller, "scroll", function() {
-      if (d.scroller.clientHeight) {
-        setScrollTop(cm, d.scroller.scrollTop);
-        setScrollLeft(cm, d.scroller.scrollLeft, true);
-        signal(cm, "scroll", cm);
-      }
-    });
-    on(d.scrollbarV, "scroll", function() {
-      if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
-    });
-    on(d.scrollbarH, "scroll", function() {
-      if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
-    });
-
-    // Listen to wheel events in order to try and update the viewport on time.
-    on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
-    on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
-
-    // Prevent clicks in the scrollbars from killing focus
-    function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
-    on(d.scrollbarH, "mousedown", reFocus);
-    on(d.scrollbarV, "mousedown", reFocus);
-    // Prevent wrapper from ever scrolling
-    on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
-
-    on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
-    on(d.input, "input", function() {
-      if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
-      fastPoll(cm);
-    });
-    on(d.input, "keydown", operation(cm, onKeyDown));
-    on(d.input, "keypress", operation(cm, onKeyPress));
-    on(d.input, "focus", bind(onFocus, cm));
-    on(d.input, "blur", bind(onBlur, cm));
-
-    function drag_(e) {
-      if (!signalDOMEvent(cm, e)) e_stop(e);
-    }
-    if (cm.options.dragDrop) {
-      on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
-      on(d.scroller, "dragenter", drag_);
-      on(d.scroller, "dragover", drag_);
-      on(d.scroller, "drop", operation(cm, onDrop));
-    }
-    on(d.scroller, "paste", function(e) {
-      if (eventInWidget(d, e)) return;
-      cm.state.pasteIncoming = true;
-      focusInput(cm);
-      fastPoll(cm);
-    });
-    on(d.input, "paste", function() {
-      // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
-      // Add a char to the end of textarea before paste occur so that
-      // selection doesn't span to the end of textarea.
-      if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
-        var start = d.input.selectionStart, end = d.input.selectionEnd;
-        d.input.value += "$";
-        // The selection end needs to be set before the start, otherwise there
-        // can be an intermediate non-empty selection between the two, which
-        // can override the middle-click paste buffer on linux and cause the
-        // wrong thing to get pasted.
-        d.input.selectionEnd = end;
-        d.input.selectionStart = start;
-        cm.state.fakedLastChar = true;
-      }
-      cm.state.pasteIncoming = true;
-      fastPoll(cm);
-    });
-
-    function prepareCopyCut(e) {
-      if (cm.somethingSelected()) {
-        lastCopied = cm.getSelections();
-        if (d.inaccurateSelection) {
-          d.prevInput = "";
-          d.inaccurateSelection = false;
-          d.input.value = lastCopied.join("\n");
-          selectInput(d.input);
-        }
-      } else {
-        var text = [], ranges = [];
-        for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
-          var line = cm.doc.sel.ranges[i].head.line;
-          var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
-          ranges.push(lineRange);
-          text.push(cm.getRange(lineRange.anchor, lineRange.head));
-        }
-        if (e.type == "cut") {
-          cm.setSelections(ranges, null, sel_dontScroll);
-        } else {
-          d.prevInput = "";
-          d.input.value = text.join("\n");
-          selectInput(d.input);
-        }
-        lastCopied = text;
-      }
-      if (e.type == "cut") cm.state.cutIncoming = true;
-    }
-    on(d.input, "cut", prepareCopyCut);
-    on(d.input, "copy", prepareCopyCut);
-
-    // Needed to handle Tab key in KHTML
-    if (khtml) on(d.sizer, "mouseup", function() {
-      if (activeElt() == d.input) d.input.blur();
-      focusInput(cm);
-    });
-  }
-
-  // Called when the window resizes
-  function onResize(cm) {
-    // Might be a text scaling operation, clear size caches.
-    var d = cm.display;
-    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
-    cm.setSize();
-  }
-
-  // MOUSE EVENTS
-
-  // Return true when the given mouse event happened in a widget
-  function eventInWidget(display, e) {
-    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
-      if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
-    }
-  }
-
-  // Given a mouse event, find the corresponding position. If liberal
-  // is false, it checks whether a gutter or scrollbar was clicked,
-  // and returns null if it was. forRect is used by rectangular
-  // selections, and tries to estimate a character position even for
-  // coordinates beyond the right of the text.
-  function posFromMouse(cm, e, liberal, forRect) {
-    var display = cm.display;
-    if (!liberal) {
-      var target = e_target(e);
-      if (target == display.scrollbarH || target == display.scrollbarV ||
-          target == display.scrollbarFiller || target == display.gutterFiller) return null;
-    }
-    var x, y, space = display.lineSpace.getBoundingClientRect();
-    // Fails unpredictably on IE[67] when mouse is dragged around quickly.
-    try { x = e.clientX - space.left; y = e.clientY - space.top; }
-    catch (e) { return null; }
-    var coords = coordsChar(cm, x, y), line;
-    if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
-      var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
-      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));
-    }
-    return coords;
-  }
-
-  // A mouse down can be a single click, double click, triple click,
-  // start of selection drag, start of text drag, new cursor
-  // (ctrl-click), rectangle drag (alt-drag), or xwin
-  // middle-click-paste. Or it might be a click on something we should
-  // not interfere with, such as a scrollbar or widget.
-  function onMouseDown(e) {
-    if (signalDOMEvent(this, e)) return;
-    var cm = this, display = cm.display;
-    display.shift = e.shiftKey;
-
-    if (eventInWidget(display, e)) {
-      if (!webkit) {
-        // Briefly turn off draggability, to allow widgets to do
-        // normal dragging things.
-        display.scroller.draggable = false;
-        setTimeout(function(){display.scroller.draggable = true;}, 100);
-      }
-      return;
-    }
-    if (clickInGutter(cm, e)) return;
-    var start = posFromMouse(cm, e);
-    window.focus();
-
-    switch (e_button(e)) {
-    case 1:
-      if (start)
-        leftButtonDown(cm, e, start);
-      else if (e_target(e) == display.scroller)
-        e_preventDefault(e);
-      break;
-    case 2:
-      if (webkit) cm.state.lastMiddleDown = +new Date;
-      if (start) extendSelection(cm.doc, start);
-      setTimeout(bind(focusInput, cm), 20);
-      e_preventDefault(e);
-      break;
-    case 3:
-      if (captureRightClick) onContextMenu(cm, e);
-      break;
-    }
-  }
-
-  var lastClick, lastDoubleClick;
-  function leftButtonDown(cm, e, start) {
-    setTimeout(bind(ensureFocus, cm), 0);
-
-    var now = +new Date, type;
-    if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
-      type = "triple";
-    } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
-      type = "double";
-      lastDoubleClick = {time: now, pos: start};
-    } else {
-      type = "single";
-      lastClick = {time: now, pos: start};
-    }
-
-    var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey;
-    if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
-        type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
-      leftButtonStartDrag(cm, e, start, modifier);
-    else
-      leftButtonSelect(cm, e, start, type, modifier);
-  }
-
-  // Start a text drag. When it ends, see if any dragging actually
-  // happen, and treat as a click if it didn't.
-  function leftButtonStartDrag(cm, e, start, modifier) {
-    var display = cm.display;
-    var dragEnd = operation(cm, function(e2) {
-      if (webkit) display.scroller.draggable = false;
-      cm.state.draggingText = false;
-      off(document, "mouseup", dragEnd);
-      off(display.scroller, "drop", dragEnd);
-      if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
-        e_preventDefault(e2);
-        if (!modifier)
-          extendSelection(cm.doc, start);
-        focusInput(cm);
-        // Work around unexplainable focus problem in IE9 (#2127)
-        if (ie && ie_version == 9)
-          setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
-      }
-    });
-    // Let the drag handler handle this.
-    if (webkit) display.scroller.draggable = true;
-    cm.state.draggingText = dragEnd;
-    // IE's approach to draggable
-    if (display.scroller.dragDrop) display.scroller.dragDrop();
-    on(document, "mouseup", dragEnd);
-    on(display.scroller, "drop", dragEnd);
-  }
-
-  // Normal selection, as opposed to text dragging.
-  function leftButtonSelect(cm, e, start, type, addNew) {
-    var display = cm.display, doc = cm.doc;
-    e_preventDefault(e);
-
-    var ourRange, ourIndex, startSel = doc.sel;
-    if (addNew && !e.shiftKey) {
-      ourIndex = doc.sel.contains(start);
-      if (ourIndex > -1)
-        ourRange = doc.sel.ranges[ourIndex];
-      else
-        ourRange = new Range(start, start);
-    } else {
-      ourRange = doc.sel.primary();
-    }
-
-    if (e.altKey) {
-      type = "rect";
-      if (!addNew) ourRange = new Range(start, start);
-      start = posFromMouse(cm, e, true, true);
-      ourIndex = -1;
-    } else if (type == "double") {
-      var word = cm.findWordAt(start);
-      if (cm.display.shift || doc.extend)
-        ourRange = extendRange(doc, ourRange, word.anchor, word.head);
-      else
-        ourRange = word;
-    } else if (type == "triple") {
-      var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
-      if (cm.display.shift || doc.extend)
-        ourRange = extendRange(doc, ourRange, line.anchor, line.head);
-      else
-        ourRange = line;
-    } else {
-      ourRange = extendRange(doc, ourRange, start);
-    }
-
-    if (!addNew) {
-      ourIndex = 0;
-      setSelection(doc, new Selection([ourRange], 0), sel_mouse);
-      startSel = doc.sel;
-    } else if (ourIndex > -1) {
-      replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
-    } else {
-      ourIndex = doc.sel.ranges.length;
-      setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
-                   {scroll: false, origin: "*mouse"});
-    }
-
-    var lastPos = start;
-    function extendTo(pos) {
-      if (cmp(lastPos, pos) == 0) return;
-      lastPos = pos;
-
-      if (type == "rect") {
-        var ranges = [], tabSize = cm.options.tabSize;
-        var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
-        var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
-        var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
-        for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
-             line <= end; line++) {
-          var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
-          if (left == right)
-            ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
-          else if (text.length > leftPos)
-            ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
-        }
-        if (!ranges.length) ranges.push(new Range(start, start));
-        setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),
-                     {origin: "*mouse", scroll: false});
-        cm.scrollIntoView(pos);
-      } else {
-        var oldRange = ourRange;
-        var anchor = oldRange.anchor, head = pos;
-        if (type != "single") {
-          if (type == "double")
-            var range = cm.findWordAt(pos);
-          else
-            var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
-          if (cmp(range.anchor, anchor) > 0) {
-            head = range.head;
-            anchor = minPos(oldRange.from(), range.anchor);
-          } else {
-            head = range.anchor;
-            anchor = maxPos(oldRange.to(), range.head);
-          }
-        }
-        var ranges = startSel.ranges.slice(0);
-        ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
-        setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
-      }
-    }
-
-    var editorSize = display.wrapper.getBoundingClientRect();
-    // Used to ensure timeout re-tries don't fire when another extend
-    // happened in the meantime (clearTimeout isn't reliable -- at
-    // least on Chrome, the timeouts still happen even when cleared,
-    // if the clear happens after their scheduled firing time).
-    var counter = 0;
-
-    function extend(e) {
-      var curCount = ++counter;
-      var cur = posFromMouse(cm, e, true, type == "rect");
-      if (!cur) return;
-      if (cmp(cur, lastPos) != 0) {
-        ensureFocus(cm);
-        extendTo(cur);
-        var visible = visibleLines(display, doc);
-        if (cur.line >= visible.to || cur.line < visible.from)
-          setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
-      } else {
-        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
-        if (outside) setTimeout(operation(cm, function() {
-          if (counter != curCount) return;
-          display.scroller.scrollTop += outside;
-          extend(e);
-        }), 50);
-      }
-    }
-
-    function done(e) {
-      counter = Infinity;
-      e_preventDefault(e);
-      focusInput(cm);
-      off(document, "mousemove", move);
-      off(document, "mouseup", up);
-      doc.history.lastSelOrigin = null;
-    }
-
-    var move = operation(cm, function(e) {
-      if (!e_button(e)) done(e);
-      else extend(e);
-    });
-    var up = operation(cm, done);
-    on(document, "mousemove", move);
-    on(document, "mouseup", up);
-  }
-
-  // Determines whether an event happened in the gutter, and fires the
-  // handlers for the corresponding event.
-  function gutterEvent(cm, e, type, prevent, signalfn) {
-    try { var mX = e.clientX, mY = e.clientY; }
-    catch(e) { return false; }
-    if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
-    if (prevent) e_preventDefault(e);
-
-    var display = cm.display;
-    var lineBox = display.lineDiv.getBoundingClientRect();
-
-    if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
-    mY -= lineBox.top - display.viewOffset;
-
-    for (var i = 0; i < cm.options.gutters.length; ++i) {
-      var g = display.gutters.childNodes[i];
-      if (g && g.getBoundingClientRect().right >= mX) {
-        var line = lineAtHeight(cm.doc, mY);
-        var gutter = cm.options.gutters[i];
-        signalfn(cm, type, cm, line, gutter, e);
-        return e_defaultPrevented(e);
-      }
-    }
-  }
-
-  function clickInGutter(cm, e) {
-    return gutterEvent(cm, e, "gutterClick", true, signalLater);
-  }
-
-  // Kludge to work around strange IE behavior where it'll sometimes
-  // re-fire a series of drag-related events right after the drop (#1551)
-  var lastDrop = 0;
-
-  function onDrop(e) {
-    var cm = this;
-    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
-      return;
-    e_preventDefault(e);
-    if (ie) lastDrop = +new Date;
-    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
-    if (!pos || isReadOnly(cm)) return;
-    // Might be a file drop, in which case we simply extract the text
-    // and insert it.
-    if (files && files.length && window.FileReader && window.File) {
-      var n = files.length, text = Array(n), read = 0;
-      var loadFile = function(file, i) {
-        var reader = new FileReader;
-        reader.onload = operation(cm, function() {
-          text[i] = reader.result;
-          if (++read == n) {
-            pos = clipPos(cm.doc, pos);
-            var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
-            makeChange(cm.doc, change);
-            setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
-          }
-        });
-        reader.readAsText(file);
-      };
-      for (var i = 0; i < n; ++i) loadFile(files[i], i);
-    } else { // Normal drop
-      // Don't do a replace if the drop happened inside of the selected text.
-      if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
-        cm.state.draggingText(e);
-        // Ensure the editor is re-focused
-        setTimeout(bind(focusInput, cm), 20);
-        return;
-      }
-      try {
-        var text = e.dataTransfer.getData("Text");
-        if (text) {
-          if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
-            var selected = cm.listSelections();
-          setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
-          if (selected) for (var i = 0; i < selected.length; ++i)
-            replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
-          cm.replaceSelection(text, "around", "paste");
-          focusInput(cm);
-        }
-      }
-      catch(e){}
-    }
-  }
-
-  function onDragStart(cm, e) {
-    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
-    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
-
-    e.dataTransfer.setData("Text", cm.getSelection());
-
-    // Use dummy image instead of default browsers image.
-    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
-    if (e.dataTransfer.setDragImage && !safari) {
-      var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
-      img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
-      if (presto) {
-        img.width = img.height = 1;
-        cm.display.wrapper.appendChild(img);
-        // Force a relayout, or Opera won't use our image for some obscure reason
-        img._top = img.offsetTop;
-      }
-      e.dataTransfer.setDragImage(img, 0, 0);
-      if (presto) img.parentNode.removeChild(img);
-    }
-  }
-
-  // SCROLL EVENTS
-
-  // Sync the scrollable area and scrollbars, ensure the viewport
-  // covers the visible area.
-  function setScrollTop(cm, val) {
-    if (Math.abs(cm.doc.scrollTop - val) < 2) return;
-    cm.doc.scrollTop = val;
-    if (!gecko) updateDisplaySimple(cm, {top: val});
-    if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
-    if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
-    if (gecko) updateDisplaySimple(cm);
-    startWorker(cm, 100);
-  }
-  // Sync scroller and scrollbar, ensure the gutter elements are
-  // aligned.
-  function setScrollLeft(cm, val, isScroller) {
-    if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
-    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
-    cm.doc.scrollLeft = val;
-    alignHorizontally(cm);
-    if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
-    if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
-  }
-
-  // Since the delta values reported on mouse wheel events are
-  // unstandardized between browsers and even browser versions, and
-  // generally horribly unpredictable, this code starts by measuring
-  // the scroll effect that the first few mouse wheel events have,
-  // and, from that, detects the way it can convert deltas to pixel
-  // offsets afterwards.
-  //
-  // The reason we want to know the amount a wheel event will scroll
-  // is that it gives us a chance to update the display before the
-  // actual scrolling happens, reducing flickering.
-
-  var wheelSamples = 0, wheelPixelsPerUnit = null;
-  // Fill in a browser-detected starting value on browsers where we
-  // know one. These don't have to be accurate -- the result of them
-  // being wrong would just be a slight flicker on the first wheel
-  // scroll (if it is large enough).
-  if (ie) wheelPixelsPerUnit = -.53;
-  else if (gecko) wheelPixelsPerUnit = 15;
-  else if (chrome) wheelPixelsPerUnit = -.7;
-  else if (safari) wheelPixelsPerUnit = -1/3;
-
-  function onScrollWheel(cm, e) {
-    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
-    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
-    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
-    else if (dy == null) dy = e.wheelDelta;
-
-    var display = cm.display, scroll = display.scroller;
-    // Quit if there's nothing to scroll here
-    if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
-          dy && scroll.scrollHeight > scroll.clientHeight)) return;
-
-    // Webkit browsers on OS X abort momentum scrolls when the target
-    // of the scroll event is removed from the scrollable element.
-    // This hack (see related code in patchDisplay) makes sure the
-    // element is kept around.
-    if (dy && mac && webkit) {
-      outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
-        for (var i = 0; i < view.length; i++) {
-          if (view[i].node == cur) {
-            cm.display.currentWheelTarget = cur;
-            break outer;
-          }
-        }
-      }
-    }
-
-    // On some browsers, horizontal scrolling will cause redraws to
-    // happen before the gutter has been realigned, causing it to
-    // wriggle around in a most unseemly way. When we have an
-    // estimated pixels/delta value, we just handle horizontal
-    // scrolling entirely here. It'll be slightly off from native, but
-    // better than glitching out.
-    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
-      if (dy)
-        setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
-      setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
-      e_preventDefault(e);
-      display.wheelStartX = null; // Abort measurement, if in progress
-      return;
-    }
-
-    // 'Project' the visible viewport to cover the area that is being
-    // scrolled into view (if we know enough to estimate it).
-    if (dy && wheelPixelsPerUnit != null) {
-      var pixels = dy * wheelPixelsPerUnit;
-      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
-      if (pixels < 0) top = Math.max(0, top + pixels - 50);
-      else bot = Math.min(cm.doc.height, bot + pixels + 50);
-      updateDisplaySimple(cm, {top: top, bottom: bot});
-    }
-
-    if (wheelSamples < 20) {
-      if (display.wheelStartX == null) {
-        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
-        display.wheelDX = dx; display.wheelDY = dy;
-        setTimeout(function() {
-          if (display.wheelStartX == null) return;
-          var movedX = scroll.scrollLeft - display.wheelStartX;
-          var movedY = scroll.scrollTop - display.wheelStartY;
-          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
-            (movedX && display.wheelDX && movedX / display.wheelDX);
-          display.wheelStartX = display.wheelStartY = null;
-          if (!sample) return;
-          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
-          ++wheelSamples;
-        }, 200);
-      } else {
-        display.wheelDX += dx; display.wheelDY += dy;
-      }
-    }
-  }
-
-  // KEY EVENTS
-
-  // Run a handler that was bound to a key.
-  function doHandleBinding(cm, bound, dropShift) {
-    if (typeof bound == "string") {
-      bound = commands[bound];
-      if (!bound) return false;
-    }
-    // Ensure previous input has been read, so that the handler sees a
-    // consistent view of the document
-    if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
-    var prevShift = cm.display.shift, done = false;
-    try {
-      if (isReadOnly(cm)) cm.state.suppressEdits = true;
-      if (dropShift) cm.display.shift = false;
-      done = bound(cm) != Pass;
-    } finally {
-      cm.display.shift = prevShift;
-      cm.state.suppressEdits = false;
-    }
-    return done;
-  }
-
-  // Collect the currently active keymaps.
-  function allKeyMaps(cm) {
-    var maps = cm.state.keyMaps.slice(0);
-    if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
-    maps.push(cm.options.keyMap);
-    return maps;
-  }
-
-  var maybeTransition;
-  // Handle a key from the keydown event.
-  function handleKeyBinding(cm, e) {
-    // Handle automatic keymap transitions
-    var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
-    clearTimeout(maybeTransition);
-    if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
-      if (getKeyMap(cm.options.keyMap) == startMap) {
-        cm.options.keyMap = (next.call ? next.call(null, cm) : next);
-        keyMapChanged(cm);
-      }
-    }, 50);
-
-    var name = keyName(e, true), handled = false;
-    if (!name) return false;
-    var keymaps = allKeyMaps(cm);
-
-    if (e.shiftKey) {
-      // First try to resolve full name (including 'Shift-'). Failing
-      // that, see if there is a cursor-motion command (starting with
-      // 'go') bound to the keyname without 'Shift-'.
-      handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
-             || lookupKey(name, keymaps, function(b) {
-                  if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
-                    return doHandleBinding(cm, b);
-                });
-    } else {
-      handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
-    }
-
-    if (handled) {
-      e_preventDefault(e);
-      restartBlink(cm);
-      signalLater(cm, "keyHandled", cm, name, e);
-    }
-    return handled;
-  }
-
-  // Handle a key from the keypress event
-  function handleCharBinding(cm, e, ch) {
-    var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
-                            function(b) { return doHandleBinding(cm, b, true); });
-    if (handled) {
-      e_preventDefault(e);
-      restartBlink(cm);
-      signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
-    }
-    return handled;
-  }
-
-  var lastStoppedKey = null;
-  function onKeyDown(e) {
-    var cm = this;
-    ensureFocus(cm);
-    if (signalDOMEvent(cm, e)) return;
-    // IE does strange things with escape.
-    if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false;
-    var code = e.keyCode;
-    cm.display.shift = code == 16 || e.shiftKey;
-    var handled = handleKeyBinding(cm, e);
-    if (presto) {
-      lastStoppedKey = handled ? code : null;
-      // Opera has no cut event... we try to at least catch the key combo
-      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
-        cm.replaceSelection("", null, "cut");
-    }
-
-    // Turn mouse into crosshair when Alt is held on Mac.
-    if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
-      showCrossHair(cm);
-  }
-
-  function showCrossHair(cm) {
-    var lineDiv = cm.display.lineDiv;
-    addClass(lineDiv, "CodeMirror-crosshair");
-
-    function up(e) {
-      if (e.keyCode == 18 || !e.altKey) {
-        rmClass(lineDiv, "CodeMirror-crosshair");
-        off(document, "keyup", up);
-        off(document, "mouseover", up);
-      }
-    }
-    on(document, "keyup", up);
-    on(document, "mouseover", up);
-  }
-
-  function onKeyUp(e) {
-    if (e.keyCode == 16) this.doc.sel.shift = false;
-    signalDOMEvent(this, e);
-  }
-
-  function onKeyPress(e) {
-    var cm = this;
-    if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return;
-    var keyCode = e.keyCode, charCode = e.charCode;
-    if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
-    if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
-    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
-    if (handleCharBinding(cm, e, ch)) return;
-    if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
-    fastPoll(cm);
-  }
-
-  // FOCUS/BLUR EVENTS
-
-  function onFocus(cm) {
-    if (cm.options.readOnly == "nocursor") return;
-    if (!cm.state.focused) {
-      signal(cm, "focus", cm);
-      cm.state.focused = true;
-      addClass(cm.display.wrapper, "CodeMirror-focused");
-      // The prevInput test prevents this from firing when a context
-      // menu is closed (since the resetInput would kill the
-      // select-all detection hack)
-      if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {
-        resetInput(cm);
-        if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
-      }
-    }
-    slowPoll(cm);
-    restartBlink(cm);
-  }
-  function onBlur(cm) {
-    if (cm.state.focused) {
-      signal(cm, "blur", cm);
-      cm.state.focused = false;
-      rmClass(cm.display.wrapper, "CodeMirror-focused");
-    }
-    clearInterval(cm.display.blinker);
-    setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
-  }
-
-  // CONTEXT MENU HANDLING
-
-  // To make the context menu work, we need to briefly unhide the
-  // textarea (making it as unobtrusive as possible) to let the
-  // right-click take effect on it.
-  function onContextMenu(cm, e) {
-    if (signalDOMEvent(cm, e, "contextmenu")) return;
-    var display = cm.display;
-    if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
-
-    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
-    if (!pos || presto) return; // Opera is difficult.
-
-    // Reset the current text selection only if the click is done outside of the selection
-    // and 'resetSelectionOnContextMenu' option is true.
-    var reset = cm.options.resetSelectionOnContextMenu;
-    if (reset && cm.doc.sel.contains(pos) == -1)
-      operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
-
-    var oldCSS = display.input.style.cssText;
-    display.inputDiv.style.position = "absolute";
-    display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
-      "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
-      (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
-      "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
-    if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712)
-    focusInput(cm);
-    if (webkit) window.scrollTo(null, oldScrollY);
-    resetInput(cm);
-    // Adds "Select all" to context menu in FF
-    if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
-    display.selForContextMenu = cm.doc.sel;
-    clearTimeout(display.detectingSelectAll);
-
-    // Select-all will be greyed out if there's nothing to select, so
-    // this adds a zero-width space so that we can later check whether
-    // it got selected.
-    function prepareSelectAllHack() {
-      if (display.input.selectionStart != null) {
-        var selected = cm.somethingSelected();
-        var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
-        display.prevInput = selected ? "" : "\u200b";
-        display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
-        // Re-set this, in case some other handler touched the
-        // selection in the meantime.
-        display.selForContextMenu = cm.doc.sel;
-      }
-    }
-    function rehide() {
-      display.inputDiv.style.position = "relative";
-      display.input.style.cssText = oldCSS;
-      if (ie && ie_version < 9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
-      slowPoll(cm);
-
-      // Try to detect the user choosing select-all
-      if (display.input.selectionStart != null) {
-        if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
-        var i = 0, poll = function() {
-          if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
-            operation(cm, commands.selectAll)(cm);
-          else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
-          else resetInput(cm);
-        };
-        display.detectingSelectAll = setTimeout(poll, 200);
-      }
-    }
-
-    if (ie && ie_version >= 9) prepareSelectAllHack();
-    if (captureRightClick) {
-      e_stop(e);
-      var mouseup = function() {
-        off(window, "mouseup", mouseup);
-        setTimeout(rehide, 20);
-      };
-      on(window, "mouseup", mouseup);
-    } else {
-      setTimeout(rehide, 50);
-    }
-  }
-
-  function contextMenuInGutter(cm, e) {
-    if (!hasHandler(cm, "gutterContextMenu")) return false;
-    return gutterEvent(cm, e, "gutterContextMenu", false, signal);
-  }
-
-  // UPDATING
-
-  // Compute the position of the end of a change (its 'to' property
-  // refers to the pre-change end).
-  var changeEnd = CodeMirror.changeEnd = function(change) {
-    if (!change.text) return change.to;
-    return Pos(change.from.line + change.text.length - 1,
-               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
-  };
-
-  // Adjust a position to refer to the post-change position of the
-  // same text, or the end of the change if the change covers it.
-  function adjustForChange(pos, change) {
-    if (cmp(pos, change.from) < 0) return pos;
-    if (cmp(pos, change.to) <= 0) return changeEnd(change);
-
-    var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
-    if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch;
-    return Pos(line, ch);
-  }
-
-  function computeSelAfterChange(doc, change) {
-    var out = [];
-    for (var i = 0; i < doc.sel.ranges.length; i++) {
-      var range = doc.sel.ranges[i];
-      out.push(new Range(adjustForChange(range.anchor, change),
-                         adjustForChange(range.head, change)));
-    }
-    return normalizeSelection(out, doc.sel.primIndex);
-  }
-
-  function offsetPos(pos, old, nw) {
-    if (pos.line == old.line)
-      return Pos(nw.line, pos.ch - old.ch + nw.ch);
-    else
-      return Pos(nw.line + (pos.line - old.line), pos.ch);
-  }
-
-  // Used by replaceSelections to allow moving the selection to the
-  // start or around the replaced test. Hint may be "start" or "around".
-  function computeReplacedSel(doc, changes, hint) {
-    var out = [];
-    var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
-    for (var i = 0; i < changes.length; i++) {
-      var change = changes[i];
-      var from = offsetPos(change.from, oldPrev, newPrev);
-      var to = offsetPos(changeEnd(change), oldPrev, newPrev);
-      oldPrev = change.to;
-      newPrev = to;
-      if (hint == "around") {
-        var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
-        out[i] = new Range(inv ? to : from, inv ? from : to);
-      } else {
-        out[i] = new Range(from, from);
-      }
-    }
-    return new Selection(out, doc.sel.primIndex);
-  }
-
-  // Allow "beforeChange" event handlers to influence a change
-  function filterChange(doc, change, update) {
-    var obj = {
-      canceled: false,
-      from: change.from,
-      to: change.to,
-      text: change.text,
-      origin: change.origin,
-      cancel: function() { this.canceled = true; }
-    };
-    if (update) obj.update = function(from, to, text, origin) {
-      if (from) this.from = clipPos(doc, from);
-      if (to) this.to = clipPos(doc, to);
-      if (text) this.text = text;
-      if (origin !== undefined) this.origin = origin;
-    };
-    signal(doc, "beforeChange", doc, obj);
-    if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
-
-    if (obj.canceled) return null;
-    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
-  }
-
-  // Apply a change to a document, and add it to the document's
-  // history, and propagating it to all linked documents.
-  function makeChange(doc, change, ignoreReadOnly) {
-    if (doc.cm) {
-      if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly);
-      if (doc.cm.state.suppressEdits) return;
-    }
-
-    if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
-      change = filterChange(doc, change, true);
-      if (!change) return;
-    }
-
-    // Possibly split or suppress the update based on the presence
-    // of read-only spans in its range.
-    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
-    if (split) {
-      for (var i = split.length - 1; i >= 0; --i)
-        makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text});
-    } else {
-      makeChangeInner(doc, change);
-    }
-  }
-
-  function makeChangeInner(doc, change) {
-    if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return;
-    var selAfter = computeSelAfterChange(doc, change);
-    addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
-
-    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
-    var rebased = [];
-
-    linkedDocs(doc, function(doc, sharedHist) {
-      if (!sharedHist && indexOf(rebased, doc.history) == -1) {
-        rebaseHist(doc.history, change);
-        rebased.push(doc.history);
-      }
-      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
-    });
-  }
-
-  // Revert a change stored in a document's history.
-  function makeChangeFromHistory(doc, type, allowSelectionOnly) {
-    if (doc.cm && doc.cm.state.suppressEdits) return;
-
-    var hist = doc.history, event, selAfter = doc.sel;
-    var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
-
-    // Verify that there is a useable event (so that ctrl-z won't
-    // needlessly clear selection events)
-    for (var i = 0; i < source.length; i++) {
-      event = source[i];
-      if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
-        break;
-    }
-    if (i == source.length) return;
-    hist.lastOrigin = hist.lastSelOrigin = null;
-
-    for (;;) {
-      event = source.pop();
-      if (event.ranges) {
-        pushSelectionToHistory(event, dest);
-        if (allowSelectionOnly && !event.equals(doc.sel)) {
-          setSelection(doc, event, {clearRedo: false});
-          return;
-        }
-        selAfter = event;
-      }
-      else break;
-    }
-
-    // Build up a reverse change object to add to the opposite history
-    // stack (redo when undoing, and vice versa).
-    var antiChanges = [];
-    pushSelectionToHistory(selAfter, dest);
-    dest.push({changes: antiChanges, generation: hist.generation});
-    hist.generation = event.generation || ++hist.maxGeneration;
-
-    var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
-
-    for (var i = event.changes.length - 1; i >= 0; --i) {
-      var change = event.changes[i];
-      change.origin = type;
-      if (filter && !filterChange(doc, change, false)) {
-        source.length = 0;
-        return;
-      }
-
-      antiChanges.push(historyChangeFromChange(doc, change));
-
-      var after = i ? computeSelAfterChange(doc, change) : lst(source);
-      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
-      if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)});
-      var rebased = [];
-
-      // Propagate to the linked documents
-      linkedDocs(doc, function(doc, sharedHist) {
-        if (!sharedHist && indexOf(rebased, doc.history) == -1) {
-          rebaseHist(doc.history, change);
-          rebased.push(doc.history);
-        }
-        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
-      });
-    }
-  }
-
-  // Sub-views need their line numbers shifted when text is added
-  // above or below them in the parent document.
-  function shiftDoc(doc, distance) {
-    if (distance == 0) return;
-    doc.first += distance;
-    doc.sel = new Selection(map(doc.sel.ranges, function(range) {
-      return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
-                       Pos(range.head.line + distance, range.head.ch));
-    }), doc.sel.primIndex);
-    if (doc.cm) {
-      regChange(doc.cm, doc.first, doc.first - distance, distance);
-      for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)
-        regLineChange(doc.cm, l, "gutter");
-    }
-  }
-
-  // More lower-level change function, handling only a single document
-  // (not linked ones).
-  function makeChangeSingleDoc(doc, change, selAfter, spans) {
-    if (doc.cm && !doc.cm.curOp)
-      return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
-
-    if (change.to.line < doc.first) {
-      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
-      return;
-    }
-    if (change.from.line > doc.lastLine()) return;
-
-    // Clip the change to the size of this doc
-    if (change.from.line < doc.first) {
-      var shift = change.text.length - 1 - (doc.first - change.from.line);
-      shiftDoc(doc, shift);
-      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
-                text: [lst(change.text)], origin: change.origin};
-    }
-    var last = doc.lastLine();
-    if (change.to.line > last) {
-      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
-                text: [change.text[0]], origin: change.origin};
-    }
-
-    change.removed = getBetween(doc, change.from, change.to);
-
-    if (!selAfter) selAfter = computeSelAfterChange(doc, change);
-    if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
-    else updateDoc(doc, change, spans);
-    setSelectionNoUndo(doc, selAfter, sel_dontScroll);
-  }
-
-  // Handle the interaction of a change to a document with the editor
-  // that this document is part of.
-  function makeChangeSingleDocInEditor(cm, change, spans) {
-    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
-
-    var recomputeMaxLength = false, checkWidthStart = from.line;
-    if (!cm.options.lineWrapping) {
-      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
-      doc.iter(checkWidthStart, to.line + 1, function(line) {
-        if (line == display.maxLine) {
-          recomputeMaxLength = true;
-          return true;
-        }
-      });
-    }
-
-    if (doc.sel.contains(change.from, change.to) > -1)
-      signalCursorActivity(cm);
-
-    updateDoc(doc, change, spans, estimateHeight(cm));
-
-    if (!cm.options.lineWrapping) {
-      doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
-        var len = lineLength(line);
-        if (len > display.maxLineLength) {
-          display.maxLine = line;
-          display.maxLineLength = len;
-          display.maxLineChanged = true;
-          recomputeMaxLength = false;
-        }
-      });
-      if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
-    }
-
-    // Adjust frontier, schedule worker
-    doc.frontier = Math.min(doc.frontier, from.line);
-    startWorker(cm, 400);
-
-    var lendiff = change.text.length - (to.line - from.line) - 1;
-    // Remember that these lines changed, for updating the display
-    if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
-      regLineChange(cm, from.line, "text");
-    else
-      regChange(cm, from.line, to.line + 1, lendiff);
-
-    var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change");
-    if (changeHandler || changesHandler) {
-      var obj = {
-        from: from, to: to,
-        text: change.text,
-        removed: change.removed,
-        origin: change.origin
-      };
-      if (changeHandler) signalLater(cm, "change", cm, obj);
-      if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj);
-    }
-    cm.display.selForContextMenu = null;
-  }
-
-  function replaceRange(doc, code, from, to, origin) {
-    if (!to) to = from;
-    if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
-    if (typeof code == "string") code = splitLines(code);
-    makeChange(doc, {from: from, to: to, text: code, origin: origin});
-  }
-
-  // SCROLLING THINGS INTO VIEW
-
-  // If an editor sits on the top or bottom of the window, partially
-  // scrolled out of view, this ensures that the cursor is visible.
-  function maybeScrollWindow(cm, coords) {
-    var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
-    if (coords.top + box.top < 0) doScroll = true;
-    else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
-    if (doScroll != null && !phantom) {
-      var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
-                           (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
-                           (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
-                           coords.left + "px; width: 2px;");
-      cm.display.lineSpace.appendChild(scrollNode);
-      scrollNode.scrollIntoView(doScroll);
-      cm.display.lineSpace.removeChild(scrollNode);
-    }
-  }
-
-  // Scroll a given position into view (immediately), verifying that
-  // it actually became visible (as line heights are accurately
-  // measured, the position of something may 'drift' during drawing).
-  function scrollPosIntoView(cm, pos, end, margin) {
-    if (margin == null) margin = 0;
-    for (var limit = 0; limit < 5; limit++) {
-      var changed = false, coords = cursorCoords(cm, pos);
-      var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
-      var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
-                                         Math.min(coords.top, endCoords.top) - margin,
-                                         Math.max(coords.left, endCoords.left),
-                                         Math.max(coords.bottom, endCoords.bottom) + margin);
-      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
-      if (scrollPos.scrollTop != null) {
-        setScrollTop(cm, scrollPos.scrollTop);
-        if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
-      }
-      if (scrollPos.scrollLeft != null) {
-        setScrollLeft(cm, scrollPos.scrollLeft);
-        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
-      }
-      if (!changed) return coords;
-    }
-  }
-
-  // Scroll a given set of coordinates into view (immediately).
-  function scrollIntoView(cm, x1, y1, x2, y2) {
-    var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
-    if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
-    if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
-  }
-
-  // Calculate a new scroll position needed to scroll the given
-  // rectangle into view. Returns an object with scrollTop and
-  // scrollLeft properties. When these are undefined, the
-  // vertical/horizontal position does not need to be adjusted.
-  function calculateScrollPos(cm, x1, y1, x2, y2) {
-    var display = cm.display, snapMargin = textHeight(cm.display);
-    if (y1 < 0) y1 = 0;
-    var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
-    var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
-    if (y2 - y1 > screen) y2 = y1 + screen;
-    var docBottom = cm.doc.height + paddingVert(display);
-    var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
-    if (y1 < screentop) {
-      result.scrollTop = atTop ? 0 : y1;
-    } else if (y2 > screentop + screen) {
-      var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen);
-      if (newTop != screentop) result.scrollTop = newTop;
-    }
-
-    var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
-    var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth;
-    var tooWide = x2 - x1 > screenw;
-    if (tooWide) x2 = x1 + screenw;
-    if (x1 < 10)
-      result.scrollLeft = 0;
-    else if (x1 < screenleft)
-      result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
-    else if (x2 > screenw + screenleft - 3)
-      result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;
-
-    return result;
-  }
-
-  // Store a relative adjustment to the scroll position in the current
-  // operation (to be applied when the operation finishes).
-  function addToScrollPos(cm, left, top) {
-    if (left != null || top != null) resolveScrollToPos(cm);
-    if (left != null)
-      cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left;
-    if (top != null)
-      cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
-  }
-
-  // Make sure that at the end of the operation the current cursor is
-  // shown.
-  function ensureCursorVisible(cm) {
-    resolveScrollToPos(cm);
-    var cur = cm.getCursor(), from = cur, to = cur;
-    if (!cm.options.lineWrapping) {
-      from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
-      to = Pos(cur.line, cur.ch + 1);
-    }
-    cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true};
-  }
-
-  // When an operation has its scrollToPos property set, and another
-  // scroll action is applied before the end of the operation, this
-  // 'simulates' scrolling that position into view in a cheap way, so
-  // that the effect of intermediate scroll commands is not ignored.
-  function resolveScrollToPos(cm) {
-    var range = cm.curOp.scrollToPos;
-    if (range) {
-      cm.curOp.scrollToPos = null;
-      var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);
-      var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
-                                    Math.min(from.top, to.top) - range.margin,
-                                    Math.max(from.right, to.right),
-                                    Math.max(from.bottom, to.bottom) + range.margin);
-      cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
-    }
-  }
-
-  // API UTILITIES
-
-  // Indent the given line. The how parameter can be "smart",
-  // "add"/null, "subtract", or "prev". When aggressive is false
-  // (typically set to true for forced single-line indents), empty
-  // lines are not indented, and places where the mode returns Pass
-  // are left alone.
-  function indentLine(cm, n, how, aggressive) {
-    var doc = cm.doc, state;
-    if (how == null) how = "add";
-    if (how == "smart") {
-      // Fall back to "prev" when the mode doesn't have an indentation
-      // method.
-      if (!doc.mode.indent) how = "prev";
-      else state = getStateBefore(cm, n);
-    }
-
-    var tabSize = cm.options.tabSize;
-    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
-    if (line.stateAfter) line.stateAfter = null;
-    var curSpaceString = line.text.match(/^\s*/)[0], indentation;
-    if (!aggressive && !/\S/.test(line.text)) {
-      indentation = 0;
-      how = "not";
-    } else if (how == "smart") {
-      indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
-      if (indentation == Pass || indentation > 150) {
-        if (!aggressive) return;
-        how = "prev";
-      }
-    }
-    if (how == "prev") {
-      if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
-      else indentation = 0;
-    } else if (how == "add") {
-      indentation = curSpace + cm.options.indentUnit;
-    } else if (how == "subtract") {
-      indentation = curSpace - cm.options.indentUnit;
-    } else if (typeof how == "number") {
-      indentation = curSpace + how;
-    }
-    indentation = Math.max(0, indentation);
-
-    var indentString = "", pos = 0;
-    if (cm.options.indentWithTabs)
-      for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
-    if (pos < indentation) indentString += spaceStr(indentation - pos);
-
-    if (indentString != curSpaceString) {
-      replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
-    } else {
-      // Ensure that, if the cursor was in the whitespace at the start
-      // of the line, it is moved to the end of that space.
-      for (var i = 0; i < doc.sel.ranges.length; i++) {
-        var range = doc.sel.ranges[i];
-        if (range.head.line == n && range.head.ch < curSpaceString.length) {
-          var pos = Pos(n, curSpaceString.length);
-          replaceOneSelection(doc, i, new Range(pos, pos));
-          break;
-        }
-      }
-    }
-    line.stateAfter = null;
-  }
-
-  // Utility for applying a change to a line by handle or number,
-  // returning the number and optionally registering the line as
-  // changed.
-  function changeLine(doc, handle, changeType, op) {
-    var no = handle, line = handle;
-    if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
-    else no = lineNo(handle);
-    if (no == null) return null;
-    if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType);
-    return line;
-  }
-
-  // Helper for deleting text near the selection(s), used to implement
-  // backspace, delete, and similar functionality.
-  function deleteNearSelection(cm, compute) {
-    var ranges = cm.doc.sel.ranges, kill = [];
-    // Build up a set of ranges to kill first, merging overlapping
-    // ranges.
-    for (var i = 0; i < ranges.length; i++) {
-      var toKill = compute(ranges[i]);
-      while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
-        var replaced = kill.pop();
-        if (cmp(replaced.from, toKill.from) < 0) {
-          toKill.from = replaced.from;
-          break;
-        }
-      }
-      kill.push(toKill);
-    }
-    // Next, remove those actual ranges.
-    runInOp(cm, function() {
-      for (var i = kill.length - 1; i >= 0; i--)
-        replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete");
-      ensureCursorVisible(cm);
-    });
-  }
-
-  // Used for horizontal relative motion. Dir is -1 or 1 (left or
-  // right), unit can be "char", "column" (like char, but doesn't
-  // cross line boundaries), "word" (across next word), or "group" (to
-  // the start of next group of word or non-word-non-whitespace
-  // chars). The visually param controls whether, in right-to-left
-  // text, direction 1 means to move towards the next index in the
-  // string, or towards the character to the right of the current
-  // position. The resulting position will have a hitSide=true
-  // property if it reached the end of the document.
-  function findPosH(doc, pos, dir, unit, visually) {
-    var line = pos.line, ch = pos.ch, origDir = dir;
-    var lineObj = getLine(doc, line);
-    var possible = true;
-    function findNextLine() {
-      var l = line + dir;
-      if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
-      line = l;
-      return lineObj = getLine(doc, l);
-    }
-    function moveOnce(boundToLine) {
-      var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
-      if (next == null) {
-        if (!boundToLine && findNextLine()) {
-          if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
-          else ch = dir < 0 ? lineObj.text.length : 0;
-        } else return (possible = false);
-      } else ch = next;
-      return true;
-    }
-
-    if (unit == "char") moveOnce();
-    else if (unit == "column") moveOnce(true);
-    else if (unit == "word" || unit == "group") {
-      var sawType = null, group = unit == "group";
-      var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
-      for (var first = true;; first = false) {
-        if (dir < 0 && !moveOnce(!first)) break;
-        var cur = lineObj.text.charAt(ch) || "\n";
-        var type = isWordChar(cur, helper) ? "w"
-          : group && cur == "\n" ? "n"
-          : !group || /\s/.test(cur) ? null
-          : "p";
-        if (group && !first && !type) type = "s";
-        if (sawType && sawType != type) {
-          if (dir < 0) {dir = 1; moveOnce();}
-          break;
-        }
-
-        if (type) sawType = type;
-        if (dir > 0 && !moveOnce(!first)) break;
-      }
-    }
-    var result = skipAtomic(doc, Pos(line, ch), origDir, true);
-    if (!possible) result.hitSide = true;
-    return result;
-  }
-
-  // For relative vertical movement. Dir may be -1 or 1. Unit can be
-  // "page" or "line". The resulting position will have a hitSide=true
-  // property if it reached the end of the document.
-  function findPosV(cm, pos, dir, unit) {
-    var doc = cm.doc, x = pos.left, y;
-    if (unit == "page") {
-      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
-      y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
-    } else if (unit == "line") {
-      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
-    }
-    for (;;) {
-      var target = coordsChar(cm, x, y);
-      if (!target.outside) break;
-      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
-      y += dir * 5;
-    }
-    return target;
-  }
-
-  // EDITOR METHODS
-
-  // The publicly visible API. Note that methodOp(f) means
-  // 'wrap f in an operation, performed on its `this` parameter'.
-
-  // This is not the complete set of editor methods. Most of the
-  // methods defined on the Doc type are also injected into
-  // CodeMirror.prototype, for backwards compatibility and
-  // convenience.
-
-  CodeMirror.prototype = {
-    constructor: CodeMirror,
-    focus: function(){window.focus(); focusInput(this); fastPoll(this);},
-
-    setOption: function(option, value) {
-      var options = this.options, old = options[option];
-      if (options[option] == value && option != "mode") return;
-      options[option] = value;
-      if (optionHandlers.hasOwnProperty(option))
-        operation(this, optionHandlers[option])(this, value, old);
-    },
-
-    getOption: function(option) {return this.options[option];},
-    getDoc: function() {return this.doc;},
-
-    addKeyMap: function(map, bottom) {
-      this.state.keyMaps[bottom ? "push" : "unshift"](map);
-    },
-    removeKeyMap: function(map) {
-      var maps = this.state.keyMaps;
-      for (var i = 0; i < maps.length; ++i)
-        if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) {
-          maps.splice(i, 1);
-          return true;
-        }
-    },
-
-    addOverlay: methodOp(function(spec, options) {
-      var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
-      if (mode.startState) throw new Error("Overlays may not be stateful.");
-      this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
-      this.state.modeGen++;
-      regChange(this);
-    }),
-    removeOverlay: methodOp(function(spec) {
-      var overlays = this.state.overlays;
-      for (var i = 0; i < overlays.length; ++i) {
-        var cur = overlays[i].modeSpec;
-        if (cur == spec || typeof spec == "string" && cur.name == spec) {
-          overlays.splice(i, 1);
-          this.state.modeGen++;
-          regChange(this);
-          return;
-        }
-      }
-    }),
-
-    indentLine: methodOp(function(n, dir, aggressive) {
-      if (typeof dir != "string" && typeof dir != "number") {
-        if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
-        else dir = dir ? "add" : "subtract";
-      }
-      if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
-    }),
-    indentSelection: methodOp(function(how) {
-      var ranges = this.doc.sel.ranges, end = -1;
-      for (var i = 0; i < ranges.length; i++) {
-        var range = ranges[i];
-        if (!range.empty()) {
-          var from = range.from(), to = range.to();
-          var start = Math.max(end, from.line);
-          end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
-          for (var j = start; j < end; ++j)
-            indentLine(this, j, how);
-          var newRanges = this.doc.sel.ranges;
-          if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
-            replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
-        } else if (range.head.line > end) {
-          indentLine(this, range.head.line, how, true);
-          end = range.head.line;
-          if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
-        }
-      }
-    }),
-
-    // Fetch the parser token for a given character. Useful for hacks
-    // that want to inspect the mode state (say, for completion).
-    getTokenAt: function(pos, precise) {
-      var doc = this.doc;
-      pos = clipPos(doc, pos);
-      var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
-      var line = getLine(doc, pos.line);
-      var stream = new StringStream(line.text, this.options.tabSize);
-      while (stream.pos < pos.ch && !stream.eol()) {
-        stream.start = stream.pos;
-        var style = readToken(mode, stream, state);
-      }
-      return {start: stream.start,
-              end: stream.pos,
-              string: stream.current(),
-              type: style || null,
-              state: state};
-    },
-
-    getTokenTypeAt: function(pos) {
-      pos = clipPos(this.doc, pos);
-      var styles = getLineStyles(this, getLine(this.doc, pos.line));
-      var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;
-      var type;
-      if (ch == 0) type = styles[2];
-      else for (;;) {
-        var mid = (before + after) >> 1;
-        if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid;
-        else if (styles[mid * 2 + 1] < ch) before = mid + 1;
-        else { type = styles[mid * 2 + 2]; break; }
-      }
-      var cut = type ? type.indexOf("cm-overlay ") : -1;
-      return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
-    },
-
-    getModeAt: function(pos) {
-      var mode = this.doc.mode;
-      if (!mode.innerMode) return mode;
-      return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
-    },
-
-    getHelper: function(pos, type) {
-      return this.getHelpers(pos, type)[0];
-    },
-
-    getHelpers: function(pos, type) {
-      var found = [];
-      if (!helpers.hasOwnProperty(type)) return helpers;
-      var help = helpers[type], mode = this.getModeAt(pos);
-      if (typeof mode[type] == "string") {
-        if (help[mode[type]]) found.push(help[mode[type]]);
-      } else if (mode[type]) {
-        for (var i = 0; i < mode[type].length; i++) {
-          var val = help[mode[type][i]];
-          if (val) found.push(val);
-        }
-      } else if (mode.helperType && help[mode.helperType]) {
-        found.push(help[mode.helperType]);
-      } else if (help[mode.name]) {
-        found.push(help[mode.name]);
-      }
-      for (var i = 0; i < help._global.length; i++) {
-        var cur = help._global[i];
-        if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
-          found.push(cur.val);
-      }
-      return found;
-    },
-
-    getStateAfter: function(line, precise) {
-      var doc = this.doc;
-      line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
-      return getStateBefore(this, line + 1, precise);
-    },
-
-    cursorCoords: function(start, mode) {
-      var pos, range = this.doc.sel.primary();
-      if (start == null) pos = range.head;
-      else if (typeof start == "object") pos = clipPos(this.doc, start);
-      else pos = start ? range.from() : range.to();
-      return cursorCoords(this, pos, mode || "page");
-    },
-
-    charCoords: function(pos, mode) {
-      return charCoords(this, clipPos(this.doc, pos), mode || "page");
-    },
-
-    coordsChar: function(coords, mode) {
-      coords = fromCoordSystem(this, coords, mode || "page");
-      return coordsChar(this, coords.left, coords.top);
-    },
-
-    lineAtHeight: function(height, mode) {
-      height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top;
-      return lineAtHeight(this.doc, height + this.display.viewOffset);
-    },
-    heightAtLine: function(line, mode) {
-      var end = false, last = this.doc.first + this.doc.size - 1;
-      if (line < this.doc.first) line = this.doc.first;
-      else if (line > last) { line = last; end = true; }
-      var lineObj = getLine(this.doc, line);
-      return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
-        (end ? this.doc.height - heightAtLine(lineObj) : 0);
-    },
-
-    defaultTextHeight: function() { return textHeight(this.display); },
-    defaultCharWidth: function() { return charWidth(this.display); },
-
-    setGutterMarker: methodOp(function(line, gutterID, value) {
-      return changeLine(this.doc, line, "gutter", function(line) {
-        var markers = line.gutterMarkers || (line.gutterMarkers = {});
-        markers[gutterID] = value;
-        if (!value && isEmpty(markers)) line.gutterMarkers = null;
-        return true;
-      });
-    }),
-
-    clearGutter: methodOp(function(gutterID) {
-      var cm = this, doc = cm.doc, i = doc.first;
-      doc.iter(function(line) {
-        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
-          line.gutterMarkers[gutterID] = null;
-          regLineChange(cm, i, "gutter");
-          if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
-        }
-        ++i;
-      });
-    }),
-
-    addLineWidget: methodOp(function(handle, node, options) {
-      return addLineWidget(this, handle, node, options);
-    }),
-
-    removeLineWidget: function(widget) { widget.clear(); },
-
-    lineInfo: function(line) {
-      if (typeof line == "number") {
-        if (!isLine(this.doc, line)) return null;
-        var n = line;
-        line = getLine(this.doc, line);
-        if (!line) return null;
-      } else {
-        var n = lineNo(line);
-        if (n == null) return null;
-      }
-      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
-              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
-              widgets: line.widgets};
-    },
-
-    getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};},
-
-    addWidget: function(pos, node, scroll, vert, horiz) {
-      var display = this.display;
-      pos = cursorCoords(this, clipPos(this.doc, pos));
-      var top = pos.bottom, left = pos.left;
-      node.style.position = "absolute";
-      display.sizer.appendChild(node);
-      if (vert == "over") {
-        top = pos.top;
-      } else if (vert == "above" || vert == "near") {
-        var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
-        hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
-        // Default to positioning above (if specified and possible); otherwise default to positioning below
-        if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
-          top = pos.top - node.offsetHeight;
-        else if (pos.bottom + node.offsetHeight <= vspace)
-          top = pos.bottom;
-        if (left + node.offsetWidth > hspace)
-          left = hspace - node.offsetWidth;
-      }
-      node.style.top = top + "px";
-      node.style.left = node.style.right = "";
-      if (horiz == "right") {
-        left = display.sizer.clientWidth - node.offsetWidth;
-        node.style.right = "0px";
-      } else {
-        if (horiz == "left") left = 0;
-        else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
-        node.style.left = left + "px";
-      }
-      if (scroll)
-        scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
-    },
-
-    triggerOnKeyDown: methodOp(onKeyDown),
-    triggerOnKeyPress: methodOp(onKeyPress),
-    triggerOnKeyUp: onKeyUp,
-
-    execCommand: function(cmd) {
-      if (commands.hasOwnProperty(cmd))
-        return commands[cmd](this);
-    },
-
-    findPosH: function(from, amount, unit, visually) {
-      var dir = 1;
-      if (amount < 0) { dir = -1; amount = -amount; }
-      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
-        cur = findPosH(this.doc, cur, dir, unit, visually);
-        if (cur.hitSide) break;
-      }
-      return cur;
-    },
-
-    moveH: methodOp(function(dir, unit) {
-      var cm = this;
-      cm.extendSelectionsBy(function(range) {
-        if (cm.display.shift || cm.doc.extend || range.empty())
-          return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually);
-        else
-          return dir < 0 ? range.from() : range.to();
-      }, sel_move);
-    }),
-
-    deleteH: methodOp(function(dir, unit) {
-      var sel = this.doc.sel, doc = this.doc;
-      if (sel.somethingSelected())
-        doc.replaceSelection("", null, "+delete");
-      else
-        deleteNearSelection(this, function(range) {
-          var other = findPosH(doc, range.head, dir, unit, false);
-          return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other};
-        });
-    }),
-
-    findPosV: function(from, amount, unit, goalColumn) {
-      var dir = 1, x = goalColumn;
-      if (amount < 0) { dir = -1; amount = -amount; }
-      for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
-        var coords = cursorCoords(this, cur, "div");
-        if (x == null) x = coords.left;
-        else coords.left = x;
-        cur = findPosV(this, coords, dir, unit);
-        if (cur.hitSide) break;
-      }
-      return cur;
-    },
-
-    moveV: methodOp(function(dir, unit) {
-      var cm = this, doc = this.doc, goals = [];
-      var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected();
-      doc.extendSelectionsBy(function(range) {
-        if (collapse)
-          return dir < 0 ? range.from() : range.to();
-        var headPos = cursorCoords(cm, range.head, "div");
-        if (range.goalColumn != null) headPos.left = range.goalColumn;
-        goals.push(headPos.left);
-        var pos = findPosV(cm, headPos, dir, unit);
-        if (unit == "page" && range == doc.sel.primary())
-          addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top);
-        return pos;
-      }, sel_move);
-      if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++)
-        doc.sel.ranges[i].goalColumn = goals[i];
-    }),
-
-    // Find the word at the given position (as returned by coordsChar).
-    findWordAt: function(pos) {
-      var doc = this.doc, line = getLine(doc, pos.line).text;
-      var start = pos.ch, end = pos.ch;
-      if (line) {
-        var helper = this.getHelper(pos, "wordChars");
-        if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
-        var startChar = line.charAt(start);
-        var check = isWordChar(startChar, helper)
-          ? function(ch) { return isWordChar(ch, helper); }
-          : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);}
-          : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
-        while (start > 0 && check(line.charAt(start - 1))) --start;
-        while (end < line.length && check(line.charAt(end))) ++end;
-      }
-      return new Range(Pos(pos.line, start), Pos(pos.line, end));
-    },
-
-    toggleOverwrite: function(value) {
-      if (value != null && value == this.state.overwrite) return;
-      if (this.state.overwrite = !this.state.overwrite)
-        addClass(this.display.cursorDiv, "CodeMirror-overwrite");
-      else
-        rmClass(this.display.cursorDiv, "CodeMirror-overwrite");
-
-      signal(this, "overwriteToggle", this, this.state.overwrite);
-    },
-    hasFocus: function() { return activeElt() == this.display.input; },
-
-    scrollTo: methodOp(function(x, y) {
-      if (x != null || y != null) resolveScrollToPos(this);
-      if (x != null) this.curOp.scrollLeft = x;
-      if (y != null) this.curOp.scrollTop = y;
-    }),
-    getScrollInfo: function() {
-      var scroller = this.display.scroller, co = scrollerCutOff;
-      return {left: scroller.scrollLeft, top: scroller.scrollTop,
-              height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
-              clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
-    },
-
-    scrollIntoView: methodOp(function(range, margin) {
-      if (range == null) {
-        range = {from: this.doc.sel.primary().head, to: null};
-        if (margin == null) margin = this.options.cursorScrollMargin;
-      } else if (typeof range == "number") {
-        range = {from: Pos(range, 0), to: null};
-      } else if (range.from == null) {
-        range = {from: range, to: null};
-      }
-      if (!range.to) range.to = range.from;
-      range.margin = margin || 0;
-
-      if (range.from.line != null) {
-        resolveScrollToPos(this);
-        this.curOp.scrollToPos = range;
-      } else {
-        var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left),
-                                      Math.min(range.from.top, range.to.top) - range.margin,
-                                      Math.max(range.from.right, range.to.right),
-                                      Math.max(range.from.bottom, range.to.bottom) + range.margin);
-        this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
-      }
-    }),
-
-    setSize: methodOp(function(width, height) {
-      var cm = this;
-      function interpret(val) {
-        return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
-      }
-      if (width != null) cm.display.wrapper.style.width = interpret(width);
-      if (height != null) cm.display.wrapper.style.height = interpret(height);
-      if (cm.options.lineWrapping) clearLineMeasurementCache(this);
-      var lineNo = cm.display.viewFrom;
-      cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
-        if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
-          if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
-        ++lineNo;
-      });
-      cm.curOp.forceUpdate = true;
-      signal(cm, "refresh", this);
-    }),
-
-    operation: function(f){return runInOp(this, f);},
-
-    refresh: methodOp(function() {
-      var oldHeight = this.display.cachedTextHeight;
-      regChange(this);
-      this.curOp.forceUpdate = true;
-      clearCaches(this);
-      this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
-      updateGutterSpace(this);
-      if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
-        estimateLineHeights(this);
-      signal(this, "refresh", this);
-    }),
-
-    swapDoc: methodOp(function(doc) {
-      var old = this.doc;
-      old.cm = null;
-      attachDoc(this, doc);
-      clearCaches(this);
-      resetInput(this);
-      this.scrollTo(doc.scrollLeft, doc.scrollTop);
-      this.curOp.forceScroll = true;
-      signalLater(this, "swapDoc", this, old);
-      return old;
-    }),
-
-    getInputField: function(){return this.display.input;},
-    getWrapperElement: function(){return this.display.wrapper;},
-    getScrollerElement: function(){return this.display.scroller;},
-    getGutterElement: function(){return this.display.gutters;}
-  };
-  eventMixin(CodeMirror);
-
-  // OPTION DEFAULTS
-
-  // The default configuration options.
-  var defaults = CodeMirror.defaults = {};
-  // Functions to run when options are changed.
-  var optionHandlers = CodeMirror.optionHandlers = {};
-
-  function option(name, deflt, handle, notOnInit) {
-    CodeMirror.defaults[name] = deflt;
-    if (handle) optionHandlers[name] =
-      notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
-  }
-
-  // Passed to option handlers when there is no old value.
-  var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
-
-  // These two are, on init, called from the constructor because they
-  // have to be initialized before the editor can start at all.
-  option("value", "", function(cm, val) {
-    cm.setValue(val);
-  }, true);
-  option("mode", null, function(cm, val) {
-    cm.doc.modeOption = val;
-    loadMode(cm);
-  }, true);
-
-  option("indentUnit", 2, loadMode, true);
-  option("indentWithTabs", false);
-  option("smartIndent", true);
-  option("tabSize", 4, function(cm) {
-    resetModeState(cm);
-    clearCaches(cm);
-    regChange(cm);
-  }, true);
-  option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
-    cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
-    cm.refresh();
-  }, true);
-  option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true);
-  option("electricChars", true);
-  option("rtlMoveVisually", !windows);
-  option("wholeLineUpdateBefore", true);
-
-  option("theme", "default", function(cm) {
-    themeChanged(cm);
-    guttersChanged(cm);
-  }, true);
-  option("keyMap", "default", keyMapChanged);
-  option("extraKeys", null);
-
-  option("lineWrapping", false, wrappingChanged, true);
-  option("gutters", [], function(cm) {
-    setGuttersForLineNumbers(cm.options);
-    guttersChanged(cm);
-  }, true);
-  option("fixedGutter", true, function(cm, val) {
-    cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
-    cm.refresh();
-  }, true);
-  option("coverGutterNextToScrollbar", false, updateScrollbars, true);
-  option("lineNumbers", false, function(cm) {
-    setGuttersForLineNumbers(cm.options);
-    guttersChanged(cm);
-  }, true);
-  option("firstLineNumber", 1, guttersChanged, true);
-  option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
-  option("showCursorWhenSelecting", false, updateSelection, true);
-
-  option("resetSelectionOnContextMenu", true);
-
-  option("readOnly", false, function(cm, val) {
-    if (val == "nocursor") {
-      onBlur(cm);
-      cm.display.input.blur();
-      cm.display.disabled = true;
-    } else {
-      cm.display.disabled = false;
-      if (!val) resetInput(cm);
-    }
-  });
-  option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
-  option("dragDrop", true);
-
-  option("cursorBlinkRate", 530);
-  option("cursorScrollMargin", 0);
-  option("cursorHeight", 1, updateSelection, true);
-  option("singleCursorHeightPerLine", true, updateSelection, true);
-  option("workTime", 100);
-  option("workDelay", 100);
-  option("flattenSpans", true, resetModeState, true);
-  option("addModeClass", false, resetModeState, true);
-  option("pollInterval", 100);
-  option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;});
-  option("historyEventDelay", 1250);
-  option("viewportMargin", 10, function(cm){cm.refresh();}, true);
-  option("maxHighlightLength", 10000, resetModeState, true);
-  option("moveInputWithCursor", true, function(cm, val) {
-    if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
-  });
-
-  option("tabindex", null, function(cm, val) {
-    cm.display.input.tabIndex = val || "";
-  });
-  option("autofocus", null);
-
-  // MODE DEFINITION AND QUERYING
-
-  // Known modes, by name and by MIME
-  var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
-
-  // Extra arguments are stored as the mode's dependencies, which is
-  // used by (legacy) mechanisms like loadmode.js to automatically
-  // load a mode. (Preferred mechanism is the require/define calls.)
-  CodeMirror.defineMode = function(name, mode) {
-    if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
-    if (arguments.length > 2)
-      mode.dependencies = Array.prototype.slice.call(arguments, 2);
-    modes[name] = mode;
-  };
-
-  CodeMirror.defineMIME = function(mime, spec) {
-    mimeModes[mime] = spec;
-  };
-
-  // Given a MIME type, a {name, ...options} config object, or a name
-  // string, return a mode config object.
-  CodeMirror.resolveMode = function(spec) {
-    if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
-      spec = mimeModes[spec];
-    } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
-      var found = mimeModes[spec.name];
-      if (typeof found == "string") found = {name: found};
-      spec = createObj(found, spec);
-      spec.name = found.name;
-    } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
-      return CodeMirror.resolveMode("application/xml");
-    }
-    if (typeof spec == "string") return {name: spec};
-    else return spec || {name: "null"};
-  };
-
-  // Given a mode spec (anything that resolveMode accepts), find and
-  // initialize an actual mode object.
-  CodeMirror.getMode = function(options, spec) {
-    var spec = CodeMirror.resolveMode(spec);
-    var mfactory = modes[spec.name];
-    if (!mfactory) return CodeMirror.getMode(options, "text/plain");
-    var modeObj = mfactory(options, spec);
-    if (modeExtensions.hasOwnProperty(spec.name)) {
-      var exts = modeExtensions[spec.name];
-      for (var prop in exts) {
-        if (!exts.hasOwnProperty(prop)) continue;
-        if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
-        modeObj[prop] = exts[prop];
-      }
-    }
-    modeObj.name = spec.name;
-    if (spec.helperType) modeObj.helperType = spec.helperType;
-    if (spec.modeProps) for (var prop in spec.modeProps)
-      modeObj[prop] = spec.modeProps[prop];
-
-    return modeObj;
-  };
-
-  // Minimal default mode.
-  CodeMirror.defineMode("null", function() {
-    return {token: function(stream) {stream.skipToEnd();}};
-  });
-  CodeMirror.defineMIME("text/plain", "null");
-
-  // This can be used to attach properties to mode objects from
-  // outside the actual mode definition.
-  var modeExtensions = CodeMirror.modeExtensions = {};
-  CodeMirror.extendMode = function(mode, properties) {
-    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
-    copyObj(properties, exts);
-  };
-
-  // EXTENSIONS
-
-  CodeMirror.defineExtension = function(name, func) {
-    CodeMirror.prototype[name] = func;
-  };
-  CodeMirror.defineDocExtension = function(name, func) {
-    Doc.prototype[name] = func;
-  };
-  CodeMirror.defineOption = option;
-
-  var initHooks = [];
-  CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
-
-  var helpers = CodeMirror.helpers = {};
-  CodeMirror.registerHelper = function(type, name, value) {
-    if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []};
-    helpers[type][name] = value;
-  };
-  CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {
-    CodeMirror.registerHelper(type, name, value);
-    helpers[type]._global.push({pred: predicate, val: value});
-  };
-
-  // MODE STATE HANDLING
-
-  // Utility functions for working with state. Exported because nested
-  // modes need to do this for their inner modes.
-
-  var copyState = CodeMirror.copyState = function(mode, state) {
-    if (state === true) return state;
-    if (mode.copyState) return mode.copyState(state);
-    var nstate = {};
-    for (var n in state) {
-      var val = state[n];
-      if (val instanceof Array) val = val.concat([]);
-      nstate[n] = val;
-    }
-    return nstate;
-  };
-
-  var startState = CodeMirror.startState = function(mode, a1, a2) {
-    return mode.startState ? mode.startState(a1, a2) : true;
-  };
-
-  // Given a mode and a state (for that mode), find the inner mode and
-  // state at the position that the state refers to.
-  CodeMirror.innerMode = function(mode, state) {
-    while (mode.innerMode) {
-      var info = mode.innerMode(state);
-      if (!info || info.mode == mode) break;
-      state = info.state;
-      mode = info.mode;
-    }
-    return info || {mode: mode, state: state};
-  };
-
-  // STANDARD COMMANDS
-
-  // Commands are parameter-less actions that can be performed on an
-  // editor, mostly used for keybindings.
-  var commands = CodeMirror.commands = {
-    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
-    singleSelection: function(cm) {
-      cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
-    },
-    killLine: function(cm) {
-      deleteNearSelection(cm, function(range) {
-        if (range.empty()) {
-          var len = getLine(cm.doc, range.head.line).text.length;
-          if (range.head.ch == len && range.head.line < cm.lastLine())
-            return {from: range.head, to: Pos(range.head.line + 1, 0)};
-          else
-            return {from: range.head, to: Pos(range.head.line, len)};
-        } else {
-          return {from: range.from(), to: range.to()};
-        }
-      });
-    },
-    deleteLine: function(cm) {
-      deleteNearSelection(cm, function(range) {
-        return {from: Pos(range.from().line, 0),
-                to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
-      });
-    },
-    delLineLeft: function(cm) {
-      deleteNearSelection(cm, function(range) {
-        return {from: Pos(range.from().line, 0), to: range.from()};
-      });
-    },
-    delWrappedLineLeft: function(cm) {
-      deleteNearSelection(cm, function(range) {
-        var top = cm.charCoords(range.head, "div").top + 5;
-        var leftPos = cm.coordsChar({left: 0, top: top}, "div");
-        return {from: leftPos, to: range.from()};
-      });
-    },
-    delWrappedLineRight: function(cm) {
-      deleteNearSelection(cm, function(range) {
-        var top = cm.charCoords(range.head, "div").top + 5;
-        var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
-        return {from: range.from(), to: rightPos };
-      });
-    },
-    undo: function(cm) {cm.undo();},
-    redo: function(cm) {cm.redo();},
-    undoSelection: function(cm) {cm.undoSelection();},
-    redoSelection: function(cm) {cm.redoSelection();},
-    goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
-    goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
-    goLineStart: function(cm) {
-      cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); },
-                            {origin: "+move", bias: 1});
-    },
-    goLineStartSmart: function(cm) {
-      cm.extendSelectionsBy(function(range) {
-        return lineStartSmart(cm, range.head);
-      }, {origin: "+move", bias: 1});
-    },
-    goLineEnd: function(cm) {
-      cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); },
-                            {origin: "+move", bias: -1});
-    },
-    goLineRight: function(cm) {
-      cm.extendSelectionsBy(function(range) {
-        var top = cm.charCoords(range.head, "div").top + 5;
-        return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
-      }, sel_move);
-    },
-    goLineLeft: function(cm) {
-      cm.extendSelectionsBy(function(range) {
-        var top = cm.charCoords(range.head, "div").top + 5;
-        return cm.coordsChar({left: 0, top: top}, "div");
-      }, sel_move);
-    },
-    goLineLeftSmart: function(cm) {
-      cm.extendSelectionsBy(function(range) {
-        var top = cm.charCoords(range.head, "div").top + 5;
-        var pos = cm.coordsChar({left: 0, top: top}, "div");
-        if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head);
-        return pos;
-      }, sel_move);
-    },
-    goLineUp: function(cm) {cm.moveV(-1, "line");},
-    goLineDown: function(cm) {cm.moveV(1, "line");},
-    goPageUp: function(cm) {cm.moveV(-1, "page");},
-    goPageDown: function(cm) {cm.moveV(1, "page");},
-    goCharLeft: function(cm) {cm.moveH(-1, "char");},
-    goCharRight: function(cm) {cm.moveH(1, "char");},
-    goColumnLeft: function(cm) {cm.moveH(-1, "column");},
-    goColumnRight: function(cm) {cm.moveH(1, "column");},
-    goWordLeft: function(cm) {cm.moveH(-1, "word");},
-    goGroupRight: function(cm) {cm.moveH(1, "group");},
-    goGroupLeft: function(cm) {cm.moveH(-1, "group");},
-    goWordRight: function(cm) {cm.moveH(1, "word");},
-    delCharBefore: function(cm) {cm.deleteH(-1, "char");},
-    delCharAfter: function(cm) {cm.deleteH(1, "char");},
-    delWordBefore: function(cm) {cm.deleteH(-1, "word");},
-    delWordAfter: function(cm) {cm.deleteH(1, "word");},
-    delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
-    delGroupAfter: function(cm) {cm.deleteH(1, "group");},
-    indentAuto: function(cm) {cm.indentSelection("smart");},
-    indentMore: function(cm) {cm.indentSelection("add");},
-    indentLess: function(cm) {cm.indentSelection("subtract");},
-    insertTab: function(cm) {cm.replaceSelection("\t");},
-    insertSoftTab: function(cm) {
-      var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;
-      for (var i = 0; i < ranges.length; i++) {
-        var pos = ranges[i].from();
-        var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);
-        spaces.push(new Array(tabSize - col % tabSize + 1).join(" "));
-      }
-      cm.replaceSelections(spaces);
-    },
-    defaultTab: function(cm) {
-      if (cm.somethingSelected()) cm.indentSelection("add");
-      else cm.execCommand("insertTab");
-    },
-    transposeChars: function(cm) {
-      runInOp(cm, function() {
-        var ranges = cm.listSelections(), newSel = [];
-        for (var i = 0; i < ranges.length; i++) {
-          var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
-          if (line) {
-            if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1);
-            if (cur.ch > 0) {
-              cur = new Pos(cur.line, cur.ch + 1);
-              cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),
-                              Pos(cur.line, cur.ch - 2), cur, "+transpose");
-            } else if (cur.line > cm.doc.first) {
-              var prev = getLine(cm.doc, cur.line - 1).text;
-              if (prev)
-                cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
-                                Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
-            }
-          }
-          newSel.push(new Range(cur, cur));
-        }
-        cm.setSelections(newSel);
-      });
-    },
-    newlineAndIndent: function(cm) {
-      runInOp(cm, function() {
-        var len = cm.listSelections().length;
-        for (var i = 0; i < len; i++) {
-          var range = cm.listSelections()[i];
-          cm.replaceRange("\n", range.anchor, range.head, "+input");
-          cm.indentLine(range.from().line + 1, null, true);
-          ensureCursorVisible(cm);
-        }
-      });
-    },
-    toggleOverwrite: function(cm) {cm.toggleOverwrite();}
-  };
-
-  // STANDARD KEYMAPS
-
-  var keyMap = CodeMirror.keyMap = {};
-  keyMap.basic = {
-    "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
-    "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
-    "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
-    "Tab": "defaultTab", "Shift-Tab": "indentAuto",
-    "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
-    "Esc": "singleSelection"
-  };
-  // Note that the save and find-related commands aren't defined by
-  // default. User code or addons can define them. Unknown commands
-  // are simply ignored.
-  keyMap.pcDefault = {
-    "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
-    "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown",
-    "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
-    "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
-    "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
-    "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
-    "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
-    fallthrough: "basic"
-  };
-  keyMap.macDefault = {
-    "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
-    "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
-    "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
-    "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
-    "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
-    "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
-    "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
-    fallthrough: ["basic", "emacsy"]
-  };
-  // Very basic readline/emacs-style bindings, which are standard on Mac.
-  keyMap.emacsy = {
-    "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
-    "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
-    "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
-    "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
-  };
-  keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
-
-  // KEYMAP DISPATCH
-
-  function getKeyMap(val) {
-    if (typeof val == "string") return keyMap[val];
-    else return val;
-  }
-
-  // Given an array of keymaps and a key name, call handle on any
-  // bindings found, until that returns a truthy value, at which point
-  // we consider the key handled. Implements things like binding a key
-  // to false stopping further handling and keymap fallthrough.
-  var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) {
-    function lookup(map) {
-      map = getKeyMap(map);
-      var found = map[name];
-      if (found === false) return "stop";
-      if (found != null && handle(found)) return true;
-      if (map.nofallthrough) return "stop";
-
-      var fallthrough = map.fallthrough;
-      if (fallthrough == null) return false;
-      if (Object.prototype.toString.call(fallthrough) != "[object Array]")
-        return lookup(fallthrough);
-      for (var i = 0; i < fallthrough.length; ++i) {
-        var done = lookup(fallthrough[i]);
-        if (done) return done;
-      }
-      return false;
-    }
-
-    for (var i = 0; i < maps.length; ++i) {
-      var done = lookup(maps[i]);
-      if (done) return done != "stop";
-    }
-  };
-
-  // Modifier key presses don't count as 'real' key presses for the
-  // purpose of keymap fallthrough.
-  var isModifierKey = CodeMirror.isModifierKey = function(event) {
-    var name = keyNames[event.keyCode];
-    return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
-  };
-
-  // Look up the name of a key as indicated by an event object.
-  var keyName = CodeMirror.keyName = function(event, noShift) {
-    if (presto && event.keyCode == 34 && event["char"]) return false;
-    var name = keyNames[event.keyCode];
-    if (name == null || event.altGraphKey) return false;
-    if (event.altKey) name = "Alt-" + name;
-    if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
-    if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
-    if (!noShift && event.shiftKey) name = "Shift-" + name;
-    return name;
-  };
-
-  // FROMTEXTAREA
-
-  CodeMirror.fromTextArea = function(textarea, options) {
-    if (!options) options = {};
-    options.value = textarea.value;
-    if (!options.tabindex && textarea.tabindex)
-      options.tabindex = textarea.tabindex;
-    if (!options.placeholder && textarea.placeholder)
-      options.placeholder = textarea.placeholder;
-    // Set autofocus to true if this textarea is focused, or if it has
-    // autofocus and no other element is focused.
-    if (options.autofocus == null) {
-      var hasFocus = activeElt();
-      options.autofocus = hasFocus == textarea ||
-        textarea.getAttribute("autofocus") != null && hasFocus == document.body;
-    }
-
-    function save() {textarea.value = cm.getValue();}
-    if (textarea.form) {
-      on(textarea.form, "submit", save);
-      // Deplorable hack to make the submit method do the right thing.
-      if (!options.leaveSubmitMethodAlone) {
-        var form = textarea.form, realSubmit = form.submit;
-        try {
-          var wrappedSubmit = form.submit = function() {
-            save();
-            form.submit = realSubmit;
-            form.submit();
-            form.submit = wrappedSubmit;
-          };
-        } catch(e) {}
-      }
-    }
-
-    textarea.style.display = "none";
-    var cm = CodeMirror(function(node) {
-      textarea.parentNode.insertBefore(node, textarea.nextSibling);
-    }, options);
-    cm.save = save;
-    cm.getTextArea = function() { return textarea; };
-    cm.toTextArea = function() {
-      cm.toTextArea = isNaN; // Prevent this from being ran twice
-      save();
-      textarea.parentNode.removeChild(cm.getWrapperElement());
-      textarea.style.display = "";
-      if (textarea.form) {
-        off(textarea.form, "submit", save);
-        if (typeof textarea.form.submit == "function")
-          textarea.form.submit = realSubmit;
-      }
-    };
-    return cm;
-  };
-
-  // STRING STREAM
-
-  // Fed to the mode parsers, provides helper functions to make
-  // parsers more succinct.
-
-  var StringStream = CodeMirror.StringStream = function(string, tabSize) {
-    this.pos = this.start = 0;
-    this.string = string;
-    this.tabSize = tabSize || 8;
-    this.lastColumnPos = this.lastColumnValue = 0;
-    this.lineStart = 0;
-  };
-
-  StringStream.prototype = {
-    eol: function() {return this.pos >= this.string.length;},
-    sol: function() {return this.pos == this.lineStart;},
-    peek: function() {return this.string.charAt(this.pos) || undefined;},
-    next: function() {
-      if (this.pos < this.string.length)
-        return this.string.charAt(this.pos++);
-    },
-    eat: function(match) {
-      var ch = this.string.charAt(this.pos);
-      if (typeof match == "string") var ok = ch == match;
-      else var ok = ch && (match.test ? match.test(ch) : match(ch));
-      if (ok) {++this.pos; return ch;}
-    },
-    eatWhile: function(match) {
-      var start = this.pos;
-      while (this.eat(match)){}
-      return this.pos > start;
-    },
-    eatSpace: function() {
-      var start = this.pos;
-      while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
-      return this.pos > start;
-    },
-    skipToEnd: function() {this.pos = this.string.length;},
-    skipTo: function(ch) {
-      var found = this.string.indexOf(ch, this.pos);
-      if (found > -1) {this.pos = found; return true;}
-    },
-    backUp: function(n) {this.pos -= n;},
-    column: function() {
-      if (this.lastColumnPos < this.start) {
-        this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);
-        this.lastColumnPos = this.start;
-      }
-      return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
-    },
-    indentation: function() {
-      return countColumn(this.string, null, this.tabSize) -
-        (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
-    },
-    match: function(pattern, consume, caseInsensitive) {
-      if (typeof pattern == "string") {
-        var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
-        var substr = this.string.substr(this.pos, pattern.length);
-        if (cased(substr) == cased(pattern)) {
-          if (consume !== false) this.pos += pattern.length;
-          return true;
-        }
-      } else {
-        var match = this.string.slice(this.pos).match(pattern);
-        if (match && match.index > 0) return null;
-        if (match && consume !== false) this.pos += match[0].length;
-        return match;
-      }
-    },
-    current: function(){return this.string.slice(this.start, this.pos);},
-    hideFirstChars: function(n, inner) {
-      this.lineStart += n;
-      try { return inner(); }
-      finally { this.lineStart -= n; }
-    }
-  };
-
-  // TEXTMARKERS
-
-  // Created with markText and setBookmark methods. A TextMarker is a
-  // handle that can be used to clear or find a marked position in the
-  // document. Line objects hold arrays (markedSpans) containing
-  // {from, to, marker} object pointing to such marker objects, and
-  // indicating that such a marker is present on that line. Multiple
-  // lines may point to the same marker when it spans across lines.
-  // The spans will have null for their from/to properties when the
-  // marker continues beyond the start/end of the line. Markers have
-  // links back to the lines they currently touch.
-
-  var TextMarker = CodeMirror.TextMarker = function(doc, type) {
-    this.lines = [];
-    this.type = type;
-    this.doc = doc;
-  };
-  eventMixin(TextMarker);
-
-  // Clear the marker.
-  TextMarker.prototype.clear = function() {
-    if (this.explicitlyCleared) return;
-    var cm = this.doc.cm, withOp = cm && !cm.curOp;
-    if (withOp) startOperation(cm);
-    if (hasHandler(this, "clear")) {
-      var found = this.find();
-      if (found) signalLater(this, "clear", found.from, found.to);
-    }
-    var min = null, max = null;
-    for (var i = 0; i < this.lines.length; ++i) {
-      var line = this.lines[i];
-      var span = getMarkedSpanFor(line.markedSpans, this);
-      if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text");
-      else if (cm) {
-        if (span.to != null) max = lineNo(line);
-        if (span.from != null) min = lineNo(line);
-      }
-      line.markedSpans = removeMarkedSpan(line.markedSpans, span);
-      if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)
-        updateLineHeight(line, textHeight(cm.display));
-    }
-    if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
-      var visual = visualLine(this.lines[i]), len = lineLength(visual);
-      if (len > cm.display.maxLineLength) {
-        cm.display.maxLine = visual;
-        cm.display.maxLineLength = len;
-        cm.display.maxLineChanged = true;
-      }
-    }
-
-    if (min != null && cm && this.collapsed) regChange(cm, min, max + 1);
-    this.lines.length = 0;
-    this.explicitlyCleared = true;
-    if (this.atomic && this.doc.cantEdit) {
-      this.doc.cantEdit = false;
-      if (cm) reCheckSelection(cm.doc);
-    }
-    if (cm) signalLater(cm, "markerCleared", cm, this);
-    if (withOp) endOperation(cm);
-    if (this.parent) this.parent.clear();
-  };
-
-  // Find the position of the marker in the document. Returns a {from,
-  // to} object by default. Side can be passed to get a specific side
-  // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
-  // Pos objects returned contain a line object, rather than a line
-  // number (used to prevent looking up the same line twice).
-  TextMarker.prototype.find = function(side, lineObj) {
-    if (side == null && this.type == "bookmark") side = 1;
-    var from, to;
-    for (var i = 0; i < this.lines.length; ++i) {
-      var line = this.lines[i];
-      var span = getMarkedSpanFor(line.markedSpans, this);
-      if (span.from != null) {
-        from = Pos(lineObj ? line : lineNo(line), span.from);
-        if (side == -1) return from;
-      }
-      if (span.to != null) {
-        to = Pos(lineObj ? line : lineNo(line), span.to);
-        if (side == 1) return to;
-      }
-    }
-    return from && {from: from, to: to};
-  };
-
-  // Signals that the marker's widget changed, and surrounding layout
-  // should be recomputed.
-  TextMarker.prototype.changed = function() {
-    var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
-    if (!pos || !cm) return;
-    runInOp(cm, function() {
-      var line = pos.line, lineN = lineNo(pos.line);
-      var view = findViewForLine(cm, lineN);
-      if (view) {
-        clearLineMeasurementCacheFor(view);
-        cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
-      }
-      cm.curOp.updateMaxLine = true;
-      if (!lineIsHidden(widget.doc, line) && widget.height != null) {
-        var oldHeight = widget.height;
-        widget.height = null;
-        var dHeight = widgetHeight(widget) - oldHeight;
-        if (dHeight)
-          updateLineHeight(line, line.height + dHeight);
-      }
-    });
-  };
-
-  TextMarker.prototype.attachLine = function(line) {
-    if (!this.lines.length && this.doc.cm) {
-      var op = this.doc.cm.curOp;
-      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
-        (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
-    }
-    this.lines.push(line);
-  };
-  TextMarker.prototype.detachLine = function(line) {
-    this.lines.splice(indexOf(this.lines, line), 1);
-    if (!this.lines.length && this.doc.cm) {
-      var op = this.doc.cm.curOp;
-      (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
-    }
-  };
-
-  // Collapsed markers have unique ids, in order to be able to order
-  // them, which is needed for uniquely determining an outer marker
-  // when they overlap (they may nest, but not partially overlap).
-  var nextMarkerId = 0;
-
-  // Create a marker, wire it up to the right lines, and
-  function markText(doc, from, to, options, type) {
-    // Shared markers (across linked documents) are handled separately
-    // (markTextShared will call out to this again, once per
-    // document).
-    if (options && options.shared) return markTextShared(doc, from, to, options, type);
-    // Ensure we are in an operation.
-    if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
-
-    var marker = new TextMarker(doc, type), diff = cmp(from, to);
-    if (options) copyObj(options, marker, false);
-    // Don't connect empty markers unless clearWhenEmpty is false
-    if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
-      return marker;
-    if (marker.replacedWith) {
-      // Showing up as a widget implies collapsed (widget replaces text)
-      marker.collapsed = true;
-      marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
-      if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true;
-      if (options.insertLeft) marker.widgetNode.insertLeft = true;
-    }
-    if (marker.collapsed) {
-      if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
-          from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))
-        throw new Error("Inserting collapsed marker partially overlapping an existing one");
-      sawCollapsedSpans = true;
-    }
-
-    if (marker.addToHistory)
-      addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
-
-    var curLine = from.line, cm = doc.cm, updateMaxLine;
-    doc.iter(curLine, to.line + 1, function(line) {
-      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
-        updateMaxLine = true;
-      if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
-      addMarkedSpan(line, new MarkedSpan(marker,
-                                         curLine == from.line ? from.ch : null,
-                                         curLine == to.line ? to.ch : null));
-      ++curLine;
-    });
-    // lineIsHidden depends on the presence of the spans, so needs a second pass
-    if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
-      if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
-    });
-
-    if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
-
-    if (marker.readOnly) {
-      sawReadOnlySpans = true;
-      if (doc.history.done.length || doc.history.undone.length)
-        doc.clearHistory();
-    }
-    if (marker.collapsed) {
-      marker.id = ++nextMarkerId;
-      marker.atomic = true;
-    }
-    if (cm) {
-      // Sync editor state
-      if (updateMaxLine) cm.curOp.updateMaxLine = true;
-      if (marker.collapsed)
-        regChange(cm, from.line, to.line + 1);
-      else if (marker.className || marker.title || marker.startStyle || marker.endStyle)
-        for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
-      if (marker.atomic) reCheckSelection(cm.doc);
-      signalLater(cm, "markerAdded", cm, marker);
-    }
-    return marker;
-  }
-
-  // SHARED TEXTMARKERS
-
-  // A shared marker spans multiple linked documents. It is
-  // implemented as a meta-marker-object controlling multiple normal
-  // markers.
-  var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
-    this.markers = markers;
-    this.primary = primary;
-    for (var i = 0; i < markers.length; ++i)
-      markers[i].parent = this;
-  };
-  eventMixin(SharedTextMarker);
-
-  SharedTextMarker.prototype.clear = function() {
-    if (this.explicitlyCleared) return;
-    this.explicitlyCleared = true;
-    for (var i = 0; i < this.markers.length; ++i)
-      this.markers[i].clear();
-    signalLater(this, "clear");
-  };
-  SharedTextMarker.prototype.find = function(side, lineObj) {
-    return this.primary.find(side, lineObj);
-  };
-
-  function markTextShared(doc, from, to, options, type) {
-    options = copyObj(options);
-    options.shared = false;
-    var markers = [markText(doc, from, to, options, type)], primary = markers[0];
-    var widget = options.widgetNode;
-    linkedDocs(doc, function(doc) {
-      if (widget) options.widgetNode = widget.cloneNode(true);
-      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
-      for (var i = 0; i < doc.linked.length; ++i)
-        if (doc.linked[i].isParent) return;
-      primary = lst(markers);
-    });
-    return new SharedTextMarker(markers, primary);
-  }
-
-  function findSharedMarkers(doc) {
-    return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
-                         function(m) { return m.parent; });
-  }
-
-  function copySharedMarkers(doc, markers) {
-    for (var i = 0; i < markers.length; i++) {
-      var marker = markers[i], pos = marker.find();
-      var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);
-      if (cmp(mFrom, mTo)) {
-        var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);
-        marker.markers.push(subMark);
-        subMark.parent = marker;
-      }
-    }
-  }
-
-  function detachSharedMarkers(markers) {
-    for (var i = 0; i < markers.length; i++) {
-      var marker = markers[i], linked = [marker.primary.doc];;
-      linkedDocs(marker.primary.doc, function(d) { linked.push(d); });
-      for (var j = 0; j < marker.markers.length; j++) {
-        var subMarker = marker.markers[j];
-        if (indexOf(linked, subMarker.doc) == -1) {
-          subMarker.parent = null;
-          marker.markers.splice(j--, 1);
-        }
-      }
-    }
-  }
-
-  // TEXTMARKER SPANS
-
-  function MarkedSpan(marker, from, to) {
-    this.marker = marker;
-    this.from = from; this.to = to;
-  }
-
-  // Search an array of spans for a span matching the given marker.
-  function getMarkedSpanFor(spans, marker) {
-    if (spans) for (var i = 0; i < spans.length; ++i) {
-      var span = spans[i];
-      if (span.marker == marker) return span;
-    }
-  }
-  // Remove a span from an array, returning undefined if no spans are
-  // left (we don't store arrays for lines without spans).
-  function removeMarkedSpan(spans, span) {
-    for (var r, i = 0; i < spans.length; ++i)
-      if (spans[i] != span) (r || (r = [])).push(spans[i]);
-    return r;
-  }
-  // Add a span to a line.
-  function addMarkedSpan(line, span) {
-    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
-    span.marker.attachLine(line);
-  }
-
-  // Used for the algorithm that adjusts markers for a change in the
-  // document. These functions cut an array of spans at a given
-  // character position, returning an array of remaining chunks (or
-  // undefined if nothing remains).
-  function markedSpansBefore(old, startCh, isInsert) {
-    if (old) for (var i = 0, nw; i < old.length; ++i) {
-      var span = old[i], marker = span.marker;
-      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
-      if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
-        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
-        (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
-      }
-    }
-    return nw;
-  }
-  function markedSpansAfter(old, endCh, isInsert) {
-    if (old) for (var i = 0, nw; i < old.length; ++i) {
-      var span = old[i], marker = span.marker;
-      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
-      if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
-        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
-        (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
-                                              span.to == null ? null : span.to - endCh));
-      }
-    }
-    return nw;
-  }
-
-  // Given a change object, compute the new set of marker spans that
-  // cover the line in which the change took place. Removes spans
-  // entirely within the change, reconnects spans belonging to the
-  // same marker that appear on both sides of the change, and cuts off
-  // spans partially within the change. Returns an array of span
-  // arrays with one element for each line in (after) the change.
-  function stretchSpansOverChange(doc, change) {
-    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
-    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
-    if (!oldFirst && !oldLast) return null;
-
-    var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;
-    // Get the spans that 'stick out' on both sides
-    var first = markedSpansBefore(oldFirst, startCh, isInsert);
-    var last = markedSpansAfter(oldLast, endCh, isInsert);
-
-    // Next, merge those two ends
-    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
-    if (first) {
-      // Fix up .to properties of first
-      for (var i = 0; i < first.length; ++i) {
-        var span = first[i];
-        if (span.to == null) {
-          var found = getMarkedSpanFor(last, span.marker);
-          if (!found) span.to = startCh;
-          else if (sameLine) span.to = found.to == null ? null : found.to + offset;
-        }
-      }
-    }
-    if (last) {
-      // Fix up .from in last (or move them into first in case of sameLine)
-      for (var i = 0; i < last.length; ++i) {
-        var span = last[i];
-        if (span.to != null) span.to += offset;
-        if (span.from == null) {
-          var found = getMarkedSpanFor(first, span.marker);
-          if (!found) {
-            span.from = offset;
-            if (sameLine) (first || (first = [])).push(span);
-          }
-        } else {
-          span.from += offset;
-          if (sameLine) (first || (first = [])).push(span);
-        }
-      }
-    }
-    // Make sure we didn't create any zero-length spans
-    if (first) first = clearEmptySpans(first);
-    if (last && last != first) last = clearEmptySpans(last);
-
-    var newMarkers = [first];
-    if (!sameLine) {
-      // Fill gap with whole-line-spans
-      var gap = change.text.length - 2, gapMarkers;
-      if (gap > 0 && first)
-        for (var i = 0; i < first.length; ++i)
-          if (first[i].to == null)
-            (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null));
-      for (var i = 0; i < gap; ++i)
-        newMarkers.push(gapMarkers);
-      newMarkers.push(last);
-    }
-    return newMarkers;
-  }
-
-  // Remove spans that are empty and don't have a clearWhenEmpty
-  // option of false.
-  function clearEmptySpans(spans) {
-    for (var i = 0; i < spans.length; ++i) {
-      var span = spans[i];
-      if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)
-        spans.splice(i--, 1);
-    }
-    if (!spans.length) return null;
-    return spans;
-  }
-
-  // Used for un/re-doing changes from the history. Combines the
-  // result of computing the existing spans with the set of spans that
-  // existed in the history (so that deleting around a span and then
-  // undoing brings back the span).
-  function mergeOldSpans(doc, change) {
-    var old = getOldSpans(doc, change);
-    var stretched = stretchSpansOverChange(doc, change);
-    if (!old) return stretched;
-    if (!stretched) return old;
-
-    for (var i = 0; i < old.length; ++i) {
-      var oldCur = old[i], stretchCur = stretched[i];
-      if (oldCur && stretchCur) {
-        spans: for (var j = 0; j < stretchCur.length; ++j) {
-          var span = stretchCur[j];
-          for (var k = 0; k < oldCur.length; ++k)
-            if (oldCur[k].marker == span.marker) continue spans;
-          oldCur.push(span);
-        }
-      } else if (stretchCur) {
-        old[i] = stretchCur;
-      }
-    }
-    return old;
-  }
-
-  // Used to 'clip' out readOnly ranges when making a change.
-  function removeReadOnlyRanges(doc, from, to) {
-    var markers = null;
-    doc.iter(from.line, to.line + 1, function(line) {
-      if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
-        var mark = line.markedSpans[i].marker;
-        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
-          (markers || (markers = [])).push(mark);
-      }
-    });
-    if (!markers) return null;
-    var parts = [{from: from, to: to}];
-    for (var i = 0; i < markers.length; ++i) {
-      var mk = markers[i], m = mk.find(0);
-      for (var j = 0; j < parts.length; ++j) {
-        var p = parts[j];
-        if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue;
-        var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
-        if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
-          newParts.push({from: p.from, to: m.from});
-        if (dto > 0 || !mk.inclusiveRight && !dto)
-          newParts.push({from: m.to, to: p.to});
-        parts.splice.apply(parts, newParts);
-        j += newParts.length - 1;
-      }
-    }
-    return parts;
-  }
-
-  // Connect or disconnect spans from a line.
-  function detachMarkedSpans(line) {
-    var spans = line.markedSpans;
-    if (!spans) return;
-    for (var i = 0; i < spans.length; ++i)
-      spans[i].marker.detachLine(line);
-    line.markedSpans = null;
-  }
-  function attachMarkedSpans(line, spans) {
-    if (!spans) return;
-    for (var i = 0; i < spans.length; ++i)
-      spans[i].marker.attachLine(line);
-    line.markedSpans = spans;
-  }
-
-  // Helpers used when computing which overlapping collapsed span
-  // counts as the larger one.
-  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
-  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
-
-  // Returns a number indicating which of two overlapping collapsed
-  // spans is larger (and thus includes the other). Falls back to
-  // comparing ids when the spans cover exactly the same range.
-  function compareCollapsedMarkers(a, b) {
-    var lenDiff = a.lines.length - b.lines.length;
-    if (lenDiff != 0) return lenDiff;
-    var aPos = a.find(), bPos = b.find();
-    var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);
-    if (fromCmp) return -fromCmp;
-    var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);
-    if (toCmp) return toCmp;
-    return b.id - a.id;
-  }
-
-  // Find out whether a line ends or starts in a collapsed span. If
-  // so, return the marker for that span.
-  function collapsedSpanAtSide(line, start) {
-    var sps = sawCollapsedSpans && line.markedSpans, found;
-    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
-      sp = sps[i];
-      if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&
-          (!found || compareCollapsedMarkers(found, sp.marker) < 0))
-        found = sp.marker;
-    }
-    return found;
-  }
-  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
-  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
-
-  // Test whether there exists a collapsed span that partially
-  // overlaps (covers the start or end, but not both) of a new span.
-  // Such overlap is not allowed.
-  function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
-    var line = getLine(doc, lineNo);
-    var sps = sawCollapsedSpans && line.markedSpans;
-    if (sps) for (var i = 0; i < sps.length; ++i) {
-      var sp = sps[i];
-      if (!sp.marker.collapsed) continue;
-      var found = sp.marker.find(0);
-      var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
-      var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
-      if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
-      if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
-          fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
-        return true;
-    }
-  }
-
-  // A visual line is a line as drawn on the screen. Folding, for
-  // example, can cause multiple logical lines to appear on the same
-  // visual line. This finds the start of the visual line that the
-  // given line is part of (usually that is the line itself).
-  function visualLine(line) {
-    var merged;
-    while (merged = collapsedSpanAtStart(line))
-      line = merged.find(-1, true).line;
-    return line;
-  }
-
-  // Returns an array of logical lines that continue the visual line
-  // started by the argument, or undefined if there are no such lines.
-  function visualLineContinued(line) {
-    var merged, lines;
-    while (merged = collapsedSpanAtEnd(line)) {
-      line = merged.find(1, true).line;
-      (lines || (lines = [])).push(line);
-    }
-    return lines;
-  }
-
-  // Get the line number of the start of the visual line that the
-  // given line number is part of.
-  function visualLineNo(doc, lineN) {
-    var line = getLine(doc, lineN), vis = visualLine(line);
-    if (line == vis) return lineN;
-    return lineNo(vis);
-  }
-  // Get the line number of the start of the next visual line after
-  // the given line.
-  function visualLineEndNo(doc, lineN) {
-    if (lineN > doc.lastLine()) return lineN;
-    var line = getLine(doc, lineN), merged;
-    if (!lineIsHidden(doc, line)) return lineN;
-    while (merged = collapsedSpanAtEnd(line))
-      line = merged.find(1, true).line;
-    return lineNo(line) + 1;
-  }
-
-  // Compute whether a line is hidden. Lines count as hidden when they
-  // are part of a visual line that starts with another line, or when
-  // they are entirely covered by collapsed, non-widget span.
-  function lineIsHidden(doc, line) {
-    var sps = sawCollapsedSpans && line.markedSpans;
-    if (sps) for (var sp, i = 0; i < sps.length; ++i) {
-      sp = sps[i];
-      if (!sp.marker.collapsed) continue;
-      if (sp.from == null) return true;
-      if (sp.marker.widgetNode) continue;
-      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
-        return true;
-    }
-  }
-  function lineIsHiddenInner(doc, line, span) {
-    if (span.to == null) {
-      var end = span.marker.find(1, true);
-      return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker));
-    }
-    if (span.marker.inclusiveRight && span.to == line.text.length)
-      return true;
-    for (var sp, i = 0; i < line.markedSpans.length; ++i) {
-      sp = line.markedSpans[i];
-      if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
-          (sp.to == null || sp.to != span.from) &&
-          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
-          lineIsHiddenInner(doc, line, sp)) return true;
-    }
-  }
-
-  // LINE WIDGETS
-
-  // Line widgets are block elements displayed above or below a line.
-
-  var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
-    if (options) for (var opt in options) if (options.hasOwnProperty(opt))
-      this[opt] = options[opt];
-    this.cm = cm;
-    this.node = node;
-  };
-  eventMixin(LineWidget);
-
-  function adjustScrollWhenAboveVisible(cm, line, diff) {
-    if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
-      addToScrollPos(cm, null, diff);
-  }
-
-  LineWidget.prototype.clear = function() {
-    var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
-    if (no == null || !ws) return;
-    for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
-    if (!ws.length) line.widgets = null;
-    var height = widgetHeight(this);
-    runInOp(cm, function() {
-      adjustScrollWhenAboveVisible(cm, line, -height);
-      regLineChange(cm, no, "widget");
-      updateLineHeight(line, Math.max(0, line.height - height));
-    });
-  };
-  LineWidget.prototype.changed = function() {
-    var oldH = this.height, cm = this.cm, line = this.line;
-    this.height = null;
-    var diff = widgetHeight(this) - oldH;
-    if (!diff) return;
-    runInOp(cm, function() {
-      cm.curOp.forceUpdate = true;
-      adjustScrollWhenAboveVisible(cm, line, diff);
-      updateLineHeight(line, line.height + diff);
-    });
-  };
-
-  function widgetHeight(widget) {
-    if (widget.height != null) return widget.height;
-    if (!contains(document.body, widget.node)) {
-      var parentStyle = "position: relative;";
-      if (widget.coverGutter)
-        parentStyle += "margin-left: -" + widget.cm.getGutterElement().offsetWidth + "px;";
-      removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
-    }
-    return widget.height = widget.node.offsetHeight;
-  }
-
-  function addLineWidget(cm, handle, node, options) {
-    var widget = new LineWidget(cm, node, options);
-    if (widget.noHScroll) cm.display.alignWidgets = true;
-    changeLine(cm.doc, handle, "widget", function(line) {
-      var widgets = line.widgets || (line.widgets = []);
-      if (widget.insertAt == null) widgets.push(widget);
-      else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
-      widget.line = line;
-      if (!lineIsHidden(cm.doc, line)) {
-        var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
-        updateLineHeight(line, line.height + widgetHeight(widget));
-        if (aboveVisible) addToScrollPos(cm, null, widget.height);
-        cm.curOp.forceUpdate = true;
-      }
-      return true;
-    });
-    return widget;
-  }
-
-  // LINE DATA STRUCTURE
-
-  // Line objects. These hold state related to a line, including
-  // highlighting info (the styles array).
-  var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
-    this.text = text;
-    attachMarkedSpans(this, markedSpans);
-    this.height = estimateHeight ? estimateHeight(this) : 1;
-  };
-  eventMixin(Line);
-  Line.prototype.lineNo = function() { return lineNo(this); };
-
-  // Change the content (text, markers) of a line. Automatically
-  // invalidates cached information and tries to re-estimate the
-  // line's height.
-  function updateLine(line, text, markedSpans, estimateHeight) {
-    line.text = text;
-    if (line.stateAfter) line.stateAfter = null;
-    if (line.styles) line.styles = null;
-    if (line.order != null) line.order = null;
-    detachMarkedSpans(line);
-    attachMarkedSpans(line, markedSpans);
-    var estHeight = estimateHeight ? estimateHeight(line) : 1;
-    if (estHeight != line.height) updateLineHeight(line, estHeight);
-  }
-
-  // Detach a line from the document tree and its markers.
-  function cleanUpLine(line) {
-    line.parent = null;
-    detachMarkedSpans(line);
-  }
-
-  function extractLineClasses(type, output) {
-    if (type) for (;;) {
-      var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/);
-      if (!lineClass) break;
-      type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);
-      var prop = lineClass[1] ? "bgClass" : "textClass";
-      if (output[prop] == null)
-        output[prop] = lineClass[2];
-      else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop]))
-        output[prop] += " " + lineClass[2];
-    }
-    return type;
-  }
-
-  function callBlankLine(mode, state) {
-    if (mode.blankLine) return mode.blankLine(state);
-    if (!mode.innerMode) return;
-    var inner = CodeMirror.innerMode(mode, state);
-    if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
-  }
-
-  function readToken(mode, stream, state) {
-    for (var i = 0; i < 10; i++) {
-      var style = mode.token(stream, state);
-      if (stream.pos > stream.start) return style;
-    }
-    throw new Error("Mode " + mode.name + " failed to advance stream.");
-  }
-
-  // Run the given mode's parser over a line, calling f for each token.
-  function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) {
-    var flattenSpans = mode.flattenSpans;
-    if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
-    var curStart = 0, curStyle = null;
-    var stream = new StringStream(text, cm.options.tabSize), style;
-    if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
-    while (!stream.eol()) {
-      if (stream.pos > cm.options.maxHighlightLength) {
-        flattenSpans = false;
-        if (forceToEnd) processLine(cm, text, state, stream.pos);
-        stream.pos = text.length;
-        style = null;
-      } else {
-        style = extractLineClasses(readToken(mode, stream, state), lineClasses);
-      }
-      if (cm.options.addModeClass) {
-        var mName = CodeMirror.innerMode(mode, state).mode.name;
-        if (mName) style = "m-" + (style ? mName + " " + style : mName);
-      }
-      if (!flattenSpans || curStyle != style) {
-        if (curStart < stream.start) f(stream.start, curStyle);
-        curStart = stream.start; curStyle = style;
-      }
-      stream.start = stream.pos;
-    }
-    while (curStart < stream.pos) {
-      // Webkit seems to refuse to render text nodes longer than 57444 characters
-      var pos = Math.min(stream.pos, curStart + 50000);
-      f(pos, curStyle);
-      curStart = pos;
-    }
-  }
-
-  // Compute a style array (an array starting with a mode generation
-  // -- for invalidation -- followed by pairs of end positions and
-  // style strings), which is used to highlight the tokens on the
-  // line.
-  function highlightLine(cm, line, state, forceToEnd) {
-    // A styles array always starts with a number identifying the
-    // mode/overlays that it is based on (for easy invalidation).
-    var st = [cm.state.modeGen], lineClasses = {};
-    // Compute the base array of styles
-    runMode(cm, line.text, cm.doc.mode, state, function(end, style) {
-      st.push(end, style);
-    }, lineClasses, forceToEnd);
-
-    // Run overlays, adjust style array.
-    for (var o = 0; o < cm.state.overlays.length; ++o) {
-      var overlay = cm.state.overlays[o], i = 1, at = 0;
-      runMode(cm, line.text, overlay.mode, true, function(end, style) {
-        var start = i;
-        // Ensure there's a token end at the current position, and that i points at it
-        while (at < end) {
-          var i_end = st[i];
-          if (i_end > end)
-            st.splice(i, 1, end, st[i+1], i_end);
-          i += 2;
-          at = Math.min(end, i_end);
-        }
-        if (!style) return;
-        if (overlay.opaque) {
-          st.splice(start, i - start, end, "cm-overlay " + style);
-          i = start + 2;
-        } else {
-          for (; start < i; start += 2) {
-            var cur = st[start+1];
-            st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
-          }
-        }
-      }, lineClasses);
-    }
-
-    return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
-  }
-
-  function getLineStyles(cm, line) {
-    if (!line.styles || line.styles[0] != cm.state.modeGen) {
-      var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
-      line.styles = result.styles;
-      if (result.classes) line.styleClasses = result.classes;
-      else if (line.styleClasses) line.styleClasses = null;
-    }
-    return line.styles;
-  }
-
-  // Lightweight form of highlight -- proceed over this line and
-  // update state, but don't save a style array. Used for lines that
-  // aren't currently visible.
-  function processLine(cm, text, state, startAt) {
-    var mode = cm.doc.mode;
-    var stream = new StringStream(text, cm.options.tabSize);
-    stream.start = stream.pos = startAt || 0;
-    if (text == "") callBlankLine(mode, state);
-    while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
-      readToken(mode, stream, state);
-      stream.start = stream.pos;
-    }
-  }
-
-  // Convert a style as returned by a mode (either null, or a string
-  // containing one or more styles) to a CSS style. This is cached,
-  // and also looks for line-wide styles.
-  var styleToClassCache = {}, styleToClassCacheWithMode = {};
-  function interpretTokenStyle(style, options) {
-    if (!style || /^\s*$/.test(style)) return null;
-    var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
-    return cache[style] ||
-      (cache[style] = style.replace(/\S+/g, "cm-$&"));
-  }
-
-  // Render the DOM representation of the text of a line. Also builds
-  // up a 'line map', which points at the DOM nodes that represent
-  // specific stretches of text, and is used by the measuring code.
-  // The returned object contains the DOM node, this map, and
-  // information about line-wide styles that were set by the mode.
-  function buildLineContent(cm, lineView) {
-    // The padding-right forces the element to have a 'border', which
-    // is needed on Webkit to be able to get line-level bounding
-    // rectangles for it (in measureChar).
-    var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
-    var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
-    lineView.measure = {};
-
-    // Iterate over the logical lines that make up this visual line.
-    for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
-      var line = i ? lineView.rest[i - 1] : lineView.line, order;
-      builder.pos = 0;
-      builder.addToken = buildToken;
-      // Optionally wire in some hacks into the token-rendering
-      // algorithm, to deal with browser quirks.
-      if ((ie || webkit) && cm.getOption("lineWrapping"))
-        builder.addToken = buildTokenSplitSpaces(builder.addToken);
-      if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
-        builder.addToken = buildTokenBadBidi(builder.addToken, order);
-      builder.map = [];
-      insertLineContent(line, builder, getLineStyles(cm, line));
-      if (line.styleClasses) {
-        if (line.styleClasses.bgClass)
-          builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "");
-        if (line.styleClasses.textClass)
-          builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "");
-      }
-
-      // Ensure at least a single node is present, for measuring.
-      if (builder.map.length == 0)
-        builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure)));
-
-      // Store the map and a cache object for the current logical line
-      if (i == 0) {
-        lineView.measure.map = builder.map;
-        lineView.measure.cache = {};
-      } else {
-        (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map);
-        (lineView.measure.caches || (lineView.measure.caches = [])).push({});
-      }
-    }
-
-    signal(cm, "renderLine", cm, lineView.line, builder.pre);
-    if (builder.pre.className)
-      builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
-    return builder;
-  }
-
-  function defaultSpecialCharPlaceholder(ch) {
-    var token = elt("span", "\u2022", "cm-invalidchar");
-    token.title = "\\u" + ch.charCodeAt(0).toString(16);
-    return token;
-  }
-
-  // Build up the DOM representation for a single token, and add it to
-  // the line map. Takes care to render special characters separately.
-  function buildToken(builder, text, style, startStyle, endStyle, title) {
-    if (!text) return;
-    var special = builder.cm.options.specialChars, mustWrap = false;
-    if (!special.test(text)) {
-      builder.col += text.length;
-      var content = document.createTextNode(text);
-      builder.map.push(builder.pos, builder.pos + text.length, content);
-      if (ie && ie_version < 9) mustWrap = true;
-      builder.pos += text.length;
-    } else {
-      var content = document.createDocumentFragment(), pos = 0;
-      while (true) {
-        special.lastIndex = pos;
-        var m = special.exec(text);
-        var skipped = m ? m.index - pos : text.length - pos;
-        if (skipped) {
-          var txt = document.createTextNode(text.slice(pos, pos + skipped));
-          if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
-          else content.appendChild(txt);
-          builder.map.push(builder.pos, builder.pos + skipped, txt);
-          builder.col += skipped;
-          builder.pos += skipped;
-        }
-        if (!m) break;
-        pos += skipped + 1;
-        if (m[0] == "\t") {
-          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
-          var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
-          builder.col += tabWidth;
-        } else {
-          var txt = builder.cm.options.specialCharPlaceholder(m[0]);
-          if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
-          else content.appendChild(txt);
-          builder.col += 1;
-        }
-        builder.map.push(builder.pos, builder.pos + 1, txt);
-        builder.pos++;
-      }
-    }
-    if (style || startStyle || endStyle || mustWrap) {
-      var fullStyle = style || "";
-      if (startStyle) fullStyle += startStyle;
-      if (endStyle) fullStyle += endStyle;
-      var token = elt("span", [content], fullStyle);
-      if (title) token.title = title;
-      return builder.content.appendChild(token);
-    }
-    builder.content.appendChild(content);
-  }
-
-  function buildTokenSplitSpaces(inner) {
-    function split(old) {
-      var out = " ";
-      for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
-      out += " ";
-      return out;
-    }
-    return function(builder, text, style, startStyle, endStyle, title) {
-      inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
-    };
-  }
-
-  // Work around nonsense dimensions being reported for stretches of
-  // right-to-left text.
-  function buildTokenBadBidi(inner, order) {
-    return function(builder, text, style, startStyle, endStyle, title) {
-      style = style ? style + " cm-force-border" : "cm-force-border";
-      var start = builder.pos, end = start + text.length;
-      for (;;) {
-        // Find the part that overlaps with the start of this text
-        for (var i = 0; i < order.length; i++) {
-          var part = order[i];
-          if (part.to > start && part.from <= start) break;
-        }
-        if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
-        inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
-        startStyle = null;
-        text = text.slice(part.to - start);
-        start = part.to;
-      }
-    };
-  }
-
-  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
-    var widget = !ignoreWidget && marker.widgetNode;
-    if (widget) {
-      builder.map.push(builder.pos, builder.pos + size, widget);
-      builder.content.appendChild(widget);
-    }
-    builder.pos += size;
-  }
-
-  // Outputs a number of spans to make up a line, taking highlighting
-  // and marked text into account.
-  function insertLineContent(line, builder, styles) {
-    var spans = line.markedSpans, allText = line.text, at = 0;
-    if (!spans) {
-      for (var i = 1; i < styles.length; i+=2)
-        builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options));
-      return;
-    }
-
-    var len = allText.length, pos = 0, i = 1, text = "", style;
-    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed;
-    for (;;) {
-      if (nextChange == pos) { // Update current marker set
-        spanStyle = spanEndStyle = spanStartStyle = title = "";
-        collapsed = null; nextChange = Infinity;
-        var foundBookmarks = [];
-        for (var j = 0; j < spans.length; ++j) {
-          var sp = spans[j], m = sp.marker;
-          if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
-            if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
-            if (m.className) spanStyle += " " + m.className;
-            if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
-            if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
-            if (m.title && !title) title = m.title;
-            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
-              collapsed = sp;
-          } else if (sp.from > pos && nextChange > sp.from) {
-            nextChange = sp.from;
-          }
-          if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
-        }
-        if (collapsed && (collapsed.from || 0) == pos) {
-          buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
-                             collapsed.marker, collapsed.from == null);
-          if (collapsed.to == null) return;
-        }
-        if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
-          buildCollapsedSpan(builder, 0, foundBookmarks[j]);
-      }
-      if (pos >= len) break;
-
-      var upto = Math.min(len, nextChange);
-      while (true) {
-        if (text) {
-          var end = pos + text.length;
-          if (!collapsed) {
-            var tokenText = end > upto ? text.slice(0, upto - pos) : text;
-            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
-                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title);
-          }
-          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
-          pos = end;
-          spanStartStyle = "";
-        }
-        text = allText.slice(at, at = styles[i++]);
-        style = interpretTokenStyle(styles[i++], builder.cm.options);
-      }
-    }
-  }
-
-  // DOCUMENT DATA STRUCTURE
-
-  // By default, updates that start and end at the beginning of a line
-  // are treated specially, in order to make the association of line
-  // widgets and marker elements with the text behave more intuitive.
-  function isWholeLineUpdate(doc, change) {
-    return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
-      (!doc.cm || doc.cm.options.wholeLineUpdateBefore);
-  }
-
-  // Perform a change on the document data structure.
-  function updateDoc(doc, change, markedSpans, estimateHeight) {
-    function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
-    function update(line, text, spans) {
-      updateLine(line, text, spans, estimateHeight);
-      signalLater(line, "change", line, change);
-    }
-
-    var from = change.from, to = change.to, text = change.text;
-    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
-    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
-
-    // Adjust the line structure
-    if (isWholeLineUpdate(doc, change)) {
-      // This is a whole-line replace. Treated specially to make
-      // sure line objects move the way they are supposed to.
-      for (var i = 0, added = []; i < text.length - 1; ++i)
-        added.push(new Line(text[i], spansFor(i), estimateHeight));
-      update(lastLine, lastLine.text, lastSpans);
-      if (nlines) doc.remove(from.line, nlines);
-      if (added.length) doc.insert(from.line, added);
-    } else if (firstLine == lastLine) {
-      if (text.length == 1) {
-        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
-      } else {
-        for (var added = [], i = 1; i < text.length - 1; ++i)
-          added.push(new Line(text[i], spansFor(i), estimateHeight));
-        added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
-        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
-        doc.insert(from.line + 1, added);
-      }
-    } else if (text.length == 1) {
-      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));
-      doc.remove(from.line + 1, nlines);
-    } else {
-      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
-      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
-      for (var i = 1, added = []; i < text.length - 1; ++i)
-        added.push(new Line(text[i], spansFor(i), estimateHeight));
-      if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
-      doc.insert(from.line + 1, added);
-    }
-
-    signalLater(doc, "change", doc, change);
-  }
-
-  // The document is represented as a BTree consisting of leaves, with
-  // chunk of lines in them, and branches, with up to ten leaves or
-  // other branch nodes below them. The top node is always a branch
-  // node, and is the document object itself (meaning it has
-  // additional methods and properties).
-  //
-  // All nodes have parent links. The tree is used both to go from
-  // line numbers to line objects, and to go from objects to numbers.
-  // It also indexes by height, and is used to convert between height
-  // and line object, and to find the total height of the document.
-  //
-  // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
-
-  function LeafChunk(lines) {
-    this.lines = lines;
-    this.parent = null;
-    for (var i = 0, height = 0; i < lines.length; ++i) {
-      lines[i].parent = this;
-      height += lines[i].height;
-    }
-    this.height = height;
-  }
-
-  LeafChunk.prototype = {
-    chunkSize: function() { return this.lines.length; },
-    // Remove the n lines at offset 'at'.
-    removeInner: function(at, n) {
-      for (var i = at, e = at + n; i < e; ++i) {
-        var line = this.lines[i];
-        this.height -= line.height;
-        cleanUpLine(line);
-        signalLater(line, "delete");
-      }
-      this.lines.splice(at, n);
-    },
-    // Helper used to collapse a small branch into a single leaf.
-    collapse: function(lines) {
-      lines.push.apply(lines, this.lines);
-    },
-    // Insert the given array of lines at offset 'at', count them as
-    // having the given height.
-    insertInner: function(at, lines, height) {
-      this.height += height;
-      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
-      for (var i = 0; i < lines.length; ++i) lines[i].parent = this;
-    },
-    // Used to iterate over a part of the tree.
-    iterN: function(at, n, op) {
-      for (var e = at + n; at < e; ++at)
-        if (op(this.lines[at])) return true;
-    }
-  };
-
-  function BranchChunk(children) {
-    this.children = children;
-    var size = 0, height = 0;
-    for (var i = 0; i < children.length; ++i) {
-      var ch = children[i];
-      size += ch.chunkSize(); height += ch.height;
-      ch.parent = this;
-    }
-    this.size = size;
-    this.height = height;
-    this.parent = null;
-  }
-
-  BranchChunk.prototype = {
-    chunkSize: function() { return this.size; },
-    removeInner: function(at, n) {
-      this.size -= n;
-      for (var i = 0; i < this.children.length; ++i) {
-        var child = this.children[i], sz = child.chunkSize();
-        if (at < sz) {
-          var rm = Math.min(n, sz - at), oldHeight = child.height;
-          child.removeInner(at, rm);
-          this.height -= oldHeight - child.height;
-          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
-          if ((n -= rm) == 0) break;
-          at = 0;
-        } else at -= sz;
-      }
-      // If the result is smaller than 25 lines, ensure that it is a
-      // single leaf node.
-      if (this.size - n < 25 &&
-          (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
-        var lines = [];
-        this.collapse(lines);
-        this.children = [new LeafChunk(lines)];
-        this.children[0].parent = this;
-      }
-    },
-    collapse: function(lines) {
-      for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines);
-    },
-    insertInner: function(at, lines, height) {
-      this.size += lines.length;
-      this.height += height;
-      for (var i = 0; i < this.children.length; ++i) {
-        var child = this.children[i], sz = child.chunkSize();
-        if (at <= sz) {
-          child.insertInner(at, lines, height);
-          if (child.lines && child.lines.length > 50) {
-            while (child.lines.length > 50) {
-              var spilled = child.lines.splice(child.lines.length - 25, 25);
-              var newleaf = new LeafChunk(spilled);
-              child.height -= newleaf.height;
-              this.children.splice(i + 1, 0, newleaf);
-              newleaf.parent = this;
-            }
-            this.maybeSpill();
-          }
-          break;
-        }
-        at -= sz;
-      }
-    },
-    // When a node has grown, check whether it should be split.
-    maybeSpill: function() {
-      if (this.children.length <= 10) return;
-      var me = this;
-      do {
-        var spilled = me.children.splice(me.children.length - 5, 5);
-        var sibling = new BranchChunk(spilled);
-        if (!me.parent) { // Become the parent node
-          var copy = new BranchChunk(me.children);
-          copy.parent = me;
-          me.children = [copy, sibling];
-          me = copy;
-        } else {
-          me.size -= sibling.size;
-          me.height -= sibling.height;
-          var myIndex = indexOf(me.parent.children, me);
-          me.parent.children.splice(myIndex + 1, 0, sibling);
-        }
-        sibling.parent = me.parent;
-      } while (me.children.length > 10);
-      me.parent.maybeSpill();
-    },
-    iterN: function(at, n, op) {
-      for (var i = 0; i < this.children.length; ++i) {
-        var child = this.children[i], sz = child.chunkSize();
-        if (at < sz) {
-          var used = Math.min(n, sz - at);
-          if (child.iterN(at, used, op)) return true;
-          if ((n -= used) == 0) break;
-          at = 0;
-        } else at -= sz;
-      }
-    }
-  };
-
-  var nextDocId = 0;
-  var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
-    if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
-    if (firstLine == null) firstLine = 0;
-
-    BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
-    this.first = firstLine;
-    this.scrollTop = this.scrollLeft = 0;
-    this.cantEdit = false;
-    this.cleanGeneration = 1;
-    this.frontier = firstLine;
-    var start = Pos(firstLine, 0);
-    this.sel = simpleSelection(start);
-    this.history = new History(null);
-    this.id = ++nextDocId;
-    this.modeOption = mode;
-
-    if (typeof text == "string") text = splitLines(text);
-    updateDoc(this, {from: start, to: start, text: text});
-    setSelection(this, simpleSelection(start), sel_dontScroll);
-  };
-
-  Doc.prototype = createObj(BranchChunk.prototype, {
-    constructor: Doc,
-    // Iterate over the document. Supports two forms -- with only one
-    // argument, it calls that for each line in the document. With
-    // three, it iterates over the range given by the first two (with
-    // the second being non-inclusive).
-    iter: function(from, to, op) {
-      if (op) this.iterN(from - this.first, to - from, op);
-      else this.iterN(this.first, this.first + this.size, from);
-    },
-
-    // Non-public interface for adding and removing lines.
-    insert: function(at, lines) {
-      var height = 0;
-      for (var i = 0; i < lines.length; ++i) height += lines[i].height;
-      this.insertInner(at - this.first, lines, height);
-    },
-    remove: function(at, n) { this.removeInner(at - this.first, n); },
-
-    // From here, the methods are part of the public interface. Most
-    // are also available from CodeMirror (editor) instances.
-
-    getValue: function(lineSep) {
-      var lines = getLines(this, this.first, this.first + this.size);
-      if (lineSep === false) return lines;
-      return lines.join(lineSep || "\n");
-    },
-    setValue: docMethodOp(function(code) {
-      var top = Pos(this.first, 0), last = this.first + this.size - 1;
-      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
-                        text: splitLines(code), origin: "setValue"}, true);
-      setSelection(this, simpleSelection(top));
-    }),
-    replaceRange: function(code, from, to, origin) {
-      from = clipPos(this, from);
-      to = to ? clipPos(this, to) : from;
-      replaceRange(this, code, from, to, origin);
-    },
-    getRange: function(from, to, lineSep) {
-      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
-      if (lineSep === false) return lines;
-      return lines.join(lineSep || "\n");
-    },
-
-    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
-
-    getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
-    getLineNumber: function(line) {return lineNo(line);},
-
-    getLineHandleVisualStart: function(line) {
-      if (typeof line == "number") line = getLine(this, line);
-      return visualLine(line);
-    },
-
-    lineCount: function() {return this.size;},
-    firstLine: function() {return this.first;},
-    lastLine: function() {return this.first + this.size - 1;},
-
-    clipPos: function(pos) {return clipPos(this, pos);},
-
-    getCursor: function(start) {
-      var range = this.sel.primary(), pos;
-      if (start == null || start == "head") pos = range.head;
-      else if (start == "anchor") pos = range.anchor;
-      else if (start == "end" || start == "to" || start === false) pos = range.to();
-      else pos = range.from();
-      return pos;
-    },
-    listSelections: function() { return this.sel.ranges; },
-    somethingSelected: function() {return this.sel.somethingSelected();},
-
-    setCursor: docMethodOp(function(line, ch, options) {
-      setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
-    }),
-    setSelection: docMethodOp(function(anchor, head, options) {
-      setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
-    }),
-    extendSelection: docMethodOp(function(head, other, options) {
-      extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
-    }),
-    extendSelections: docMethodOp(function(heads, options) {
-      extendSelections(this, clipPosArray(this, heads, options));
-    }),
-    extendSelectionsBy: docMethodOp(function(f, options) {
-      extendSelections(this, map(this.sel.ranges, f), options);
-    }),
-    setSelections: docMethodOp(function(ranges, primary, options) {
-      if (!ranges.length) return;
-      for (var i = 0, out = []; i < ranges.length; i++)
-        out[i] = new Range(clipPos(this, ranges[i].anchor),
-                           clipPos(this, ranges[i].head));
-      if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex);
-      setSelection(this, normalizeSelection(out, primary), options);
-    }),
-    addSelection: docMethodOp(function(anchor, head, options) {
-      var ranges = this.sel.ranges.slice(0);
-      ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
-      setSelection(this, normalizeSelection(ranges, ranges.length - 1), options);
-    }),
-
-    getSelection: function(lineSep) {
-      var ranges = this.sel.ranges, lines;
-      for (var i = 0; i < ranges.length; i++) {
-        var sel = getBetween(this, ranges[i].from(), ranges[i].to());
-        lines = lines ? lines.concat(sel) : sel;
-      }
-      if (lineSep === false) return lines;
-      else return lines.join(lineSep || "\n");
-    },
-    getSelections: function(lineSep) {
-      var parts = [], ranges = this.sel.ranges;
-      for (var i = 0; i < ranges.length; i++) {
-        var sel = getBetween(this, ranges[i].from(), ranges[i].to());
-        if (lineSep !== false) sel = sel.join(lineSep || "\n");
-        parts[i] = sel;
-      }
-      return parts;
-    },
-    replaceSelection: function(code, collapse, origin) {
-      var dup = [];
-      for (var i = 0; i < this.sel.ranges.length; i++)
-        dup[i] = code;
-      this.replaceSelections(dup, collapse, origin || "+input");
-    },
-    replaceSelections: docMethodOp(function(code, collapse, origin) {
-      var changes = [], sel = this.sel;
-      for (var i = 0; i < sel.ranges.length; i++) {
-        var range = sel.ranges[i];
-        changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
-      }
-      var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
-      for (var i = changes.length - 1; i >= 0; i--)
-        makeChange(this, changes[i]);
-      if (newSel) setSelectionReplaceHistory(this, newSel);
-      else if (this.cm) ensureCursorVisible(this.cm);
-    }),
-    undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
-    redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
-    undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
-    redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
-
-    setExtending: function(val) {this.extend = val;},
-    getExtending: function() {return this.extend;},
-
-    historySize: function() {
-      var hist = this.history, done = 0, undone = 0;
-      for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done;
-      for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone;
-      return {undo: done, redo: undone};
-    },
-    clearHistory: function() {this.history = new History(this.history.maxGeneration);},
-
-    markClean: function() {
-      this.cleanGeneration = this.changeGeneration(true);
-    },
-    changeGeneration: function(forceSplit) {
-      if (forceSplit)
-        this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null;
-      return this.history.generation;
-    },
-    isClean: function (gen) {
-      return this.history.generation == (gen || this.cleanGeneration);
-    },
-
-    getHistory: function() {
-      return {done: copyHistoryArray(this.history.done),
-              undone: copyHistoryArray(this.history.undone)};
-    },
-    setHistory: function(histData) {
-      var hist = this.history = new History(this.history.maxGeneration);
-      hist.done = copyHistoryArray(histData.done.slice(0), null, true);
-      hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
-    },
-
-    addLineClass: docMethodOp(function(handle, where, cls) {
-      return changeLine(this, handle, "class", function(line) {
-        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
-        if (!line[prop]) line[prop] = cls;
-        else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
-        else line[prop] += " " + cls;
-        return true;
-      });
-    }),
-    removeLineClass: docMethodOp(function(handle, where, cls) {
-      return changeLine(this, handle, "class", function(line) {
-        var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
-        var cur = line[prop];
-        if (!cur) return false;
-        else if (cls == null) line[prop] = null;
-        else {
-          var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
-          if (!found) return false;
-          var end = found.index + found[0].length;
-          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null;
-        }
-        return true;
-      });
-    }),
-
-    markText: function(from, to, options) {
-      return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
-    },
-    setBookmark: function(pos, options) {
-      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
-                      insertLeft: options && options.insertLeft,
-                      clearWhenEmpty: false, shared: options && options.shared};
-      pos = clipPos(this, pos);
-      return markText(this, pos, pos, realOpts, "bookmark");
-    },
-    findMarksAt: function(pos) {
-      pos = clipPos(this, pos);
-      var markers = [], spans = getLine(this, pos.line).markedSpans;
-      if (spans) for (var i = 0; i < spans.length; ++i) {
-        var span = spans[i];
-        if ((span.from == null || span.from <= pos.ch) &&
-            (span.to == null || span.to >= pos.ch))
-          markers.push(span.marker.parent || span.marker);
-      }
-      return markers;
-    },
-    findMarks: function(from, to, filter) {
-      from = clipPos(this, from); to = clipPos(this, to);
-      var found = [], lineNo = from.line;
-      this.iter(from.line, to.line + 1, function(line) {
-        var spans = line.markedSpans;
-        if (spans) for (var i = 0; i < spans.length; i++) {
-          var span = spans[i];
-          if (!(lineNo == from.line && from.ch > span.to ||
-                span.from == null && lineNo != from.line||
-                lineNo == to.line && span.from > to.ch) &&
-              (!filter || filter(span.marker)))
-            found.push(span.marker.parent || span.marker);
-        }
-        ++lineNo;
-      });
-      return found;
-    },
-    getAllMarks: function() {
-      var markers = [];
-      this.iter(function(line) {
-        var sps = line.markedSpans;
-        if (sps) for (var i = 0; i < sps.length; ++i)
-          if (sps[i].from != null) markers.push(sps[i].marker);
-      });
-      return markers;
-    },
-
-    posFromIndex: function(off) {
-      var ch, lineNo = this.first;
-      this.iter(function(line) {
-        var sz = line.text.length + 1;
-        if (sz > off) { ch = off; return true; }
-        off -= sz;
-        ++lineNo;
-      });
-      return clipPos(this, Pos(lineNo, ch));
-    },
-    indexFromPos: function (coords) {
-      coords = clipPos(this, coords);
-      var index = coords.ch;
-      if (coords.line < this.first || coords.ch < 0) return 0;
-      this.iter(this.first, coords.line, function (line) {
-        index += line.text.length + 1;
-      });
-      return index;
-    },
-
-    copy: function(copyHistory) {
-      var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
-      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
-      doc.sel = this.sel;
-      doc.extend = false;
-      if (copyHistory) {
-        doc.history.undoDepth = this.history.undoDepth;
-        doc.setHistory(this.getHistory());
-      }
-      return doc;
-    },
-
-    linkedDoc: function(options) {
-      if (!options) options = {};
-      var from = this.first, to = this.first + this.size;
-      if (options.from != null && options.from > from) from = options.from;
-      if (options.to != null && options.to < to) to = options.to;
-      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
-      if (options.sharedHist) copy.history = this.history;
-      (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
-      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
-      copySharedMarkers(copy, findSharedMarkers(this));
-      return copy;
-    },
-    unlinkDoc: function(other) {
-      if (other instanceof CodeMirror) other = other.doc;
-      if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
-        var link = this.linked[i];
-        if (link.doc != other) continue;
-        this.linked.splice(i, 1);
-        other.unlinkDoc(this);
-        detachSharedMarkers(findSharedMarkers(this));
-        break;
-      }
-      // If the histories were shared, split them again
-      if (other.history == this.history) {
-        var splitIds = [other.id];
-        linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
-        other.history = new History(null);
-        other.history.done = copyHistoryArray(this.history.done, splitIds);
-        other.history.undone = copyHistoryArray(this.history.undone, splitIds);
-      }
-    },
-    iterLinkedDocs: function(f) {linkedDocs(this, f);},
-
-    getMode: function() {return this.mode;},
-    getEditor: function() {return this.cm;}
-  });
-
-  // Public alias.
-  Doc.prototype.eachLine = Doc.prototype.iter;
-
-  // Set up methods on CodeMirror's prototype to redirect to the editor's document.
-  var dontDelegate = "iter insert remove copy getEditor".split(" ");
-  for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
-    CodeMirror.prototype[prop] = (function(method) {
-      return function() {return method.apply(this.doc, arguments);};
-    })(Doc.prototype[prop]);
-
-  eventMixin(Doc);
-
-  // Call f for all linked documents.
-  function linkedDocs(doc, f, sharedHistOnly) {
-    function propagate(doc, skip, sharedHist) {
-      if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
-        var rel = doc.linked[i];
-        if (rel.doc == skip) continue;
-        var shared = sharedHist && rel.sharedHist;
-        if (sharedHistOnly && !shared) continue;
-        f(rel.doc, shared);
-        propagate(rel.doc, doc, shared);
-      }
-    }
-    propagate(doc, null, true);
-  }
-
-  // Attach a document to an editor.
-  function attachDoc(cm, doc) {
-    if (doc.cm) throw new Error("This document is already in use.");
-    cm.doc = doc;
-    doc.cm = cm;
-    estimateLineHeights(cm);
-    loadMode(cm);
-    if (!cm.options.lineWrapping) findMaxLine(cm);
-    cm.options.mode = doc.modeOption;
-    regChange(cm);
-  }
-
-  // LINE UTILITIES
-
-  // Find the line object corresponding to the given line number.
-  function getLine(doc, n) {
-    n -= doc.first;
-    if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.");
-    for (var chunk = doc; !chunk.lines;) {
-      for (var i = 0;; ++i) {
-        var child = chunk.children[i], sz = child.chunkSize();
-        if (n < sz) { chunk = child; break; }
-        n -= sz;
-      }
-    }
-    return chunk.lines[n];
-  }
-
-  // Get the part of a document between two positions, as an array of
-  // strings.
-  function getBetween(doc, start, end) {
-    var out = [], n = start.line;
-    doc.iter(start.line, end.line + 1, function(line) {
-      var text = line.text;
-      if (n == end.line) text = text.slice(0, end.ch);
-      if (n == start.line) text = text.slice(start.ch);
-      out.push(text);
-      ++n;
-    });
-    return out;
-  }
-  // Get the lines between from and to, as array of strings.
-  function getLines(doc, from, to) {
-    var out = [];
-    doc.iter(from, to, function(line) { out.push(line.text); });
-    return out;
-  }
-
-  // Update the height of a line, propagating the height change
-  // upwards to parent nodes.
-  function updateLineHeight(line, height) {
-    var diff = height - line.height;
-    if (diff) for (var n = line; n; n = n.parent) n.height += diff;
-  }
-
-  // Given a line object, find its line number by walking up through
-  // its parent links.
-  function lineNo(line) {
-    if (line.parent == null) return null;
-    var cur = line.parent, no = indexOf(cur.lines, line);
-    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
-      for (var i = 0;; ++i) {
-        if (chunk.children[i] == cur) break;
-        no += chunk.children[i].chunkSize();
-      }
-    }
-    return no + cur.first;
-  }
-
-  // Find the line at the given vertical position, using the height
-  // information in the document tree.
-  function lineAtHeight(chunk, h) {
-    var n = chunk.first;
-    outer: do {
-      for (var i = 0; i < chunk.children.length; ++i) {
-        var child = chunk.children[i], ch = child.height;
-        if (h < ch) { chunk = child; continue outer; }
-        h -= ch;
-        n += child.chunkSize();
-      }
-      return n;
-    } while (!chunk.lines);
-    for (var i = 0; i < chunk.lines.length; ++i) {
-      var line = chunk.lines[i], lh = line.height;
-      if (h < lh) break;
-      h -= lh;
-    }
-    return n + i;
-  }
-
-
-  // Find the height above the given line.
-  function heightAtLine(lineObj) {
-    lineObj = visualLine(lineObj);
-
-    var h = 0, chunk = lineObj.parent;
-    for (var i = 0; i < chunk.lines.length; ++i) {
-      var line = chunk.lines[i];
-      if (line == lineObj) break;
-      else h += line.height;
-    }
-    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
-      for (var i = 0; i < p.children.length; ++i) {
-        var cur = p.children[i];
-        if (cur == chunk) break;
-        else h += cur.height;
-      }
-    }
-    return h;
-  }
-
-  // Get the bidi ordering for the given line (and cache it). Returns
-  // false for lines that are fully left-to-right, and an array of
-  // BidiSpan objects otherwise.
-  function getOrder(line) {
-    var order = line.order;
-    if (order == null) order = line.order = bidiOrdering(line.text);
-    return order;
-  }
-
-  // HISTORY
-
-  function History(startGen) {
-    // Arrays of change events and selections. Doing something adds an
-    // event to done and clears undo. Undoing moves events from done
-    // to undone, redoing moves them in the other direction.
-    this.done = []; this.undone = [];
-    this.undoDepth = Infinity;
-    // Used to track when changes can be merged into a single undo
-    // event
-    this.lastModTime = this.lastSelTime = 0;
-    this.lastOp = this.lastSelOp = null;
-    this.lastOrigin = this.lastSelOrigin = null;
-    // Used by the isClean() method
-    this.generation = this.maxGeneration = startGen || 1;
-  }
-
-  // Create a history change event from an updateDoc-style change
-  // object.
-  function historyChangeFromChange(doc, change) {
-    var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
-    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
-    linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
-    return histChange;
-  }
-
-  // Pop all selection events off the end of a history array. Stop at
-  // a change event.
-  function clearSelectionEvents(array) {
-    while (array.length) {
-      var last = lst(array);
-      if (last.ranges) array.pop();
-      else break;
-    }
-  }
-
-  // Find the top change event in the history. Pop off selection
-  // events that are in the way.
-  function lastChangeEvent(hist, force) {
-    if (force) {
-      clearSelectionEvents(hist.done);
-      return lst(hist.done);
-    } else if (hist.done.length && !lst(hist.done).ranges) {
-      return lst(hist.done);
-    } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
-      hist.done.pop();
-      return lst(hist.done);
-    }
-  }
-
-  // Register a change in the history. Merges changes that are within
-  // a single operation, ore are close together with an origin that
-  // allows merging (starting with "+") into a single event.
-  function addChangeToHistory(doc, change, selAfter, opId) {
-    var hist = doc.history;
-    hist.undone.length = 0;
-    var time = +new Date, cur;
-
-    if ((hist.lastOp == opId ||
-         hist.lastOrigin == change.origin && change.origin &&
-         ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
-          change.origin.charAt(0) == "*")) &&
-        (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
-      // Merge this change into the last event
-      var last = lst(cur.changes);
-      if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
-        // Optimized case for simple insertion -- don't want to add
-        // new changesets for every character typed
-        last.to = changeEnd(change);
-      } else {
-        // Add new sub-event
-        cur.changes.push(historyChangeFromChange(doc, change));
-      }
-    } else {
-      // Can not be merged, start a new event.
-      var before = lst(hist.done);
-      if (!before || !before.ranges)
-        pushSelectionToHistory(doc.sel, hist.done);
-      cur = {changes: [historyChangeFromChange(doc, change)],
-             generation: hist.generation};
-      hist.done.push(cur);
-      while (hist.done.length > hist.undoDepth) {
-        hist.done.shift();
-        if (!hist.done[0].ranges) hist.done.shift();
-      }
-    }
-    hist.done.push(selAfter);
-    hist.generation = ++hist.maxGeneration;
-    hist.lastModTime = hist.lastSelTime = time;
-    hist.lastOp = hist.lastSelOp = opId;
-    hist.lastOrigin = hist.lastSelOrigin = change.origin;
-
-    if (!last) signal(doc, "historyAdded");
-  }
-
-  function selectionEventCanBeMerged(doc, origin, prev, sel) {
-    var ch = origin.charAt(0);
-    return ch == "*" ||
-      ch == "+" &&
-      prev.ranges.length == sel.ranges.length &&
-      prev.somethingSelected() == sel.somethingSelected() &&
-      new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500);
-  }
-
-  // Called whenever the selection changes, sets the new selection as
-  // the pending selection in the history, and pushes the old pending
-  // selection into the 'done' array when it was significantly
-  // different (in number of selected ranges, emptiness, or time).
-  function addSelectionToHistory(doc, sel, opId, options) {
-    var hist = doc.history, origin = options && options.origin;
-
-    // A new event is started when the previous origin does not match
-    // the current, or the origins don't allow matching. Origins
-    // starting with * are always merged, those starting with + are
-    // merged when similar and close together in time.
-    if (opId == hist.lastSelOp ||
-        (origin && hist.lastSelOrigin == origin &&
-         (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
-          selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
-      hist.done[hist.done.length - 1] = sel;
-    else
-      pushSelectionToHistory(sel, hist.done);
-
-    hist.lastSelTime = +new Date;
-    hist.lastSelOrigin = origin;
-    hist.lastSelOp = opId;
-    if (options && options.clearRedo !== false)
-      clearSelectionEvents(hist.undone);
-  }
-
-  function pushSelectionToHistory(sel, dest) {
-    var top = lst(dest);
-    if (!(top && top.ranges && top.equals(sel)))
-      dest.push(sel);
-  }
-
-  // Used to store marked span information in the history.
-  function attachLocalSpans(doc, change, from, to) {
-    var existing = change["spans_" + doc.id], n = 0;
-    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
-      if (line.markedSpans)
-        (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
-      ++n;
-    });
-  }
-
-  // When un/re-doing restores text containing marked spans, those
-  // that have been explicitly cleared should not be restored.
-  function removeClearedSpans(spans) {
-    if (!spans) return null;
-    for (var i = 0, out; i < spans.length; ++i) {
-      if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
-      else if (out) out.push(spans[i]);
-    }
-    return !out ? spans : out.length ? out : null;
-  }
-
-  // Retrieve and filter the old marked spans stored in a change event.
-  function getOldSpans(doc, change) {
-    var found = change["spans_" + doc.id];
-    if (!found) return null;
-    for (var i = 0, nw = []; i < change.text.length; ++i)
-      nw.push(removeClearedSpans(found[i]));
-    return nw;
-  }
-
-  // Used both to provide a JSON-safe object in .getHistory, and, when
-  // detaching a document, to split the history in two
-  function copyHistoryArray(events, newGroup, instantiateSel) {
-    for (var i = 0, copy = []; i < events.length; ++i) {
-      var event = events[i];
-      if (event.ranges) {
-        copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
-        continue;
-      }
-      var changes = event.changes, newChanges = [];
-      copy.push({changes: newChanges});
-      for (var j = 0; j < changes.length; ++j) {
-        var change = changes[j], m;
-        newChanges.push({from: change.from, to: change.to, text: change.text});
-        if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
-          if (indexOf(newGroup, Number(m[1])) > -1) {
-            lst(newChanges)[prop] = change[prop];
-            delete change[prop];
-          }
-        }
-      }
-    }
-    return copy;
-  }
-
-  // Rebasing/resetting history to deal with externally-sourced changes
-
-  function rebaseHistSelSingle(pos, from, to, diff) {
-    if (to < pos.line) {
-      pos.line += diff;
-    } else if (from < pos.line) {
-      pos.line = from;
-      pos.ch = 0;
-    }
-  }
-
-  // Tries to rebase an array of history events given a change in the
-  // document. If the change touches the same lines as the event, the
-  // event, and everything 'behind' it, is discarded. If the change is
-  // before the event, the event's positions are updated. Uses a
-  // copy-on-write scheme for the positions, to avoid having to
-  // reallocate them all on every rebase, but also avoid problems with
-  // shared position objects being unsafely updated.
-  function rebaseHistArray(array, from, to, diff) {
-    for (var i = 0; i < array.length; ++i) {
-      var sub = array[i], ok = true;
-      if (sub.ranges) {
-        if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
-        for (var j = 0; j < sub.ranges.length; j++) {
-          rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
-          rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
-        }
-        continue;
-      }
-      for (var j = 0; j < sub.changes.length; ++j) {
-        var cur = sub.changes[j];
-        if (to < cur.from.line) {
-          cur.from = Pos(cur.from.line + diff, cur.from.ch);
-          cur.to = Pos(cur.to.line + diff, cur.to.ch);
-        } else if (from <= cur.to.line) {
-          ok = false;
-          break;
-        }
-      }
-      if (!ok) {
-        array.splice(0, i + 1);
-        i = 0;
-      }
-    }
-  }
-
-  function rebaseHist(hist, change) {
-    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
-    rebaseHistArray(hist.done, from, to, diff);
-    rebaseHistArray(hist.undone, from, to, diff);
-  }
-
-  // EVENT UTILITIES
-
-  // Due to the fact that we still support jurassic IE versions, some
-  // compatibility wrappers are needed.
-
-  var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
-    if (e.preventDefault) e.preventDefault();
-    else e.returnValue = false;
-  };
-  var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
-    if (e.stopPropagation) e.stopPropagation();
-    else e.cancelBubble = true;
-  };
-  function e_defaultPrevented(e) {
-    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
-  }
-  var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);};
-
-  function e_target(e) {return e.target || e.srcElement;}
-  function e_button(e) {
-    var b = e.which;
-    if (b == null) {
-      if (e.button & 1) b = 1;
-      else if (e.button & 2) b = 3;
-      else if (e.button & 4) b = 2;
-    }
-    if (mac && e.ctrlKey && b == 1) b = 3;
-    return b;
-  }
-
-  // EVENT HANDLING
-
-  // Lightweight event framework. on/off also work on DOM nodes,
-  // registering native DOM handlers.
-
-  var on = CodeMirror.on = function(emitter, type, f) {
-    if (emitter.addEventListener)
-      emitter.addEventListener(type, f, false);
-    else if (emitter.attachEvent)
-      emitter.attachEvent("on" + type, f);
-    else {
-      var map = emitter._handlers || (emitter._handlers = {});
-      var arr = map[type] || (map[type] = []);
-      arr.push(f);
-    }
-  };
-
-  var off = CodeMirror.off = function(emitter, type, f) {
-    if (emitter.removeEventListener)
-      emitter.removeEventListener(type, f, false);
-    else if (emitter.detachEvent)
-      emitter.detachEvent("on" + type, f);
-    else {
-      var arr = emitter._handlers && emitter._handlers[type];
-      if (!arr) return;
-      for (var i = 0; i < arr.length; ++i)
-        if (arr[i] == f) { arr.splice(i, 1); break; }
-    }
-  };
-
-  var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
-    var arr = emitter._handlers && emitter._handlers[type];
-    if (!arr) return;
-    var args = Array.prototype.slice.call(arguments, 2);
-    for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
-  };
-
-  var orphanDelayedCallbacks = null;
-
-  // Often, we want to signal events at a point where we are in the
-  // middle of some work, but don't want the handler to start calling
-  // other methods on the editor, which might be in an inconsistent
-  // state or simply not expect any other events to happen.
-  // signalLater looks whether there are any handlers, and schedules
-  // them to be executed when the last operation ends, or, if no
-  // operation is active, when a timeout fires.
-  function signalLater(emitter, type /*, values...*/) {
-    var arr = emitter._handlers && emitter._handlers[type];
-    if (!arr) return;
-    var args = Array.prototype.slice.call(arguments, 2), list;
-    if (operationGroup) {
-      list = operationGroup.delayedCallbacks;
-    } else if (orphanDelayedCallbacks) {
-      list = orphanDelayedCallbacks;
-    } else {
-      list = orphanDelayedCallbacks = [];
-      setTimeout(fireOrphanDelayed, 0);
-    }
-    function bnd(f) {return function(){f.apply(null, args);};};
-    for (var i = 0; i < arr.length; ++i)
-      list.push(bnd(arr[i]));
-  }
-
-  function fireOrphanDelayed() {
-    var delayed = orphanDelayedCallbacks;
-    orphanDelayedCallbacks = null;
-    for (var i = 0; i < delayed.length; ++i) delayed[i]();
-  }
-
-  // The DOM events that CodeMirror handles can be overridden by
-  // registering a (non-DOM) handler on the editor for the event name,
-  // and preventDefault-ing the event in that handler.
-  function signalDOMEvent(cm, e, override) {
-    signal(cm, override || e.type, cm, e);
-    return e_defaultPrevented(e) || e.codemirrorIgnore;
-  }
-
-  function signalCursorActivity(cm) {
-    var arr = cm._handlers && cm._handlers.cursorActivity;
-    if (!arr) return;
-    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);
-    for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1)
-      set.push(arr[i]);
-  }
-
-  function hasHandler(emitter, type) {
-    var arr = emitter._handlers && emitter._handlers[type];
-    return arr && arr.length > 0;
-  }
-
-  // Add on and off methods to a constructor's prototype, to make
-  // registering events on such objects more convenient.
-  function eventMixin(ctor) {
-    ctor.prototype.on = function(type, f) {on(this, type, f);};
-    ctor.prototype.off = function(type, f) {off(this, type, f);};
-  }
-
-  // MISC UTILITIES
-
-  // Number of pixels added to scroller and sizer to hide scrollbar
-  var scrollerCutOff = 30;
-
-  // Returned or thrown by various protocols to signal 'I'm not
-  // handling this'.
-  var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
-
-  // Reused option objects for setSelection & friends
-  var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
-
-  function Delayed() {this.id = null;}
-  Delayed.prototype.set = function(ms, f) {
-    clearTimeout(this.id);
-    this.id = setTimeout(f, ms);
-  };
-
-  // Counts the column offset in a string, taking tabs into account.
-  // Used mostly to find indentation.
-  var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) {
-    if (end == null) {
-      end = string.search(/[^\s\u00a0]/);
-      if (end == -1) end = string.length;
-    }
-    for (var i = startIndex || 0, n = startValue || 0;;) {
-      var nextTab = string.indexOf("\t", i);
-      if (nextTab < 0 || nextTab >= end)
-        return n + (end - i);
-      n += nextTab - i;
-      n += tabSize - (n % tabSize);
-      i = nextTab + 1;
-    }
-  };
-
-  // The inverse of countColumn -- find the offset that corresponds to
-  // a particular column.
-  function findColumn(string, goal, tabSize) {
-    for (var pos = 0, col = 0;;) {
-      var nextTab = string.indexOf("\t", pos);
-      if (nextTab == -1) nextTab = string.length;
-      var skipped = nextTab - pos;
-      if (nextTab == string.length || col + skipped >= goal)
-        return pos + Math.min(skipped, goal - col);
-      col += nextTab - pos;
-      col += tabSize - (col % tabSize);
-      pos = nextTab + 1;
-      if (col >= goal) return pos;
-    }
-  }
-
-  var spaceStrs = [""];
-  function spaceStr(n) {
-    while (spaceStrs.length <= n)
-      spaceStrs.push(lst(spaceStrs) + " ");
-    return spaceStrs[n];
-  }
-
-  function lst(arr) { return arr[arr.length-1]; }
-
-  var selectInput = function(node) { node.select(); };
-  if (ios) // Mobile Safari apparently has a bug where select() is broken.
-    selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; };
-  else if (ie) // Suppress mysterious IE10 errors
-    selectInput = function(node) { try { node.select(); } catch(_e) {} };
-
-  function indexOf(array, elt) {
-    for (var i = 0; i < array.length; ++i)
-      if (array[i] == elt) return i;
-    return -1;
-  }
-  if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
-  function map(array, f) {
-    var out = [];
-    for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
-    return out;
-  }
-  if ([].map) map = function(array, f) { return array.map(f); };
-
-  function createObj(base, props) {
-    var inst;
-    if (Object.create) {
-      inst = Object.create(base);
-    } else {
-      var ctor = function() {};
-      ctor.prototype = base;
-      inst = new ctor();
-    }
-    if (props) copyObj(props, inst);
-    return inst;
-  };
-
-  function copyObj(obj, target, overwrite) {
-    if (!target) target = {};
-    for (var prop in obj)
-      if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))
-        target[prop] = obj[prop];
-    return target;
-  }
-
-  function bind(f) {
-    var args = Array.prototype.slice.call(arguments, 1);
-    return function(){return f.apply(null, args);};
-  }
-
-  var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
-  var isWordCharBasic = CodeMirror.isWordChar = function(ch) {
-    return /\w/.test(ch) || ch > "\x80" &&
-      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
-  };
-  function isWordChar(ch, helper) {
-    if (!helper) return isWordCharBasic(ch);
-    if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true;
-    return helper.test(ch);
-  }
-
-  function isEmpty(obj) {
-    for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
-    return true;
-  }
-
-  // Extending unicode characters. A series of a non-extending char +
-  // any number of extending chars is treated as a single unit as far
-  // as editing and measuring is concerned. This is not fully correct,
-  // since some scripts/fonts/browsers also treat other configurations
-  // of code points as a group.
-  var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
-  function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); }
-
-  // DOM UTILITIES
-
-  function elt(tag, content, className, style) {
-    var e = document.createElement(tag);
-    if (className) e.className = className;
-    if (style) e.style.cssText = style;
-    if (typeof content == "string") e.appendChild(document.createTextNode(content));
-    else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
-    return e;
-  }
-
-  var range;
-  if (document.createRange) range = function(node, start, end) {
-    var r = document.createRange();
-    r.setEnd(node, end);
-    r.setStart(node, start);
-    return r;
-  };
-  else range = function(node, start, end) {
-    var r = document.body.createTextRange();
-    r.moveToElementText(node.parentNode);
-    r.collapse(true);
-    r.moveEnd("character", end);
-    r.moveStart("character", start);
-    return r;
-  };
-
-  function removeChildren(e) {
-    for (var count = e.childNodes.length; count > 0; --count)
-      e.removeChild(e.firstChild);
-    return e;
-  }
-
-  function removeChildrenAndAdd(parent, e) {
-    return removeChildren(parent).appendChild(e);
-  }
-
-  function contains(parent, child) {
-    if (parent.contains)
-      return parent.contains(child);
-    while (child = child.parentNode)
-      if (child == parent) return true;
-  }
-
-  function activeElt() { return document.activeElement; }
-  // Older versions of IE throws unspecified error when touching
-  // document.activeElement in some cases (during loading, in iframe)
-  if (ie && ie_version < 11) activeElt = function() {
-    try { return document.activeElement; }
-    catch(e) { return document.body; }
-  };
-
-  function classTest(cls) { return new RegExp("\\b" + cls + "\\b\\s*"); }
-  function rmClass(node, cls) {
-    var test = classTest(cls);
-    if (test.test(node.className)) node.className = node.className.replace(test, "");
-  }
-  function addClass(node, cls) {
-    if (!classTest(cls).test(node.className)) node.className += " " + cls;
-  }
-  function joinClasses(a, b) {
-    var as = a.split(" ");
-    for (var i = 0; i < as.length; i++)
-      if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i];
-    return b;
-  }
-
-  // WINDOW-WIDE EVENTS
-
-  // These must be handled carefully, because naively registering a
-  // handler for each editor will cause the editors to never be
-  // garbage collected.
-
-  function forEachCodeMirror(f) {
-    if (!document.body.getElementsByClassName) return;
-    var byClass = document.body.getElementsByClassName("CodeMirror");
-    for (var i = 0; i < byClass.length; i++) {
-      var cm = byClass[i].CodeMirror;
-      if (cm) f(cm);
-    }
-  }
-
-  var globalsRegistered = false;
-  function ensureGlobalHandlers() {
-    if (globalsRegistered) return;
-    registerGlobalHandlers();
-    globalsRegistered = true;
-  }
-  function registerGlobalHandlers() {
-    // When the window resizes, we need to refresh active editors.
-    var resizeTimer;
-    on(window, "resize", function() {
-      if (resizeTimer == null) resizeTimer = setTimeout(function() {
-        resizeTimer = null;
-        knownScrollbarWidth = null;
-        forEachCodeMirror(onResize);
-      }, 100);
-    });
-    // When the window loses focus, we want to show the editor as blurred
-    on(window, "blur", function() {
-      forEachCodeMirror(onBlur);
-    });
-  }
-
-  // FEATURE DETECTION
-
-  // Detect drag-and-drop
-  var dragAndDrop = function() {
-    // There is *some* kind of drag-and-drop support in IE6-8, but I
-    // couldn't get it to work yet.
-    if (ie && ie_version < 9) return false;
-    var div = elt('div');
-    return "draggable" in div || "dragDrop" in div;
-  }();
-
-  var knownScrollbarWidth;
-  function scrollbarWidth(measure) {
-    if (knownScrollbarWidth != null) return knownScrollbarWidth;
-    var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
-    removeChildrenAndAdd(measure, test);
-    if (test.offsetWidth)
-      knownScrollbarWidth = test.offsetHeight - test.clientHeight;
-    return knownScrollbarWidth || 0;
-  }
-
-  var zwspSupported;
-  function zeroWidthElement(measure) {
-    if (zwspSupported == null) {
-      var test = elt("span", "\u200b");
-      removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
-      if (measure.firstChild.offsetHeight != 0)
-        zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
-    }
-    if (zwspSupported) return elt("span", "\u200b");
-    else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
-  }
-
-  // Feature-detect IE's crummy client rect reporting for bidi text
-  var badBidiRects;
-  function hasBadBidiRects(measure) {
-    if (badBidiRects != null) return badBidiRects;
-    var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
-    var r0 = range(txt, 0, 1).getBoundingClientRect();
-    if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
-    var r1 = range(txt, 1, 2).getBoundingClientRect();
-    return badBidiRects = (r1.right - r0.right < 3);
-  }
-
-  // See if "".split is the broken IE version, if so, provide an
-  // alternative way to split lines.
-  var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
-    var pos = 0, result = [], l = string.length;
-    while (pos <= l) {
-      var nl = string.indexOf("\n", pos);
-      if (nl == -1) nl = string.length;
-      var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
-      var rt = line.indexOf("\r");
-      if (rt != -1) {
-        result.push(line.slice(0, rt));
-        pos += rt + 1;
-      } else {
-        result.push(line);
-        pos = nl + 1;
-      }
-    }
-    return result;
-  } : function(string){return string.split(/\r\n?|\n/);};
-
-  var hasSelection = window.getSelection ? function(te) {
-    try { return te.selectionStart != te.selectionEnd; }
-    catch(e) { return false; }
-  } : function(te) {
-    try {var range = te.ownerDocument.selection.createRange();}
-    catch(e) {}
-    if (!range || range.parentElement() != te) return false;
-    return range.compareEndPoints("StartToEnd", range) != 0;
-  };
-
-  var hasCopyEvent = (function() {
-    var e = elt("div");
-    if ("oncopy" in e) return true;
-    e.setAttribute("oncopy", "return;");
-    return typeof e.oncopy == "function";
-  })();
-
-  var badZoomedRects = null;
-  function hasBadZoomedRects(measure) {
-    if (badZoomedRects != null) return badZoomedRects;
-    var node = removeChildrenAndAdd(measure, elt("span", "x"));
-    var normal = node.getBoundingClientRect();
-    var fromRange = range(node, 0, 1).getBoundingClientRect();
-    return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1;
-  }
-
-  // KEY NAMES
-
-  var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
-                  19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
-                  36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
-                  46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
-                  173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
-                  221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
-                  63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
-  CodeMirror.keyNames = keyNames;
-  (function() {
-    // Number keys
-    for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
-    // Alphabetic keys
-    for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
-    // Function keys
-    for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
-  })();
-
-  // BIDI HELPERS
-
-  function iterateBidiSections(order, from, to, f) {
-    if (!order) return f(from, to, "ltr");
-    var found = false;
-    for (var i = 0; i < order.length; ++i) {
-      var part = order[i];
-      if (part.from < to && part.to > from || from == to && part.to == from) {
-        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
-        found = true;
-      }
-    }
-    if (!found) f(from, to, "ltr");
-  }
-
-  function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
-  function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
-
-  function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
-  function lineRight(line) {
-    var order = getOrder(line);
-    if (!order) return line.text.length;
-    return bidiRight(lst(order));
-  }
-
-  function lineStart(cm, lineN) {
-    var line = getLine(cm.doc, lineN);
-    var visual = visualLine(line);
-    if (visual != line) lineN = lineNo(visual);
-    var order = getOrder(visual);
-    var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
-    return Pos(lineN, ch);
-  }
-  function lineEnd(cm, lineN) {
-    var merged, line = getLine(cm.doc, lineN);
-    while (merged = collapsedSpanAtEnd(line)) {
-      line = merged.find(1, true).line;
-      lineN = null;
-    }
-    var order = getOrder(line);
-    var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
-    return Pos(lineN == null ? lineNo(line) : lineN, ch);
-  }
-  function lineStartSmart(cm, pos) {
-    var start = lineStart(cm, pos.line);
-    var line = getLine(cm.doc, start.line);
-    var order = getOrder(line);
-    if (!order || order[0].level == 0) {
-      var firstNonWS = Math.max(0, line.text.search(/\S/));
-      var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
-      return Pos(start.line, inWS ? 0 : firstNonWS);
-    }
-    return start;
-  }
-
-  function compareBidiLevel(order, a, b) {
-    var linedir = order[0].level;
-    if (a == linedir) return true;
-    if (b == linedir) return false;
-    return a < b;
-  }
-  var bidiOther;
-  function getBidiPartAt(order, pos) {
-    bidiOther = null;
-    for (var i = 0, found; i < order.length; ++i) {
-      var cur = order[i];
-      if (cur.from < pos && cur.to > pos) return i;
-      if ((cur.from == pos || cur.to == pos)) {
-        if (found == null) {
-          found = i;
-        } else if (compareBidiLevel(order, cur.level, order[found].level)) {
-          if (cur.from != cur.to) bidiOther = found;
-          return i;
-        } else {
-          if (cur.from != cur.to) bidiOther = i;
-          return found;
-        }
-      }
-    }
-    return found;
-  }
-
-  function moveInLine(line, pos, dir, byUnit) {
-    if (!byUnit) return pos + dir;
-    do pos += dir;
-    while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
-    return pos;
-  }
-
-  // This is needed in order to move 'visually' through bi-directional
-  // text -- i.e., pressing left should make the cursor go left, even
-  // when in RTL text. The tricky part is the 'jumps', where RTL and
-  // LTR text touch each other. This often requires the cursor offset
-  // to move more than one unit, in order to visually move one unit.
-  function moveVisually(line, start, dir, byUnit) {
-    var bidi = getOrder(line);
-    if (!bidi) return moveLogically(line, start, dir, byUnit);
-    var pos = getBidiPartAt(bidi, start), part = bidi[pos];
-    var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
-
-    for (;;) {
-      if (target > part.from && target < part.to) return target;
-      if (target == part.from || target == part.to) {
-        if (getBidiPartAt(bidi, target) == pos) return target;
-        part = bidi[pos += dir];
-        return (dir > 0) == part.level % 2 ? part.to : part.from;
-      } else {
-        part = bidi[pos += dir];
-        if (!part) return null;
-        if ((dir > 0) == part.level % 2)
-          target = moveInLine(line, part.to, -1, byUnit);
-        else
-          target = moveInLine(line, part.from, 1, byUnit);
-      }
-    }
-  }
-
-  function moveLogically(line, start, dir, byUnit) {
-    var target = start + dir;
-    if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
-    return target < 0 || target > line.text.length ? null : target;
-  }
-
-  // Bidirectional ordering algorithm
-  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
-  // that this (partially) implements.
-
-  // One-char codes used for character types:
-  // L (L):   Left-to-Right
-  // R (R):   Right-to-Left
-  // r (AL):  Right-to-Left Arabic
-  // 1 (EN):  European Number
-  // + (ES):  European Number Separator
-  // % (ET):  European Number Terminator
-  // n (AN):  Arabic Number
-  // , (CS):  Common Number Separator
-  // m (NSM): Non-Spacing Mark
-  // b (BN):  Boundary Neutral
-  // s (B):   Paragraph Separator
-  // t (S):   Segment Separator
-  // w (WS):  Whitespace
-  // N (ON):  Other Neutrals
-
-  // Returns null if characters are ordered as they appear
-  // (left-to-right), or an array of sections ({from, to, level}
-  // objects) in the order in which they occur visually.
-  var bidiOrdering = (function() {
-    // Character types for codepoints 0 to 0xff
-    var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
-    // Character types for codepoints 0x600 to 0x6ff
-    var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
-    function charType(code) {
-      if (code <= 0xf7) return lowTypes.charAt(code);
-      else if (0x590 <= code && code <= 0x5f4) return "R";
-      else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600);
-      else if (0x6ee <= code && code <= 0x8ac) return "r";
-      else if (0x2000 <= code && code <= 0x200b) return "w";
-      else if (code == 0x200c) return "b";
-      else return "L";
-    }
-
-    var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
-    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
-    // Browsers seem to always treat the boundaries of block elements as being L.
-    var outerType = "L";
-
-    function BidiSpan(level, from, to) {
-      this.level = level;
-      this.from = from; this.to = to;
-    }
-
-    return function(str) {
-      if (!bidiRE.test(str)) return false;
-      var len = str.length, types = [];
-      for (var i = 0, type; i < len; ++i)
-        types.push(type = charType(str.charCodeAt(i)));
-
-      // W1. Examine each non-spacing mark (NSM) in the level run, and
-      // change the type of the NSM to the type of the previous
-      // character. If the NSM is at the start of the level run, it will
-      // get the type of sor.
-      for (var i = 0, prev = outerType; i < len; ++i) {
-        var type = types[i];
-        if (type == "m") types[i] = prev;
-        else prev = type;
-      }
-
-      // W2. Search backwards from each instance of a European number
-      // until the first strong type (R, L, AL, or sor) is found. If an
-      // AL is found, change the type of the European number to Arabic
-      // number.
-      // W3. Change all ALs to R.
-      for (var i = 0, cur = outerType; i < len; ++i) {
-        var type = types[i];
-        if (type == "1" && cur == "r") types[i] = "n";
-        else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
-      }
-
-      // W4. A single European separator between two European numbers
-      // changes to a European number. A single common separator between
-      // two numbers of the same type changes to that type.
-      for (var i = 1, prev = types[0]; i < len - 1; ++i) {
-        var type = types[i];
-        if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
-        else if (type == "," && prev == types[i+1] &&
-                 (prev == "1" || prev == "n")) types[i] = prev;
-        prev = type;
-      }
-
-      // W5. A sequence of European terminators adjacent to European
-      // numbers changes to all European numbers.
-      // W6. Otherwise, separators and terminators change to Other
-      // Neutral.
-      for (var i = 0; i < len; ++i) {
-        var type = types[i];
-        if (type == ",") types[i] = "N";
-        else if (type == "%") {
-          for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
-          var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
-          for (var j = i; j < end; ++j) types[j] = replace;
-          i = end - 1;
-        }
-      }
-
-      // W7. Search backwards from each instance of a European number
-      // until the first strong type (R, L, or sor) is found. If an L is
-      // found, then change the type of the European number to L.
-      for (var i = 0, cur = outerType; i < len; ++i) {
-        var type = types[i];
-        if (cur == "L" && type == "1") types[i] = "L";
-        else if (isStrong.test(type)) cur = type;
-      }
-
-      // N1. A sequence of neutrals takes the direction of the
-      // surrounding strong text if the text on both sides has the same
-      // direction. European and Arabic numbers act as if they were R in
-      // terms of their influence on neutrals. Start-of-level-run (sor)
-      // and end-of-level-run (eor) are used at level run boundaries.
-      // N2. Any remaining neutrals take the embedding direction.
-      for (var i = 0; i < len; ++i) {
-        if (isNeutral.test(types[i])) {
-          for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
-          var before = (i ? types[i-1] : outerType) == "L";
-          var after = (end < len ? types[end] : outerType) == "L";
-          var replace = before || after ? "L" : "R";
-          for (var j = i; j < end; ++j) types[j] = replace;
-          i = end - 1;
-        }
-      }
-
-      // Here we depart from the documented algorithm, in order to avoid
-      // building up an actual levels array. Since there are only three
-      // levels (0, 1, 2) in an implementation that doesn't take
-      // explicit embedding into account, we can build up the order on
-      // the fly, without following the level-based algorithm.
-      var order = [], m;
-      for (var i = 0; i < len;) {
-        if (countsAsLeft.test(types[i])) {
-          var start = i;
-          for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
-          order.push(new BidiSpan(0, start, i));
-        } else {
-          var pos = i, at = order.length;
-          for (++i; i < len && types[i] != "L"; ++i) {}
-          for (var j = pos; j < i;) {
-            if (countsAsNum.test(types[j])) {
-              if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
-              var nstart = j;
-              for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
-              order.splice(at, 0, new BidiSpan(2, nstart, j));
-              pos = j;
-            } else ++j;
-          }
-          if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
-        }
-      }
-      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
-        order[0].from = m[0].length;
-        order.unshift(new BidiSpan(0, 0, m[0].length));
-      }
-      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
-        lst(order).to -= m[0].length;
-        order.push(new BidiSpan(0, len - m[0].length, len));
-      }
-      if (order[0].level != lst(order).level)
-        order.push(new BidiSpan(order[0].level, len, len));
-
-      return order;
-    };
-  })();
-
-  // THE END
-
-  CodeMirror.version = "4.7.0";
-
-  return CodeMirror;
-});
--- a/kallithea/public/codemirror/mode/apl/apl.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,175 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("apl", function() {
-  var builtInOps = {
-    ".": "innerProduct",
-    "\\": "scan",
-    "/": "reduce",
-    "⌿": "reduce1Axis",
-    "⍀": "scan1Axis",
-    "¨": "each",
-    "⍣": "power"
-  };
-  var builtInFuncs = {
-    "+": ["conjugate", "add"],
-    "−": ["negate", "subtract"],
-    "×": ["signOf", "multiply"],
-    "÷": ["reciprocal", "divide"],
-    "⌈": ["ceiling", "greaterOf"],
-    "⌊": ["floor", "lesserOf"],
-    "∣": ["absolute", "residue"],
-    "⍳": ["indexGenerate", "indexOf"],
-    "?": ["roll", "deal"],
-    "⋆": ["exponentiate", "toThePowerOf"],
-    "⍟": ["naturalLog", "logToTheBase"],
-    "○": ["piTimes", "circularFuncs"],
-    "!": ["factorial", "binomial"],
-    "⌹": ["matrixInverse", "matrixDivide"],
-    "<": [null, "lessThan"],
-    "≤": [null, "lessThanOrEqual"],
-    "=": [null, "equals"],
-    ">": [null, "greaterThan"],
-    "≥": [null, "greaterThanOrEqual"],
-    "≠": [null, "notEqual"],
-    "≡": ["depth", "match"],
-    "≢": [null, "notMatch"],
-    "∈": ["enlist", "membership"],
-    "⍷": [null, "find"],
-    "∪": ["unique", "union"],
-    "∩": [null, "intersection"],
-    "∼": ["not", "without"],
-    "∨": [null, "or"],
-    "∧": [null, "and"],
-    "⍱": [null, "nor"],
-    "⍲": [null, "nand"],
-    "⍴": ["shapeOf", "reshape"],
-    ",": ["ravel", "catenate"],
-    "⍪": [null, "firstAxisCatenate"],
-    "⌽": ["reverse", "rotate"],
-    "⊖": ["axis1Reverse", "axis1Rotate"],
-    "⍉": ["transpose", null],
-    "↑": ["first", "take"],
-    "↓": [null, "drop"],
-    "⊂": ["enclose", "partitionWithAxis"],
-    "⊃": ["diclose", "pick"],
-    "⌷": [null, "index"],
-    "⍋": ["gradeUp", null],
-    "⍒": ["gradeDown", null],
-    "⊤": ["encode", null],
-    "⊥": ["decode", null],
-    "⍕": ["format", "formatByExample"],
-    "⍎": ["execute", null],
-    "⊣": ["stop", "left"],
-    "⊢": ["pass", "right"]
-  };
-
-  var isOperator = /[\.\/⌿⍀¨⍣]/;
-  var isNiladic = /⍬/;
-  var isFunction = /[\+−×÷⌈⌊∣⍳\?⋆⍟○!⌹<≤=>≥≠≡≢∈⍷∪∩∼∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢]/;
-  var isArrow = /←/;
-  var isComment = /[⍝#].*$/;
-
-  var stringEater = function(type) {
-    var prev;
-    prev = false;
-    return function(c) {
-      prev = c;
-      if (c === type) {
-        return prev === "\\";
-      }
-      return true;
-    };
-  };
-  return {
-    startState: function() {
-      return {
-        prev: false,
-        func: false,
-        op: false,
-        string: false,
-        escape: false
-      };
-    },
-    token: function(stream, state) {
-      var ch, funcName, word;
-      if (stream.eatSpace()) {
-        return null;
-      }
-      ch = stream.next();
-      if (ch === '"' || ch === "'") {
-        stream.eatWhile(stringEater(ch));
-        stream.next();
-        state.prev = true;
-        return "string";
-      }
-      if (/[\[{\(]/.test(ch)) {
-        state.prev = false;
-        return null;
-      }
-      if (/[\]}\)]/.test(ch)) {
-        state.prev = true;
-        return null;
-      }
-      if (isNiladic.test(ch)) {
-        state.prev = false;
-        return "niladic";
-      }
-      if (/[¯\d]/.test(ch)) {
-        if (state.func) {
-          state.func = false;
-          state.prev = false;
-        } else {
-          state.prev = true;
-        }
-        stream.eatWhile(/[\w\.]/);
-        return "number";
-      }
-      if (isOperator.test(ch)) {
-        return "operator apl-" + builtInOps[ch];
-      }
-      if (isArrow.test(ch)) {
-        return "apl-arrow";
-      }
-      if (isFunction.test(ch)) {
-        funcName = "apl-";
-        if (builtInFuncs[ch] != null) {
-          if (state.prev) {
-            funcName += builtInFuncs[ch][1];
-          } else {
-            funcName += builtInFuncs[ch][0];
-          }
-        }
-        state.func = true;
-        state.prev = false;
-        return "function " + funcName;
-      }
-      if (isComment.test(ch)) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      if (ch === "∘" && stream.peek() === ".") {
-        stream.next();
-        return "function jot-dot";
-      }
-      stream.eatWhile(/[\w\$_]/);
-      word = stream.current();
-      state.prev = true;
-      return "keyword";
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/apl", "apl");
-
-});
--- a/kallithea/public/codemirror/mode/asterisk/asterisk.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
- * =====================================================================================
- *
- *       Filename:  mode/asterisk/asterisk.js
- *
- *    Description:  CodeMirror mode for Asterisk dialplan
- *
- *        Created:  05/17/2012 09:20:25 PM
- *       Revision:  none
- *
- *         Author:  Stas Kobzar (stas@modulis.ca),
- *        Company:  Modulis.ca Inc.
- *
- * =====================================================================================
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("asterisk", function() {
-  var atoms    = ["exten", "same", "include","ignorepat","switch"],
-      dpcmd    = ["#include","#exec"],
-      apps     = [
-                  "addqueuemember","adsiprog","aelsub","agentlogin","agentmonitoroutgoing","agi",
-                  "alarmreceiver","amd","answer","authenticate","background","backgrounddetect",
-                  "bridge","busy","callcompletioncancel","callcompletionrequest","celgenuserevent",
-                  "changemonitor","chanisavail","channelredirect","chanspy","clearhash","confbridge",
-                  "congestion","continuewhile","controlplayback","dahdiacceptr2call","dahdibarge",
-                  "dahdiras","dahdiscan","dahdisendcallreroutingfacility","dahdisendkeypadfacility",
-                  "datetime","dbdel","dbdeltree","deadagi","dial","dictate","directory","disa",
-                  "dumpchan","eagi","echo","endwhile","exec","execif","execiftime","exitwhile","extenspy",
-                  "externalivr","festival","flash","followme","forkcdr","getcpeid","gosub","gosubif",
-                  "goto","gotoif","gotoiftime","hangup","iax2provision","ices","importvar","incomplete",
-                  "ivrdemo","jabberjoin","jabberleave","jabbersend","jabbersendgroup","jabberstatus",
-                  "jack","log","macro","macroexclusive","macroexit","macroif","mailboxexists","meetme",
-                  "meetmeadmin","meetmechanneladmin","meetmecount","milliwatt","minivmaccmess","minivmdelete",
-                  "minivmgreet","minivmmwi","minivmnotify","minivmrecord","mixmonitor","monitor","morsecode",
-                  "mp3player","mset","musiconhold","nbscat","nocdr","noop","odbc","odbc","odbcfinish",
-                  "originate","ospauth","ospfinish","osplookup","ospnext","page","park","parkandannounce",
-                  "parkedcall","pausemonitor","pausequeuemember","pickup","pickupchan","playback","playtones",
-                  "privacymanager","proceeding","progress","queue","queuelog","raiseexception","read","readexten",
-                  "readfile","receivefax","receivefax","receivefax","record","removequeuemember",
-                  "resetcdr","retrydial","return","ringing","sayalpha","saycountedadj","saycountednoun",
-                  "saycountpl","saydigits","saynumber","sayphonetic","sayunixtime","senddtmf","sendfax",
-                  "sendfax","sendfax","sendimage","sendtext","sendurl","set","setamaflags",
-                  "setcallerpres","setmusiconhold","sipaddheader","sipdtmfmode","sipremoveheader","skel",
-                  "slastation","slatrunk","sms","softhangup","speechactivategrammar","speechbackground",
-                  "speechcreate","speechdeactivategrammar","speechdestroy","speechloadgrammar","speechprocessingsound",
-                  "speechstart","speechunloadgrammar","stackpop","startmusiconhold","stopmixmonitor","stopmonitor",
-                  "stopmusiconhold","stopplaytones","system","testclient","testserver","transfer","tryexec",
-                  "trysystem","unpausemonitor","unpausequeuemember","userevent","verbose","vmauthenticate",
-                  "vmsayname","voicemail","voicemailmain","wait","waitexten","waitfornoise","waitforring",
-                  "waitforsilence","waitmusiconhold","waituntil","while","zapateller"
-                 ];
-
-  function basicToken(stream,state){
-    var cur = '';
-    var ch  = '';
-    ch = stream.next();
-    // comment
-    if(ch == ";") {
-      stream.skipToEnd();
-      return "comment";
-    }
-    // context
-    if(ch == '[') {
-      stream.skipTo(']');
-      stream.eat(']');
-      return "header";
-    }
-    // string
-    if(ch == '"') {
-      stream.skipTo('"');
-      return "string";
-    }
-    if(ch == "'") {
-      stream.skipTo("'");
-      return "string-2";
-    }
-    // dialplan commands
-    if(ch == '#') {
-      stream.eatWhile(/\w/);
-      cur = stream.current();
-      if(dpcmd.indexOf(cur) !== -1) {
-        stream.skipToEnd();
-        return "strong";
-      }
-    }
-    // application args
-    if(ch == '$'){
-      var ch1 = stream.peek();
-      if(ch1 == '{'){
-        stream.skipTo('}');
-        stream.eat('}');
-        return "variable-3";
-      }
-    }
-    // extension
-    stream.eatWhile(/\w/);
-    cur = stream.current();
-    if(atoms.indexOf(cur) !== -1) {
-      state.extenStart = true;
-      switch(cur) {
-        case 'same': state.extenSame = true; break;
-        case 'include':
-        case 'switch':
-        case 'ignorepat':
-          state.extenInclude = true;break;
-        default:break;
-      }
-      return "atom";
-    }
-  }
-
-  return {
-    startState: function() {
-      return {
-        extenStart: false,
-        extenSame:  false,
-        extenInclude: false,
-        extenExten: false,
-        extenPriority: false,
-        extenApplication: false
-      };
-    },
-    token: function(stream, state) {
-
-      var cur = '';
-      var ch  = '';
-      if(stream.eatSpace()) return null;
-      // extension started
-      if(state.extenStart){
-        stream.eatWhile(/[^\s]/);
-        cur = stream.current();
-        if(/^=>?$/.test(cur)){
-          state.extenExten = true;
-          state.extenStart = false;
-          return "strong";
-        } else {
-          state.extenStart = false;
-          stream.skipToEnd();
-          return "error";
-        }
-      } else if(state.extenExten) {
-        // set exten and priority
-        state.extenExten = false;
-        state.extenPriority = true;
-        stream.eatWhile(/[^,]/);
-        if(state.extenInclude) {
-          stream.skipToEnd();
-          state.extenPriority = false;
-          state.extenInclude = false;
-        }
-        if(state.extenSame) {
-          state.extenPriority = false;
-          state.extenSame = false;
-          state.extenApplication = true;
-        }
-        return "tag";
-      } else if(state.extenPriority) {
-        state.extenPriority = false;
-        state.extenApplication = true;
-        ch = stream.next(); // get comma
-        if(state.extenSame) return null;
-        stream.eatWhile(/[^,]/);
-        return "number";
-      } else if(state.extenApplication) {
-        stream.eatWhile(/,/);
-        cur = stream.current();
-        if(cur === ',') return null;
-        stream.eatWhile(/\w/);
-        cur = stream.current().toLowerCase();
-        state.extenApplication = false;
-        if(apps.indexOf(cur) !== -1){
-          return "def strong";
-        }
-      } else{
-        return basicToken(stream,state);
-      }
-
-      return null;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-asterisk", "asterisk");
-
-});
--- a/kallithea/public/codemirror/mode/clike/clike.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,474 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("clike", function(config, parserConfig) {
-  var indentUnit = config.indentUnit,
-      statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
-      dontAlignCalls = parserConfig.dontAlignCalls,
-      keywords = parserConfig.keywords || {},
-      builtin = parserConfig.builtin || {},
-      blockKeywords = parserConfig.blockKeywords || {},
-      atoms = parserConfig.atoms || {},
-      hooks = parserConfig.hooks || {},
-      multiLineStrings = parserConfig.multiLineStrings,
-      indentStatements = parserConfig.indentStatements !== false;
-  var isOperatorChar = /[+\-*&%=<>!?|\/]/;
-
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (hooks[ch]) {
-      var result = hooks[ch](stream, state);
-      if (result !== false) return result;
-    }
-    if (ch == '"' || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_\xa1-\uffff]/);
-    var cur = stream.current();
-    if (keywords.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "keyword";
-    }
-    if (builtin.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "builtin";
-    }
-    if (atoms.propertyIsEnumerable(cur)) return "atom";
-    return "variable";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || multiLineStrings))
-        state.tokenize = null;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    var indent = state.indented;
-    if (state.context && state.context.type == "statement")
-      indent = state.context.indented;
-    return state.context = new Context(indent, col, type, null, state.context);
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      return {
-        tokenize: null,
-        context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
-      else if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "}") {
-        while (ctx.type == "statement") ctx = popContext(state);
-        if (ctx.type == "}") ctx = popContext(state);
-        while (ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == ctx.type) popContext(state);
-      else if (indentStatements &&
-               (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') ||
-                (ctx.type == "statement" && curPunc == "newstatement")))
-        pushContext(state, stream.column(), "statement");
-      state.startOfLine = false;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
-      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
-      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
-      var closing = firstChar == ctx.type;
-      if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
-      else if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1);
-      else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
-      else return ctx.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricChars: "{}",
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: "//",
-    fold: "brace"
-  };
-});
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
-    "double static else struct entry switch extern typedef float union for unsigned " +
-    "goto while enum void const signed volatile";
-
-  function cppHook(stream, state) {
-    if (!state.startOfLine) return false;
-    for (;;) {
-      if (stream.skipTo("\\")) {
-        stream.next();
-        if (stream.eol()) {
-          state.tokenize = cppHook;
-          break;
-        }
-      } else {
-        stream.skipToEnd();
-        state.tokenize = null;
-        break;
-      }
-    }
-    return "meta";
-  }
-
-  function cpp11StringHook(stream, state) {
-    stream.backUp(1);
-    // Raw strings.
-    if (stream.match(/(R|u8R|uR|UR|LR)/)) {
-      var match = stream.match(/"([^\s\\()]{0,16})\(/);
-      if (!match) {
-        return false;
-      }
-      state.cpp11RawStringDelim = match[1];
-      state.tokenize = tokenRawString;
-      return tokenRawString(stream, state);
-    }
-    // Unicode strings/chars.
-    if (stream.match(/(u8|u|U|L)/)) {
-      if (stream.match(/["']/, /* eat */ false)) {
-        return "string";
-      }
-      return false;
-    }
-    // Ignore this hook.
-    stream.next();
-    return false;
-  }
-
-  // C#-style strings where "" escapes a quote.
-  function tokenAtString(stream, state) {
-    var next;
-    while ((next = stream.next()) != null) {
-      if (next == '"' && !stream.eat('"')) {
-        state.tokenize = null;
-        break;
-      }
-    }
-    return "string";
-  }
-
-  // C++11 raw string literal is <prefix>"<delim>( anything )<delim>", where
-  // <delim> can be a string up to 16 characters long.
-  function tokenRawString(stream, state) {
-    // Escape characters that have special regex meanings.
-    var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&');
-    var match = stream.match(new RegExp(".*?\\)" + delim + '"'));
-    if (match)
-      state.tokenize = null;
-    else
-      stream.skipToEnd();
-    return "string";
-  }
-
-  function def(mimes, mode) {
-    if (typeof mimes == "string") mimes = [mimes];
-    var words = [];
-    function add(obj) {
-      if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop))
-        words.push(prop);
-    }
-    add(mode.keywords);
-    add(mode.builtin);
-    add(mode.atoms);
-    if (words.length) {
-      mode.helperType = mimes[0];
-      CodeMirror.registerHelper("hintWords", mimes[0], words);
-    }
-
-    for (var i = 0; i < mimes.length; ++i)
-      CodeMirror.defineMIME(mimes[i], mode);
-  }
-
-  def(["text/x-csrc", "text/x-c", "text/x-chdr"], {
-    name: "clike",
-    keywords: words(cKeywords),
-    blockKeywords: words("case do else for if switch while struct"),
-    atoms: words("null"),
-    hooks: {"#": cppHook},
-    modeProps: {fold: ["brace", "include"]}
-  });
-
-  def(["text/x-c++src", "text/x-c++hdr"], {
-    name: "clike",
-    keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
-                    "static_cast typeid catch operator template typename class friend private " +
-                    "this using const_cast inline public throw virtual delete mutable protected " +
-                    "wchar_t alignas alignof constexpr decltype nullptr noexcept thread_local final " +
-                    "static_assert override"),
-    blockKeywords: words("catch class do else finally for if struct switch try while"),
-    atoms: words("true false null"),
-    hooks: {
-      "#": cppHook,
-      "u": cpp11StringHook,
-      "U": cpp11StringHook,
-      "L": cpp11StringHook,
-      "R": cpp11StringHook
-    },
-    modeProps: {fold: ["brace", "include"]}
-  });
-
-  def("text/x-java", {
-    name: "clike",
-    keywords: words("abstract assert boolean break byte case catch char class const continue default " +
-                    "do double else enum extends final finally float for goto if implements import " +
-                    "instanceof int interface long native new package private protected public " +
-                    "return short static strictfp super switch synchronized this throw throws transient " +
-                    "try void volatile while"),
-    blockKeywords: words("catch class do else finally for if switch try while"),
-    atoms: words("true false null"),
-    hooks: {
-      "@": function(stream) {
-        stream.eatWhile(/[\w\$_]/);
-        return "meta";
-      }
-    },
-    modeProps: {fold: ["brace", "import"]}
-  });
-
-  def("text/x-csharp", {
-    name: "clike",
-    keywords: words("abstract as base break case catch checked class const continue" +
-                    " default delegate do else enum event explicit extern finally fixed for" +
-                    " foreach goto if implicit in interface internal is lock namespace new" +
-                    " operator out override params private protected public readonly ref return sealed" +
-                    " sizeof stackalloc static struct switch this throw try typeof unchecked" +
-                    " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
-                    " global group into join let orderby partial remove select set value var yield"),
-    blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
-    builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
-                    " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
-                    " UInt64 bool byte char decimal double short int long object"  +
-                    " sbyte float string ushort uint ulong"),
-    atoms: words("true false null"),
-    hooks: {
-      "@": function(stream, state) {
-        if (stream.eat('"')) {
-          state.tokenize = tokenAtString;
-          return tokenAtString(stream, state);
-        }
-        stream.eatWhile(/[\w\$_]/);
-        return "meta";
-      }
-    }
-  });
-
-  function tokenTripleString(stream, state) {
-    var escaped = false;
-    while (!stream.eol()) {
-      if (!escaped && stream.match('"""')) {
-        state.tokenize = null;
-        break;
-      }
-      escaped = stream.next() != "\\" && !escaped;
-    }
-    return "string";
-  }
-
-  def("text/x-scala", {
-    name: "clike",
-    keywords: words(
-
-      /* scala */
-      "abstract case catch class def do else extends false final finally for forSome if " +
-      "implicit import lazy match new null object override package private protected return " +
-      "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
-      "<% >: # @ " +
-
-      /* package scala */
-      "assert assume require print println printf readLine readBoolean readByte readShort " +
-      "readChar readInt readLong readFloat readDouble " +
-
-      "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
-      "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
-      "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
-      "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
-      "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
-
-      /* package java.lang */
-      "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
-      "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
-      "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
-      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
-    ),
-    multiLineStrings: true,
-    blockKeywords: words("catch class do else finally for forSome if match switch try while"),
-    atoms: words("true false null"),
-    indentStatements: false,
-    hooks: {
-      "@": function(stream) {
-        stream.eatWhile(/[\w\$_]/);
-        return "meta";
-      },
-      '"': function(stream, state) {
-        if (!stream.match('""')) return false;
-        state.tokenize = tokenTripleString;
-        return state.tokenize(stream, state);
-      }
-    }
-  });
-
-  def(["x-shader/x-vertex", "x-shader/x-fragment"], {
-    name: "clike",
-    keywords: words("float int bool void " +
-                    "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
-                    "mat2 mat3 mat4 " +
-                    "sampler1D sampler2D sampler3D samplerCube " +
-                    "sampler1DShadow sampler2DShadow" +
-                    "const attribute uniform varying " +
-                    "break continue discard return " +
-                    "for while do if else struct " +
-                    "in out inout"),
-    blockKeywords: words("for while do if else struct"),
-    builtin: words("radians degrees sin cos tan asin acos atan " +
-                    "pow exp log exp2 sqrt inversesqrt " +
-                    "abs sign floor ceil fract mod min max clamp mix step smootstep " +
-                    "length distance dot cross normalize ftransform faceforward " +
-                    "reflect refract matrixCompMult " +
-                    "lessThan lessThanEqual greaterThan greaterThanEqual " +
-                    "equal notEqual any all not " +
-                    "texture1D texture1DProj texture1DLod texture1DProjLod " +
-                    "texture2D texture2DProj texture2DLod texture2DProjLod " +
-                    "texture3D texture3DProj texture3DLod texture3DProjLod " +
-                    "textureCube textureCubeLod " +
-                    "shadow1D shadow2D shadow1DProj shadow2DProj " +
-                    "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " +
-                    "dFdx dFdy fwidth " +
-                    "noise1 noise2 noise3 noise4"),
-    atoms: words("true false " +
-                "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " +
-                "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " +
-                "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " +
-                "gl_FogCoord " +
-                "gl_Position gl_PointSize gl_ClipVertex " +
-                "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " +
-                "gl_TexCoord gl_FogFragCoord " +
-                "gl_FragCoord gl_FrontFacing " +
-                "gl_FragColor gl_FragData gl_FragDepth " +
-                "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
-                "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
-                "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
-                "gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " +
-                "gl_ProjectionMatrixInverseTranspose " +
-                "gl_ModelViewProjectionMatrixInverseTranspose " +
-                "gl_TextureMatrixInverseTranspose " +
-                "gl_NormalScale gl_DepthRange gl_ClipPlane " +
-                "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " +
-                "gl_FrontLightModelProduct gl_BackLightModelProduct " +
-                "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " +
-                "gl_FogParameters " +
-                "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " +
-                "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " +
-                "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
-                "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
-                "gl_MaxDrawBuffers"),
-    hooks: {"#": cppHook},
-    modeProps: {fold: ["brace", "include"]}
-  });
-
-  def("text/x-nesc", {
-    name: "clike",
-    keywords: words(cKeywords + "as atomic async call command component components configuration event generic " +
-                    "implementation includes interface module new norace nx_struct nx_union post provides " +
-                    "signal task uses abstract extends"),
-    blockKeywords: words("case do else for if switch while struct"),
-    atoms: words("null"),
-    hooks: {"#": cppHook},
-    modeProps: {fold: ["brace", "include"]}
-  });
-
-});
--- a/kallithea/public/codemirror/mode/clojure/clojure.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,243 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Author: Hans Engel
- * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("clojure", function (options) {
-    var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
-        ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword", VAR = "variable";
-    var INDENT_WORD_SKIP = options.indentUnit || 2;
-    var NORMAL_INDENT_UNIT = options.indentUnit || 2;
-
-    function makeKeywords(str) {
-        var obj = {}, words = str.split(" ");
-        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-        return obj;
-    }
-
-    var atoms = makeKeywords("true false nil");
-
-    var keywords = makeKeywords(
-      "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
-
-    var builtins = makeKeywords(
-        "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->>");
-
-    var indentKeys = makeKeywords(
-        // Built-ins
-        "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +
-
-        // Binding forms
-        "let letfn binding loop for doseq dotimes when-let if-let " +
-
-        // Data structures
-        "defstruct struct-map assoc " +
-
-        // clojure.test
-        "testing deftest " +
-
-        // contrib
-        "handler-case handle dotrace deftrace");
-
-    var tests = {
-        digit: /\d/,
-        digit_or_colon: /[\d:]/,
-        hex: /[0-9a-f]/i,
-        sign: /[+-]/,
-        exponent: /e/i,
-        keyword_char: /[^\s\(\[\;\)\]]/,
-        symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/
-    };
-
-    function stateStack(indent, type, prev) { // represents a state stack object
-        this.indent = indent;
-        this.type = type;
-        this.prev = prev;
-    }
-
-    function pushStack(state, indent, type) {
-        state.indentStack = new stateStack(indent, type, state.indentStack);
-    }
-
-    function popStack(state) {
-        state.indentStack = state.indentStack.prev;
-    }
-
-    function isNumber(ch, stream){
-        // hex
-        if ( ch === '0' && stream.eat(/x/i) ) {
-            stream.eatWhile(tests.hex);
-            return true;
-        }
-
-        // leading sign
-        if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
-          stream.eat(tests.sign);
-          ch = stream.next();
-        }
-
-        if ( tests.digit.test(ch) ) {
-            stream.eat(ch);
-            stream.eatWhile(tests.digit);
-
-            if ( '.' == stream.peek() ) {
-                stream.eat('.');
-                stream.eatWhile(tests.digit);
-            }
-
-            if ( stream.eat(tests.exponent) ) {
-                stream.eat(tests.sign);
-                stream.eatWhile(tests.digit);
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    // Eat character that starts after backslash \
-    function eatCharacter(stream) {
-        var first = stream.next();
-        // Read special literals: backspace, newline, space, return.
-        // Just read all lowercase letters.
-        if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
-            return;
-        }
-        // Read unicode character: \u1000 \uA0a1
-        if (first === "u") {
-            stream.match(/[0-9a-z]{4}/i, true);
-        }
-    }
-
-    return {
-        startState: function () {
-            return {
-                indentStack: null,
-                indentation: 0,
-                mode: false
-            };
-        },
-
-        token: function (stream, state) {
-            if (state.indentStack == null && stream.sol()) {
-                // update indentation, but only if indentStack is empty
-                state.indentation = stream.indentation();
-            }
-
-            // skip spaces
-            if (stream.eatSpace()) {
-                return null;
-            }
-            var returnType = null;
-
-            switch(state.mode){
-                case "string": // multi-line string parsing mode
-                    var next, escaped = false;
-                    while ((next = stream.next()) != null) {
-                        if (next == "\"" && !escaped) {
-
-                            state.mode = false;
-                            break;
-                        }
-                        escaped = !escaped && next == "\\";
-                    }
-                    returnType = STRING; // continue on in string mode
-                    break;
-                default: // default parsing mode
-                    var ch = stream.next();
-
-                    if (ch == "\"") {
-                        state.mode = "string";
-                        returnType = STRING;
-                    } else if (ch == "\\") {
-                        eatCharacter(stream);
-                        returnType = CHARACTER;
-                    } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
-                        returnType = ATOM;
-                    } else if (ch == ";") { // comment
-                        stream.skipToEnd(); // rest of the line is a comment
-                        returnType = COMMENT;
-                    } else if (isNumber(ch,stream)){
-                        returnType = NUMBER;
-                    } else if (ch == "(" || ch == "[" || ch == "{" ) {
-                        var keyWord = '', indentTemp = stream.column(), letter;
-                        /**
-                        Either
-                        (indent-word ..
-                        (non-indent-word ..
-                        (;something else, bracket, etc.
-                        */
-
-                        if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
-                            keyWord += letter;
-                        }
-
-                        if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
-                                                   /^(?:def|with)/.test(keyWord))) { // indent-word
-                            pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
-                        } else { // non-indent word
-                            // we continue eating the spaces
-                            stream.eatSpace();
-                            if (stream.eol() || stream.peek() == ";") {
-                                // nothing significant after
-                                // we restart indentation the user defined spaces after
-                                pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
-                            } else {
-                                pushStack(state, indentTemp + stream.current().length, ch); // else we match
-                            }
-                        }
-                        stream.backUp(stream.current().length - 1); // undo all the eating
-
-                        returnType = BRACKET;
-                    } else if (ch == ")" || ch == "]" || ch == "}") {
-                        returnType = BRACKET;
-                        if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
-                            popStack(state);
-                        }
-                    } else if ( ch == ":" ) {
-                        stream.eatWhile(tests.symbol);
-                        return ATOM;
-                    } else {
-                        stream.eatWhile(tests.symbol);
-
-                        if (keywords && keywords.propertyIsEnumerable(stream.current())) {
-                            returnType = KEYWORD;
-                        } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
-                            returnType = BUILTIN;
-                        } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
-                            returnType = ATOM;
-                        } else {
-                          returnType = VAR;
-                        }
-                    }
-            }
-
-            return returnType;
-        },
-
-        indent: function (state) {
-            if (state.indentStack == null) return state.indentation;
-            return state.indentStack.indent;
-        },
-
-        lineComment: ";;"
-    };
-});
-
-CodeMirror.defineMIME("text/x-clojure", "clojure");
-
-});
--- a/kallithea/public/codemirror/mode/cobol/cobol.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,255 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Author: Gautam Mehta
- * Branched from CodeMirror's Scheme mode
- */
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("cobol", function () {
-  var BUILTIN = "builtin", COMMENT = "comment", STRING = "string",
-      ATOM = "atom", NUMBER = "number", KEYWORD = "keyword", MODTAG = "header",
-      COBOLLINENUM = "def", PERIOD = "link";
-  function makeKeywords(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var atoms = makeKeywords("TRUE FALSE ZEROES ZEROS ZERO SPACES SPACE LOW-VALUE LOW-VALUES ");
-  var keywords = makeKeywords(
-      "ACCEPT ACCESS ACQUIRE ADD ADDRESS " +
-      "ADVANCING AFTER ALIAS ALL ALPHABET " +
-      "ALPHABETIC ALPHABETIC-LOWER ALPHABETIC-UPPER ALPHANUMERIC ALPHANUMERIC-EDITED " +
-      "ALSO ALTER ALTERNATE AND ANY " +
-      "ARE AREA AREAS ARITHMETIC ASCENDING " +
-      "ASSIGN AT ATTRIBUTE AUTHOR AUTO " +
-      "AUTO-SKIP AUTOMATIC B-AND B-EXOR B-LESS " +
-      "B-NOT B-OR BACKGROUND-COLOR BACKGROUND-COLOUR BEEP " +
-      "BEFORE BELL BINARY BIT BITS " +
-      "BLANK BLINK BLOCK BOOLEAN BOTTOM " +
-      "BY CALL CANCEL CD CF " +
-      "CH CHARACTER CHARACTERS CLASS CLOCK-UNITS " +
-      "CLOSE COBOL CODE CODE-SET COL " +
-      "COLLATING COLUMN COMMA COMMIT COMMITMENT " +
-      "COMMON COMMUNICATION COMP COMP-0 COMP-1 " +
-      "COMP-2 COMP-3 COMP-4 COMP-5 COMP-6 " +
-      "COMP-7 COMP-8 COMP-9 COMPUTATIONAL COMPUTATIONAL-0 " +
-      "COMPUTATIONAL-1 COMPUTATIONAL-2 COMPUTATIONAL-3 COMPUTATIONAL-4 COMPUTATIONAL-5 " +
-      "COMPUTATIONAL-6 COMPUTATIONAL-7 COMPUTATIONAL-8 COMPUTATIONAL-9 COMPUTE " +
-      "CONFIGURATION CONNECT CONSOLE CONTAINED CONTAINS " +
-      "CONTENT CONTINUE CONTROL CONTROL-AREA CONTROLS " +
-      "CONVERTING COPY CORR CORRESPONDING COUNT " +
-      "CRT CRT-UNDER CURRENCY CURRENT CURSOR " +
-      "DATA DATE DATE-COMPILED DATE-WRITTEN DAY " +
-      "DAY-OF-WEEK DB DB-ACCESS-CONTROL-KEY DB-DATA-NAME DB-EXCEPTION " +
-      "DB-FORMAT-NAME DB-RECORD-NAME DB-SET-NAME DB-STATUS DBCS " +
-      "DBCS-EDITED DE DEBUG-CONTENTS DEBUG-ITEM DEBUG-LINE " +
-      "DEBUG-NAME DEBUG-SUB-1 DEBUG-SUB-2 DEBUG-SUB-3 DEBUGGING " +
-      "DECIMAL-POINT DECLARATIVES DEFAULT DELETE DELIMITED " +
-      "DELIMITER DEPENDING DESCENDING DESCRIBED DESTINATION " +
-      "DETAIL DISABLE DISCONNECT DISPLAY DISPLAY-1 " +
-      "DISPLAY-2 DISPLAY-3 DISPLAY-4 DISPLAY-5 DISPLAY-6 " +
-      "DISPLAY-7 DISPLAY-8 DISPLAY-9 DIVIDE DIVISION " +
-      "DOWN DROP DUPLICATE DUPLICATES DYNAMIC " +
-      "EBCDIC EGI EJECT ELSE EMI " +
-      "EMPTY EMPTY-CHECK ENABLE END END. END-ACCEPT END-ACCEPT. " +
-      "END-ADD END-CALL END-COMPUTE END-DELETE END-DISPLAY " +
-      "END-DIVIDE END-EVALUATE END-IF END-INVOKE END-MULTIPLY " +
-      "END-OF-PAGE END-PERFORM END-READ END-RECEIVE END-RETURN " +
-      "END-REWRITE END-SEARCH END-START END-STRING END-SUBTRACT " +
-      "END-UNSTRING END-WRITE END-XML ENTER ENTRY " +
-      "ENVIRONMENT EOP EQUAL EQUALS ERASE " +
-      "ERROR ESI EVALUATE EVERY EXCEEDS " +
-      "EXCEPTION EXCLUSIVE EXIT EXTEND EXTERNAL " +
-      "EXTERNALLY-DESCRIBED-KEY FD FETCH FILE FILE-CONTROL " +
-      "FILE-STREAM FILES FILLER FINAL FIND " +
-      "FINISH FIRST FOOTING FOR FOREGROUND-COLOR " +
-      "FOREGROUND-COLOUR FORMAT FREE FROM FULL " +
-      "FUNCTION GENERATE GET GIVING GLOBAL " +
-      "GO GOBACK GREATER GROUP HEADING " +
-      "HIGH-VALUE HIGH-VALUES HIGHLIGHT I-O I-O-CONTROL " +
-      "ID IDENTIFICATION IF IN INDEX " +
-      "INDEX-1 INDEX-2 INDEX-3 INDEX-4 INDEX-5 " +
-      "INDEX-6 INDEX-7 INDEX-8 INDEX-9 INDEXED " +
-      "INDIC INDICATE INDICATOR INDICATORS INITIAL " +
-      "INITIALIZE INITIATE INPUT INPUT-OUTPUT INSPECT " +
-      "INSTALLATION INTO INVALID INVOKE IS " +
-      "JUST JUSTIFIED KANJI KEEP KEY " +
-      "LABEL LAST LD LEADING LEFT " +
-      "LEFT-JUSTIFY LENGTH LENGTH-CHECK LESS LIBRARY " +
-      "LIKE LIMIT LIMITS LINAGE LINAGE-COUNTER " +
-      "LINE LINE-COUNTER LINES LINKAGE LOCAL-STORAGE " +
-      "LOCALE LOCALLY LOCK " +
-      "MEMBER MEMORY MERGE MESSAGE METACLASS " +
-      "MODE MODIFIED MODIFY MODULES MOVE " +
-      "MULTIPLE MULTIPLY NATIONAL NATIVE NEGATIVE " +
-      "NEXT NO NO-ECHO NONE NOT " +
-      "NULL NULL-KEY-MAP NULL-MAP NULLS NUMBER " +
-      "NUMERIC NUMERIC-EDITED OBJECT OBJECT-COMPUTER OCCURS " +
-      "OF OFF OMITTED ON ONLY " +
-      "OPEN OPTIONAL OR ORDER ORGANIZATION " +
-      "OTHER OUTPUT OVERFLOW OWNER PACKED-DECIMAL " +
-      "PADDING PAGE PAGE-COUNTER PARSE PERFORM " +
-      "PF PH PIC PICTURE PLUS " +
-      "POINTER POSITION POSITIVE PREFIX PRESENT " +
-      "PRINTING PRIOR PROCEDURE PROCEDURE-POINTER PROCEDURES " +
-      "PROCEED PROCESS PROCESSING PROGRAM PROGRAM-ID " +
-      "PROMPT PROTECTED PURGE QUEUE QUOTE " +
-      "QUOTES RANDOM RD READ READY " +
-      "REALM RECEIVE RECONNECT RECORD RECORD-NAME " +
-      "RECORDS RECURSIVE REDEFINES REEL REFERENCE " +
-      "REFERENCE-MONITOR REFERENCES RELATION RELATIVE RELEASE " +
-      "REMAINDER REMOVAL RENAMES REPEATED REPLACE " +
-      "REPLACING REPORT REPORTING REPORTS REPOSITORY " +
-      "REQUIRED RERUN RESERVE RESET RETAINING " +
-      "RETRIEVAL RETURN RETURN-CODE RETURNING REVERSE-VIDEO " +
-      "REVERSED REWIND REWRITE RF RH " +
-      "RIGHT RIGHT-JUSTIFY ROLLBACK ROLLING ROUNDED " +
-      "RUN SAME SCREEN SD SEARCH " +
-      "SECTION SECURE SECURITY SEGMENT SEGMENT-LIMIT " +
-      "SELECT SEND SENTENCE SEPARATE SEQUENCE " +
-      "SEQUENTIAL SET SHARED SIGN SIZE " +
-      "SKIP1 SKIP2 SKIP3 SORT SORT-MERGE " +
-      "SORT-RETURN SOURCE SOURCE-COMPUTER SPACE-FILL " +
-      "SPECIAL-NAMES STANDARD STANDARD-1 STANDARD-2 " +
-      "START STARTING STATUS STOP STORE " +
-      "STRING SUB-QUEUE-1 SUB-QUEUE-2 SUB-QUEUE-3 SUB-SCHEMA " +
-      "SUBFILE SUBSTITUTE SUBTRACT SUM SUPPRESS " +
-      "SYMBOLIC SYNC SYNCHRONIZED SYSIN SYSOUT " +
-      "TABLE TALLYING TAPE TENANT TERMINAL " +
-      "TERMINATE TEST TEXT THAN THEN " +
-      "THROUGH THRU TIME TIMES TITLE " +
-      "TO TOP TRAILING TRAILING-SIGN TRANSACTION " +
-      "TYPE TYPEDEF UNDERLINE UNEQUAL UNIT " +
-      "UNSTRING UNTIL UP UPDATE UPON " +
-      "USAGE USAGE-MODE USE USING VALID " +
-      "VALIDATE VALUE VALUES VARYING VLR " +
-      "WAIT WHEN WHEN-COMPILED WITH WITHIN " +
-      "WORDS WORKING-STORAGE WRITE XML XML-CODE " +
-      "XML-EVENT XML-NTEXT XML-TEXT ZERO ZERO-FILL " );
-
-  var builtins = makeKeywords("- * ** / + < <= = > >= ");
-  var tests = {
-    digit: /\d/,
-    digit_or_colon: /[\d:]/,
-    hex: /[0-9a-f]/i,
-    sign: /[+-]/,
-    exponent: /e/i,
-    keyword_char: /[^\s\(\[\;\)\]]/,
-    symbol: /[\w*+\-]/
-  };
-  function isNumber(ch, stream){
-    // hex
-    if ( ch === '0' && stream.eat(/x/i) ) {
-      stream.eatWhile(tests.hex);
-      return true;
-    }
-    // leading sign
-    if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
-      stream.eat(tests.sign);
-      ch = stream.next();
-    }
-    if ( tests.digit.test(ch) ) {
-      stream.eat(ch);
-      stream.eatWhile(tests.digit);
-      if ( '.' == stream.peek()) {
-        stream.eat('.');
-        stream.eatWhile(tests.digit);
-      }
-      if ( stream.eat(tests.exponent) ) {
-        stream.eat(tests.sign);
-        stream.eatWhile(tests.digit);
-      }
-      return true;
-    }
-    return false;
-  }
-  return {
-    startState: function () {
-      return {
-        indentStack: null,
-        indentation: 0,
-        mode: false
-      };
-    },
-    token: function (stream, state) {
-      if (state.indentStack == null && stream.sol()) {
-        // update indentation, but only if indentStack is empty
-        state.indentation = 6 ; //stream.indentation();
-      }
-      // skip spaces
-      if (stream.eatSpace()) {
-        return null;
-      }
-      var returnType = null;
-      switch(state.mode){
-      case "string": // multi-line string parsing mode
-        var next = false;
-        while ((next = stream.next()) != null) {
-          if (next == "\"" || next == "\'") {
-            state.mode = false;
-            break;
-          }
-        }
-        returnType = STRING; // continue on in string mode
-        break;
-      default: // default parsing mode
-        var ch = stream.next();
-        var col = stream.column();
-        if (col >= 0 && col <= 5) {
-          returnType = COBOLLINENUM;
-        } else if (col >= 72 && col <= 79) {
-          stream.skipToEnd();
-          returnType = MODTAG;
-        } else if (ch == "*" && col == 6) { // comment
-          stream.skipToEnd(); // rest of the line is a comment
-          returnType = COMMENT;
-        } else if (ch == "\"" || ch == "\'") {
-          state.mode = "string";
-          returnType = STRING;
-        } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
-          returnType = ATOM;
-        } else if (ch == ".") {
-          returnType = PERIOD;
-        } else if (isNumber(ch,stream)){
-          returnType = NUMBER;
-        } else {
-          if (stream.current().match(tests.symbol)) {
-            while (col < 71) {
-              if (stream.eat(tests.symbol) === undefined) {
-                break;
-              } else {
-                col++;
-              }
-            }
-          }
-          if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) {
-            returnType = KEYWORD;
-          } else if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) {
-            returnType = BUILTIN;
-          } else if (atoms && atoms.propertyIsEnumerable(stream.current().toUpperCase())) {
-            returnType = ATOM;
-          } else returnType = null;
-        }
-      }
-      return returnType;
-    },
-    indent: function (state) {
-      if (state.indentStack == null) return state.indentation;
-      return state.indentStack.indent;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-cobol", "cobol");
-
-});
--- a/kallithea/public/codemirror/mode/coffeescript/coffeescript.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,369 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Link to the project's GitHub page:
- * https://github.com/pickhardt/coffeescript-codemirror-mode
- */
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("coffeescript", function(conf) {
-  var ERRORCLASS = "error";
-
-  function wordRegexp(words) {
-    return new RegExp("^((" + words.join(")|(") + "))\\b");
-  }
-
-  var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
-  var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
-  var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
-  var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
-
-  var wordOperators = wordRegexp(["and", "or", "not",
-                                  "is", "isnt", "in",
-                                  "instanceof", "typeof"]);
-  var indentKeywords = ["for", "while", "loop", "if", "unless", "else",
-                        "switch", "try", "catch", "finally", "class"];
-  var commonKeywords = ["break", "by", "continue", "debugger", "delete",
-                        "do", "in", "of", "new", "return", "then",
-                        "this", "@", "throw", "when", "until", "extends"];
-
-  var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
-
-  indentKeywords = wordRegexp(indentKeywords);
-
-
-  var stringPrefixes = /^('{3}|\"{3}|['\"])/;
-  var regexPrefixes = /^(\/{3}|\/)/;
-  var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"];
-  var constants = wordRegexp(commonConstants);
-
-  // Tokenizers
-  function tokenBase(stream, state) {
-    // Handle scope changes
-    if (stream.sol()) {
-      if (state.scope.align === null) state.scope.align = false;
-      var scopeOffset = state.scope.offset;
-      if (stream.eatSpace()) {
-        var lineOffset = stream.indentation();
-        if (lineOffset > scopeOffset && state.scope.type == "coffee") {
-          return "indent";
-        } else if (lineOffset < scopeOffset) {
-          return "dedent";
-        }
-        return null;
-      } else {
-        if (scopeOffset > 0) {
-          dedent(stream, state);
-        }
-      }
-    }
-    if (stream.eatSpace()) {
-      return null;
-    }
-
-    var ch = stream.peek();
-
-    // Handle docco title comment (single line)
-    if (stream.match("####")) {
-      stream.skipToEnd();
-      return "comment";
-    }
-
-    // Handle multi line comments
-    if (stream.match("###")) {
-      state.tokenize = longComment;
-      return state.tokenize(stream, state);
-    }
-
-    // Single line comment
-    if (ch === "#") {
-      stream.skipToEnd();
-      return "comment";
-    }
-
-    // Handle number literals
-    if (stream.match(/^-?[0-9\.]/, false)) {
-      var floatLiteral = false;
-      // Floats
-      if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
-        floatLiteral = true;
-      }
-      if (stream.match(/^-?\d+\.\d*/)) {
-        floatLiteral = true;
-      }
-      if (stream.match(/^-?\.\d+/)) {
-        floatLiteral = true;
-      }
-
-      if (floatLiteral) {
-        // prevent from getting extra . on 1..
-        if (stream.peek() == "."){
-          stream.backUp(1);
-        }
-        return "number";
-      }
-      // Integers
-      var intLiteral = false;
-      // Hex
-      if (stream.match(/^-?0x[0-9a-f]+/i)) {
-        intLiteral = true;
-      }
-      // Decimal
-      if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
-        intLiteral = true;
-      }
-      // Zero by itself with no other piece of number.
-      if (stream.match(/^-?0(?![\dx])/i)) {
-        intLiteral = true;
-      }
-      if (intLiteral) {
-        return "number";
-      }
-    }
-
-    // Handle strings
-    if (stream.match(stringPrefixes)) {
-      state.tokenize = tokenFactory(stream.current(), false, "string");
-      return state.tokenize(stream, state);
-    }
-    // Handle regex literals
-    if (stream.match(regexPrefixes)) {
-      if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division
-        state.tokenize = tokenFactory(stream.current(), true, "string-2");
-        return state.tokenize(stream, state);
-      } else {
-        stream.backUp(1);
-      }
-    }
-
-    // Handle operators and delimiters
-    if (stream.match(operators) || stream.match(wordOperators)) {
-      return "operator";
-    }
-    if (stream.match(delimiters)) {
-      return "punctuation";
-    }
-
-    if (stream.match(constants)) {
-      return "atom";
-    }
-
-    if (stream.match(keywords)) {
-      return "keyword";
-    }
-
-    if (stream.match(identifiers)) {
-      return "variable";
-    }
-
-    if (stream.match(properties)) {
-      return "property";
-    }
-
-    // Handle non-detected items
-    stream.next();
-    return ERRORCLASS;
-  }
-
-  function tokenFactory(delimiter, singleline, outclass) {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        stream.eatWhile(/[^'"\/\\]/);
-        if (stream.eat("\\")) {
-          stream.next();
-          if (singleline && stream.eol()) {
-            return outclass;
-          }
-        } else if (stream.match(delimiter)) {
-          state.tokenize = tokenBase;
-          return outclass;
-        } else {
-          stream.eat(/['"\/]/);
-        }
-      }
-      if (singleline) {
-        if (conf.mode.singleLineStringErrors) {
-          outclass = ERRORCLASS;
-        } else {
-          state.tokenize = tokenBase;
-        }
-      }
-      return outclass;
-    };
-  }
-
-  function longComment(stream, state) {
-    while (!stream.eol()) {
-      stream.eatWhile(/[^#]/);
-      if (stream.match("###")) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      stream.eatWhile("#");
-    }
-    return "comment";
-  }
-
-  function indent(stream, state, type) {
-    type = type || "coffee";
-    var offset = 0, align = false, alignOffset = null;
-    for (var scope = state.scope; scope; scope = scope.prev) {
-      if (scope.type === "coffee" || scope.type == "}") {
-        offset = scope.offset + conf.indentUnit;
-        break;
-      }
-    }
-    if (type !== "coffee") {
-      align = null;
-      alignOffset = stream.column() + stream.current().length;
-    } else if (state.scope.align) {
-      state.scope.align = false;
-    }
-    state.scope = {
-      offset: offset,
-      type: type,
-      prev: state.scope,
-      align: align,
-      alignOffset: alignOffset
-    };
-  }
-
-  function dedent(stream, state) {
-    if (!state.scope.prev) return;
-    if (state.scope.type === "coffee") {
-      var _indent = stream.indentation();
-      var matched = false;
-      for (var scope = state.scope; scope; scope = scope.prev) {
-        if (_indent === scope.offset) {
-          matched = true;
-          break;
-        }
-      }
-      if (!matched) {
-        return true;
-      }
-      while (state.scope.prev && state.scope.offset !== _indent) {
-        state.scope = state.scope.prev;
-      }
-      return false;
-    } else {
-      state.scope = state.scope.prev;
-      return false;
-    }
-  }
-
-  function tokenLexer(stream, state) {
-    var style = state.tokenize(stream, state);
-    var current = stream.current();
-
-    // Handle "." connected identifiers
-    if (current === ".") {
-      style = state.tokenize(stream, state);
-      current = stream.current();
-      if (/^\.[\w$]+$/.test(current)) {
-        return "variable";
-      } else {
-        return ERRORCLASS;
-      }
-    }
-
-    // Handle scope changes.
-    if (current === "return") {
-      state.dedent = true;
-    }
-    if (((current === "->" || current === "=>") &&
-         !state.lambda &&
-         !stream.peek())
-        || style === "indent") {
-      indent(stream, state);
-    }
-    var delimiter_index = "[({".indexOf(current);
-    if (delimiter_index !== -1) {
-      indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
-    }
-    if (indentKeywords.exec(current)){
-      indent(stream, state);
-    }
-    if (current == "then"){
-      dedent(stream, state);
-    }
-
-
-    if (style === "dedent") {
-      if (dedent(stream, state)) {
-        return ERRORCLASS;
-      }
-    }
-    delimiter_index = "])}".indexOf(current);
-    if (delimiter_index !== -1) {
-      while (state.scope.type == "coffee" && state.scope.prev)
-        state.scope = state.scope.prev;
-      if (state.scope.type == current)
-        state.scope = state.scope.prev;
-    }
-    if (state.dedent && stream.eol()) {
-      if (state.scope.type == "coffee" && state.scope.prev)
-        state.scope = state.scope.prev;
-      state.dedent = false;
-    }
-
-    return style;
-  }
-
-  var external = {
-    startState: function(basecolumn) {
-      return {
-        tokenize: tokenBase,
-        scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
-        lastToken: null,
-        lambda: false,
-        dedent: 0
-      };
-    },
-
-    token: function(stream, state) {
-      var fillAlign = state.scope.align === null && state.scope;
-      if (fillAlign && stream.sol()) fillAlign.align = false;
-
-      var style = tokenLexer(stream, state);
-      if (fillAlign && style && style != "comment") fillAlign.align = true;
-
-      state.lastToken = {style:style, content: stream.current()};
-
-      if (stream.eol() && stream.lambda) {
-        state.lambda = false;
-      }
-
-      return style;
-    },
-
-    indent: function(state, text) {
-      if (state.tokenize != tokenBase) return 0;
-      var scope = state.scope;
-      var closer = text && "])}".indexOf(text.charAt(0)) > -1;
-      if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev;
-      var closes = closer && scope.type === text.charAt(0);
-      if (scope.align)
-        return scope.alignOffset - (closes ? 1 : 0);
-      else
-        return (closes ? scope.prev : scope).offset;
-    },
-
-    lineComment: "#",
-    fold: "indent"
-  };
-  return external;
-});
-
-CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
-
-});
--- a/kallithea/public/codemirror/mode/commonlisp/commonlisp.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("commonlisp", function (config) {
-  var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/;
-  var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/;
-  var symbol = /[^\s'`,@()\[\]";]/;
-  var type;
-
-  function readSym(stream) {
-    var ch;
-    while (ch = stream.next()) {
-      if (ch == "\\") stream.next();
-      else if (!symbol.test(ch)) { stream.backUp(1); break; }
-    }
-    return stream.current();
-  }
-
-  function base(stream, state) {
-    if (stream.eatSpace()) {type = "ws"; return null;}
-    if (stream.match(numLiteral)) return "number";
-    var ch = stream.next();
-    if (ch == "\\") ch = stream.next();
-
-    if (ch == '"') return (state.tokenize = inString)(stream, state);
-    else if (ch == "(") { type = "open"; return "bracket"; }
-    else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; }
-    else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; }
-    else if (/['`,@]/.test(ch)) return null;
-    else if (ch == "|") {
-      if (stream.skipTo("|")) { stream.next(); return "symbol"; }
-      else { stream.skipToEnd(); return "error"; }
-    } else if (ch == "#") {
-      var ch = stream.next();
-      if (ch == "[") { type = "open"; return "bracket"; }
-      else if (/[+\-=\.']/.test(ch)) return null;
-      else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null;
-      else if (ch == "|") return (state.tokenize = inComment)(stream, state);
-      else if (ch == ":") { readSym(stream); return "meta"; }
-      else return "error";
-    } else {
-      var name = readSym(stream);
-      if (name == ".") return null;
-      type = "symbol";
-      if (name == "nil" || name == "t") return "atom";
-      if (name.charAt(0) == ":") return "keyword";
-      if (name.charAt(0) == "&") return "variable-2";
-      return "variable";
-    }
-  }
-
-  function inString(stream, state) {
-    var escaped = false, next;
-    while (next = stream.next()) {
-      if (next == '"' && !escaped) { state.tokenize = base; break; }
-      escaped = !escaped && next == "\\";
-    }
-    return "string";
-  }
-
-  function inComment(stream, state) {
-    var next, last;
-    while (next = stream.next()) {
-      if (next == "#" && last == "|") { state.tokenize = base; break; }
-      last = next;
-    }
-    type = "ws";
-    return "comment";
-  }
-
-  return {
-    startState: function () {
-      return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base};
-    },
-
-    token: function (stream, state) {
-      if (stream.sol() && typeof state.ctx.indentTo != "number")
-        state.ctx.indentTo = state.ctx.start + 1;
-
-      type = null;
-      var style = state.tokenize(stream, state);
-      if (type != "ws") {
-        if (state.ctx.indentTo == null) {
-          if (type == "symbol" && assumeBody.test(stream.current()))
-            state.ctx.indentTo = state.ctx.start + config.indentUnit;
-          else
-            state.ctx.indentTo = "next";
-        } else if (state.ctx.indentTo == "next") {
-          state.ctx.indentTo = stream.column();
-        }
-      }
-      if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null};
-      else if (type == "close") state.ctx = state.ctx.prev || state.ctx;
-      return style;
-    },
-
-    indent: function (state, _textAfter) {
-      var i = state.ctx.indentTo;
-      return typeof i == "number" ? i : state.ctx.start + 1;
-    },
-
-    lineComment: ";;",
-    blockCommentStart: "#|",
-    blockCommentEnd: "|#"
-  };
-});
-
-CodeMirror.defineMIME("text/x-common-lisp", "commonlisp");
-
-});
--- a/kallithea/public/codemirror/mode/css/css.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,717 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("css", function(config, parserConfig) {
-  if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
-
-  var indentUnit = config.indentUnit,
-      tokenHooks = parserConfig.tokenHooks,
-      mediaTypes = parserConfig.mediaTypes || {},
-      mediaFeatures = parserConfig.mediaFeatures || {},
-      propertyKeywords = parserConfig.propertyKeywords || {},
-      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
-      colorKeywords = parserConfig.colorKeywords || {},
-      valueKeywords = parserConfig.valueKeywords || {},
-      fontProperties = parserConfig.fontProperties || {},
-      allowNested = parserConfig.allowNested;
-
-  var type, override;
-  function ret(style, tp) { type = tp; return style; }
-
-  // Tokenizers
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (tokenHooks[ch]) {
-      var result = tokenHooks[ch](stream, state);
-      if (result !== false) return result;
-    }
-    if (ch == "@") {
-      stream.eatWhile(/[\w\\\-]/);
-      return ret("def", stream.current());
-    } else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
-      return ret(null, "compare");
-    } else if (ch == "\"" || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    } else if (ch == "#") {
-      stream.eatWhile(/[\w\\\-]/);
-      return ret("atom", "hash");
-    } else if (ch == "!") {
-      stream.match(/^\s*\w*/);
-      return ret("keyword", "important");
-    } else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
-      stream.eatWhile(/[\w.%]/);
-      return ret("number", "unit");
-    } else if (ch === "-") {
-      if (/[\d.]/.test(stream.peek())) {
-        stream.eatWhile(/[\w.%]/);
-        return ret("number", "unit");
-      } else if (stream.match(/^\w+-/)) {
-        return ret("meta", "meta");
-      }
-    } else if (/[,+>*\/]/.test(ch)) {
-      return ret(null, "select-op");
-    } else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
-      return ret("qualifier", "qualifier");
-    } else if (/[:;{}\[\]\(\)]/.test(ch)) {
-      return ret(null, ch);
-    } else if (ch == "u" && stream.match("rl(")) {
-      stream.backUp(1);
-      state.tokenize = tokenParenthesized;
-      return ret("property", "word");
-    } else if (/[\w\\\-]/.test(ch)) {
-      stream.eatWhile(/[\w\\\-]/);
-      return ret("property", "word");
-    } else {
-      return ret(null, null);
-    }
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) {
-          if (quote == ")") stream.backUp(1);
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      if (ch == quote || !escaped && quote != ")") state.tokenize = null;
-      return ret("string", "string");
-    };
-  }
-
-  function tokenParenthesized(stream, state) {
-    stream.next(); // Must be '('
-    if (!stream.match(/\s*[\"\')]/, false))
-      state.tokenize = tokenString(")");
-    else
-      state.tokenize = null;
-    return ret(null, "(");
-  }
-
-  // Context management
-
-  function Context(type, indent, prev) {
-    this.type = type;
-    this.indent = indent;
-    this.prev = prev;
-  }
-
-  function pushContext(state, stream, type) {
-    state.context = new Context(type, stream.indentation() + indentUnit, state.context);
-    return type;
-  }
-
-  function popContext(state) {
-    state.context = state.context.prev;
-    return state.context.type;
-  }
-
-  function pass(type, stream, state) {
-    return states[state.context.type](type, stream, state);
-  }
-  function popAndPass(type, stream, state, n) {
-    for (var i = n || 1; i > 0; i--)
-      state.context = state.context.prev;
-    return pass(type, stream, state);
-  }
-
-  // Parser
-
-  function wordAsValue(stream) {
-    var word = stream.current().toLowerCase();
-    if (valueKeywords.hasOwnProperty(word))
-      override = "atom";
-    else if (colorKeywords.hasOwnProperty(word))
-      override = "keyword";
-    else
-      override = "variable";
-  }
-
-  var states = {};
-
-  states.top = function(type, stream, state) {
-    if (type == "{") {
-      return pushContext(state, stream, "block");
-    } else if (type == "}" && state.context.prev) {
-      return popContext(state);
-    } else if (type == "@media") {
-      return pushContext(state, stream, "media");
-    } else if (type == "@font-face") {
-      return "font_face_before";
-    } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
-      return "keyframes";
-    } else if (type && type.charAt(0) == "@") {
-      return pushContext(state, stream, "at");
-    } else if (type == "hash") {
-      override = "builtin";
-    } else if (type == "word") {
-      override = "tag";
-    } else if (type == "variable-definition") {
-      return "maybeprop";
-    } else if (type == "interpolation") {
-      return pushContext(state, stream, "interpolation");
-    } else if (type == ":") {
-      return "pseudo";
-    } else if (allowNested && type == "(") {
-      return pushContext(state, stream, "parens");
-    }
-    return state.context.type;
-  };
-
-  states.block = function(type, stream, state) {
-    if (type == "word") {
-      var word = stream.current().toLowerCase();
-      if (propertyKeywords.hasOwnProperty(word)) {
-        override = "property";
-        return "maybeprop";
-      } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
-        override = "string-2";
-        return "maybeprop";
-      } else if (allowNested) {
-        override = stream.match(/^\s*:/, false) ? "property" : "tag";
-        return "block";
-      } else {
-        override += " error";
-        return "maybeprop";
-      }
-    } else if (type == "meta") {
-      return "block";
-    } else if (!allowNested && (type == "hash" || type == "qualifier")) {
-      override = "error";
-      return "block";
-    } else {
-      return states.top(type, stream, state);
-    }
-  };
-
-  states.maybeprop = function(type, stream, state) {
-    if (type == ":") return pushContext(state, stream, "prop");
-    return pass(type, stream, state);
-  };
-
-  states.prop = function(type, stream, state) {
-    if (type == ";") return popContext(state);
-    if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
-    if (type == "}" || type == "{") return popAndPass(type, stream, state);
-    if (type == "(") return pushContext(state, stream, "parens");
-
-    if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
-      override += " error";
-    } else if (type == "word") {
-      wordAsValue(stream);
-    } else if (type == "interpolation") {
-      return pushContext(state, stream, "interpolation");
-    }
-    return "prop";
-  };
-
-  states.propBlock = function(type, _stream, state) {
-    if (type == "}") return popContext(state);
-    if (type == "word") { override = "property"; return "maybeprop"; }
-    return state.context.type;
-  };
-
-  states.parens = function(type, stream, state) {
-    if (type == "{" || type == "}") return popAndPass(type, stream, state);
-    if (type == ")") return popContext(state);
-    if (type == "(") return pushContext(state, stream, "parens");
-    if (type == "word") wordAsValue(stream);
-    return "parens";
-  };
-
-  states.pseudo = function(type, stream, state) {
-    if (type == "word") {
-      override = "variable-3";
-      return state.context.type;
-    }
-    return pass(type, stream, state);
-  };
-
-  states.media = function(type, stream, state) {
-    if (type == "(") return pushContext(state, stream, "media_parens");
-    if (type == "}") return popAndPass(type, stream, state);
-    if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
-
-    if (type == "word") {
-      var word = stream.current().toLowerCase();
-      if (word == "only" || word == "not" || word == "and")
-        override = "keyword";
-      else if (mediaTypes.hasOwnProperty(word))
-        override = "attribute";
-      else if (mediaFeatures.hasOwnProperty(word))
-        override = "property";
-      else
-        override = "error";
-    }
-    return state.context.type;
-  };
-
-  states.media_parens = function(type, stream, state) {
-    if (type == ")") return popContext(state);
-    if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
-    return states.media(type, stream, state);
-  };
-
-  states.font_face_before = function(type, stream, state) {
-    if (type == "{")
-      return pushContext(state, stream, "font_face");
-    return pass(type, stream, state);
-  };
-
-  states.font_face = function(type, stream, state) {
-    if (type == "}") return popContext(state);
-    if (type == "word") {
-      if (!fontProperties.hasOwnProperty(stream.current().toLowerCase()))
-        override = "error";
-      else
-        override = "property";
-      return "maybeprop";
-    }
-    return "font_face";
-  };
-
-  states.keyframes = function(type, stream, state) {
-    if (type == "word") { override = "variable"; return "keyframes"; }
-    if (type == "{") return pushContext(state, stream, "top");
-    return pass(type, stream, state);
-  };
-
-  states.at = function(type, stream, state) {
-    if (type == ";") return popContext(state);
-    if (type == "{" || type == "}") return popAndPass(type, stream, state);
-    if (type == "word") override = "tag";
-    else if (type == "hash") override = "builtin";
-    return "at";
-  };
-
-  states.interpolation = function(type, stream, state) {
-    if (type == "}") return popContext(state);
-    if (type == "{" || type == ";") return popAndPass(type, stream, state);
-    if (type != "variable") override = "error";
-    return "interpolation";
-  };
-
-  return {
-    startState: function(base) {
-      return {tokenize: null,
-              state: "top",
-              context: new Context("top", base || 0, null)};
-    },
-
-    token: function(stream, state) {
-      if (!state.tokenize && stream.eatSpace()) return null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style && typeof style == "object") {
-        type = style[1];
-        style = style[0];
-      }
-      override = style;
-      state.state = states[state.state](type, stream, state);
-      return override;
-    },
-
-    indent: function(state, textAfter) {
-      var cx = state.context, ch = textAfter && textAfter.charAt(0);
-      var indent = cx.indent;
-      if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
-      if (cx.prev &&
-          (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") ||
-           ch == ")" && (cx.type == "parens" || cx.type == "media_parens") ||
-           ch == "{" && (cx.type == "at" || cx.type == "media"))) {
-        indent = cx.indent - indentUnit;
-        cx = cx.prev;
-      }
-      return indent;
-    },
-
-    electricChars: "}",
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    fold: "brace"
-  };
-});
-
-  function keySet(array) {
-    var keys = {};
-    for (var i = 0; i < array.length; ++i) {
-      keys[array[i]] = true;
-    }
-    return keys;
-  }
-
-  var mediaTypes_ = [
-    "all", "aural", "braille", "handheld", "print", "projection", "screen",
-    "tty", "tv", "embossed"
-  ], mediaTypes = keySet(mediaTypes_);
-
-  var mediaFeatures_ = [
-    "width", "min-width", "max-width", "height", "min-height", "max-height",
-    "device-width", "min-device-width", "max-device-width", "device-height",
-    "min-device-height", "max-device-height", "aspect-ratio",
-    "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
-    "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
-    "max-color", "color-index", "min-color-index", "max-color-index",
-    "monochrome", "min-monochrome", "max-monochrome", "resolution",
-    "min-resolution", "max-resolution", "scan", "grid"
-  ], mediaFeatures = keySet(mediaFeatures_);
-
-  var propertyKeywords_ = [
-    "align-content", "align-items", "align-self", "alignment-adjust",
-    "alignment-baseline", "anchor-point", "animation", "animation-delay",
-    "animation-direction", "animation-duration", "animation-fill-mode",
-    "animation-iteration-count", "animation-name", "animation-play-state",
-    "animation-timing-function", "appearance", "azimuth", "backface-visibility",
-    "background", "background-attachment", "background-clip", "background-color",
-    "background-image", "background-origin", "background-position",
-    "background-repeat", "background-size", "baseline-shift", "binding",
-    "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
-    "bookmark-target", "border", "border-bottom", "border-bottom-color",
-    "border-bottom-left-radius", "border-bottom-right-radius",
-    "border-bottom-style", "border-bottom-width", "border-collapse",
-    "border-color", "border-image", "border-image-outset",
-    "border-image-repeat", "border-image-slice", "border-image-source",
-    "border-image-width", "border-left", "border-left-color",
-    "border-left-style", "border-left-width", "border-radius", "border-right",
-    "border-right-color", "border-right-style", "border-right-width",
-    "border-spacing", "border-style", "border-top", "border-top-color",
-    "border-top-left-radius", "border-top-right-radius", "border-top-style",
-    "border-top-width", "border-width", "bottom", "box-decoration-break",
-    "box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
-    "caption-side", "clear", "clip", "color", "color-profile", "column-count",
-    "column-fill", "column-gap", "column-rule", "column-rule-color",
-    "column-rule-style", "column-rule-width", "column-span", "column-width",
-    "columns", "content", "counter-increment", "counter-reset", "crop", "cue",
-    "cue-after", "cue-before", "cursor", "direction", "display",
-    "dominant-baseline", "drop-initial-after-adjust",
-    "drop-initial-after-align", "drop-initial-before-adjust",
-    "drop-initial-before-align", "drop-initial-size", "drop-initial-value",
-    "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
-    "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
-    "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
-    "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
-    "font-stretch", "font-style", "font-synthesis", "font-variant",
-    "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
-    "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
-    "font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
-    "grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
-    "grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
-    "grid-template", "grid-template-areas", "grid-template-columns",
-    "grid-template-rows", "hanging-punctuation", "height", "hyphens",
-    "icon", "image-orientation", "image-rendering", "image-resolution",
-    "inline-box-align", "justify-content", "left", "letter-spacing",
-    "line-break", "line-height", "line-stacking", "line-stacking-ruby",
-    "line-stacking-shift", "line-stacking-strategy", "list-style",
-    "list-style-image", "list-style-position", "list-style-type", "margin",
-    "margin-bottom", "margin-left", "margin-right", "margin-top",
-    "marker-offset", "marks", "marquee-direction", "marquee-loop",
-    "marquee-play-count", "marquee-speed", "marquee-style", "max-height",
-    "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
-    "nav-left", "nav-right", "nav-up", "object-fit", "object-position",
-    "opacity", "order", "orphans", "outline",
-    "outline-color", "outline-offset", "outline-style", "outline-width",
-    "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
-    "padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
-    "page", "page-break-after", "page-break-before", "page-break-inside",
-    "page-policy", "pause", "pause-after", "pause-before", "perspective",
-    "perspective-origin", "pitch", "pitch-range", "play-during", "position",
-    "presentation-level", "punctuation-trim", "quotes", "region-break-after",
-    "region-break-before", "region-break-inside", "region-fragment",
-    "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
-    "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
-    "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
-    "shape-outside", "size", "speak", "speak-as", "speak-header",
-    "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
-    "tab-size", "table-layout", "target", "target-name", "target-new",
-    "target-position", "text-align", "text-align-last", "text-decoration",
-    "text-decoration-color", "text-decoration-line", "text-decoration-skip",
-    "text-decoration-style", "text-emphasis", "text-emphasis-color",
-    "text-emphasis-position", "text-emphasis-style", "text-height",
-    "text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
-    "text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
-    "text-wrap", "top", "transform", "transform-origin", "transform-style",
-    "transition", "transition-delay", "transition-duration",
-    "transition-property", "transition-timing-function", "unicode-bidi",
-    "vertical-align", "visibility", "voice-balance", "voice-duration",
-    "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
-    "voice-volume", "volume", "white-space", "widows", "width", "word-break",
-    "word-spacing", "word-wrap", "z-index",
-    // SVG-specific
-    "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
-    "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
-    "color-interpolation", "color-interpolation-filters",
-    "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
-    "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
-    "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
-    "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
-    "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
-    "glyph-orientation-vertical", "text-anchor", "writing-mode"
-  ], propertyKeywords = keySet(propertyKeywords_);
-
-  var nonStandardPropertyKeywords_ = [
-    "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
-    "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
-    "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
-    "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
-    "searchfield-results-decoration", "zoom"
-  ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
-
-  var colorKeywords_ = [
-    "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
-    "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
-    "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
-    "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
-    "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
-    "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
-    "darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
-    "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
-    "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
-    "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
-    "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
-    "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
-    "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
-    "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
-    "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
-    "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
-    "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
-    "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
-    "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
-    "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
-    "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
-    "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
-    "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
-    "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
-    "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
-    "whitesmoke", "yellow", "yellowgreen"
-  ], colorKeywords = keySet(colorKeywords_);
-
-  var valueKeywords_ = [
-    "above", "absolute", "activeborder", "activecaption", "afar",
-    "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate",
-    "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
-    "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page",
-    "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
-    "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
-    "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel",
-    "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian",
-    "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
-    "cell", "center", "checkbox", "circle", "cjk-earthly-branch",
-    "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
-    "col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
-    "content-box", "context-menu", "continuous", "copy", "cover", "crop",
-    "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal",
-    "decimal-leading-zero", "default", "default-button", "destination-atop",
-    "destination-in", "destination-out", "destination-over", "devanagari",
-    "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted",
-    "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
-    "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
-    "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
-    "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
-    "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
-    "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
-    "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
-    "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et",
-    "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed",
-    "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes",
-    "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
-    "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
-    "help", "hidden", "hide", "higher", "highlight", "highlighttext",
-    "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
-    "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
-    "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
-    "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert",
-    "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer",
-    "landscape", "lao", "large", "larger", "left", "level", "lighter",
-    "line-through", "linear", "lines", "list-item", "listbox", "listitem",
-    "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
-    "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
-    "lower-roman", "lowercase", "ltr", "malayalam", "match",
-    "media-controls-background", "media-current-time-display",
-    "media-fullscreen-button", "media-mute-button", "media-play-button",
-    "media-return-to-realtime-button", "media-rewind-button",
-    "media-seek-back-button", "media-seek-forward-button", "media-slider",
-    "media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
-    "media-volume-slider-container", "media-volume-sliderthumb", "medium",
-    "menu", "menulist", "menulist-button", "menulist-text",
-    "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
-    "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
-    "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
-    "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
-    "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
-    "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
-    "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
-    "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer",
-    "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button",
-    "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region",
-    "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba",
-    "ridge", "right", "round", "row-resize", "rtl", "run-in", "running",
-    "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield",
-    "searchfield-cancel-button", "searchfield-decoration",
-    "searchfield-results-button", "searchfield-results-decoration",
-    "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
-    "single", "skip-white-space", "slide", "slider-horizontal",
-    "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
-    "small", "small-caps", "small-caption", "smaller", "solid", "somali",
-    "source-atop", "source-in", "source-out", "source-over", "space", "square",
-    "square-button", "start", "static", "status-bar", "stretch", "stroke",
-    "sub", "subpixel-antialiased", "super", "sw-resize", "table",
-    "table-caption", "table-cell", "table-column", "table-column-group",
-    "table-footer-group", "table-header-group", "table-row", "table-row-group",
-    "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
-    "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
-    "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
-    "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
-    "transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
-    "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
-    "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
-    "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
-    "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
-    "window", "windowframe", "windowtext", "x-large", "x-small", "xor",
-    "xx-large", "xx-small"
-  ], valueKeywords = keySet(valueKeywords_);
-
-  var fontProperties_ = [
-    "font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
-    "font-stretch", "font-weight", "font-style"
-  ], fontProperties = keySet(fontProperties_);
-
-  var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_)
-    .concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
-  CodeMirror.registerHelper("hintWords", "css", allWords);
-
-  function tokenCComment(stream, state) {
-    var maybeEnd = false, ch;
-    while ((ch = stream.next()) != null) {
-      if (maybeEnd && ch == "/") {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return ["comment", "comment"];
-  }
-
-  function tokenSGMLComment(stream, state) {
-    if (stream.skipTo("-->")) {
-      stream.match("-->");
-      state.tokenize = null;
-    } else {
-      stream.skipToEnd();
-    }
-    return ["comment", "comment"];
-  }
-
-  CodeMirror.defineMIME("text/css", {
-    mediaTypes: mediaTypes,
-    mediaFeatures: mediaFeatures,
-    propertyKeywords: propertyKeywords,
-    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
-    colorKeywords: colorKeywords,
-    valueKeywords: valueKeywords,
-    fontProperties: fontProperties,
-    tokenHooks: {
-      "<": function(stream, state) {
-        if (!stream.match("!--")) return false;
-        state.tokenize = tokenSGMLComment;
-        return tokenSGMLComment(stream, state);
-      },
-      "/": function(stream, state) {
-        if (!stream.eat("*")) return false;
-        state.tokenize = tokenCComment;
-        return tokenCComment(stream, state);
-      }
-    },
-    name: "css"
-  });
-
-  CodeMirror.defineMIME("text/x-scss", {
-    mediaTypes: mediaTypes,
-    mediaFeatures: mediaFeatures,
-    propertyKeywords: propertyKeywords,
-    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
-    colorKeywords: colorKeywords,
-    valueKeywords: valueKeywords,
-    fontProperties: fontProperties,
-    allowNested: true,
-    tokenHooks: {
-      "/": function(stream, state) {
-        if (stream.eat("/")) {
-          stream.skipToEnd();
-          return ["comment", "comment"];
-        } else if (stream.eat("*")) {
-          state.tokenize = tokenCComment;
-          return tokenCComment(stream, state);
-        } else {
-          return ["operator", "operator"];
-        }
-      },
-      ":": function(stream) {
-        if (stream.match(/\s*\{/))
-          return [null, "{"];
-        return false;
-      },
-      "$": function(stream) {
-        stream.match(/^[\w-]+/);
-        if (stream.match(/^\s*:/, false))
-          return ["variable-2", "variable-definition"];
-        return ["variable-2", "variable"];
-      },
-      "#": function(stream) {
-        if (!stream.eat("{")) return false;
-        return [null, "interpolation"];
-      }
-    },
-    name: "css",
-    helperType: "scss"
-  });
-
-  CodeMirror.defineMIME("text/x-less", {
-    mediaTypes: mediaTypes,
-    mediaFeatures: mediaFeatures,
-    propertyKeywords: propertyKeywords,
-    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
-    colorKeywords: colorKeywords,
-    valueKeywords: valueKeywords,
-    fontProperties: fontProperties,
-    allowNested: true,
-    tokenHooks: {
-      "/": function(stream, state) {
-        if (stream.eat("/")) {
-          stream.skipToEnd();
-          return ["comment", "comment"];
-        } else if (stream.eat("*")) {
-          state.tokenize = tokenCComment;
-          return tokenCComment(stream, state);
-        } else {
-          return ["operator", "operator"];
-        }
-      },
-      "@": function(stream) {
-        if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
-        stream.eatWhile(/[\w\\\-]/);
-        if (stream.match(/^\s*:/, false))
-          return ["variable-2", "variable-definition"];
-        return ["variable-2", "variable"];
-      },
-      "&": function() {
-        return ["atom", "atom"];
-      }
-    },
-    name: "css",
-    helperType: "less"
-  });
-
-});
--- a/kallithea/public/codemirror/mode/css/less_test.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
-  "use strict";
-
-  var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less");
-  function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); }
-
-  MT("variable",
-     "[variable-2 @base]: [atom #f04615];",
-     "[qualifier .class] {",
-     "  [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]",
-     "  [property color]: [variable saturate]([variable-2 @base], [number 5%]);",
-     "}");
-
-  MT("amp",
-     "[qualifier .child], [qualifier .sibling] {",
-     "  [qualifier .parent] [atom &] {",
-     "    [property color]: [keyword black];",
-     "  }",
-     "  [atom &] + [atom &] {",
-     "    [property color]: [keyword red];",
-     "  }",
-     "}");
-
-  MT("mixin",
-     "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {",
-     "  [property color]: [variable darken]([variable-2 @color], [number 10%]);",
-     "}",
-     "[qualifier .mixin] ([variable light]; [variable-2 @color]) {",
-     "  [property color]: [variable lighten]([variable-2 @color], [number 10%]);",
-     "}",
-     "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {",
-     "  [property display]: [atom block];",
-     "}",
-     "[variable-2 @switch]: [variable light];",
-     "[qualifier .class] {",
-     "  [qualifier .mixin]([variable-2 @switch]; [atom #888]);",
-     "}");
-
-  MT("nest",
-     "[qualifier .one] {",
-     "  [def @media] ([property width]: [number 400px]) {",
-     "    [property font-size]: [number 1.2em];",
-     "    [def @media] [attribute print] [keyword and] [property color] {",
-     "      [property color]: [keyword blue];",
-     "    }",
-     "  }",
-     "}");
-})();
--- a/kallithea/public/codemirror/mode/css/scss_test.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function() {
-  var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss");
-  function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }
-
-  MT('url_with_quotation',
-    "[tag foo] { [property background]:[atom url]([string test.jpg]) }");
-
-  MT('url_with_double_quotes',
-    "[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }");
-
-  MT('url_with_single_quotes',
-    "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }");
-
-  MT('string',
-    "[def @import] [string \"compass/css3\"]");
-
-  MT('important_keyword',
-    "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }");
-
-  MT('variable',
-    "[variable-2 $blue]:[atom #333]");
-
-  MT('variable_as_attribute',
-    "[tag foo] { [property color]:[variable-2 $blue] }");
-
-  MT('numbers',
-    "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }");
-
-  MT('number_percentage',
-    "[tag foo] { [property width]:[number 80%] }");
-
-  MT('selector',
-    "[builtin #hello][qualifier .world]{}");
-
-  MT('singleline_comment',
-    "[comment // this is a comment]");
-
-  MT('multiline_comment',
-    "[comment /*foobar*/]");
-
-  MT('attribute_with_hyphen',
-    "[tag foo] { [property font-size]:[number 10px] }");
-
-  MT('string_after_attribute',
-    "[tag foo] { [property content]:[string \"::\"] }");
-
-  MT('directives',
-    "[def @include] [qualifier .mixin]");
-
-  MT('basic_structure',
-    "[tag p] { [property background]:[keyword red]; }");
-
-  MT('nested_structure',
-    "[tag p] { [tag a] { [property color]:[keyword red]; } }");
-
-  MT('mixin',
-    "[def @mixin] [tag table-base] {}");
-
-  MT('number_without_semicolon',
-    "[tag p] {[property width]:[number 12]}",
-    "[tag a] {[property color]:[keyword red];}");
-
-  MT('atom_in_nested_block',
-    "[tag p] { [tag a] { [property color]:[atom #000]; } }");
-
-  MT('interpolation_in_property',
-    "[tag foo] { #{[variable-2 $hello]}:[number 2]; }");
-
-  MT('interpolation_in_selector',
-    "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }");
-
-  MT('interpolation_error',
-    "[tag foo]#{[error foo]} { [property color]:[atom #000]; }");
-
-  MT("divide_operator",
-    "[tag foo] { [property width]:[number 4] [operator /] [number 2] }");
-
-  MT('nested_structure_with_id_selector',
-    "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }");
-
-  MT('indent_mixin',
-     "[def @mixin] [tag container] (",
-     "  [variable-2 $a]: [number 10],",
-     "  [variable-2 $b]: [number 10])",
-     "{}");
-
-  MT('indent_nested',
-     "[tag foo] {",
-     "  [tag bar] {",
-     "  }",
-     "}");
-
-  MT('indent_parentheses',
-     "[tag foo] {",
-     "  [property color]: [variable darken]([variable-2 $blue],",
-     "    [number 9%]);",
-     "}");
-
-  MT('indent_vardef',
-     "[variable-2 $name]:",
-     "  [string 'val'];",
-     "[tag tag] {",
-     "  [tag inner] {",
-     "    [property margin]: [number 3px];",
-     "  }",
-     "}");
-})();
--- a/kallithea/public/codemirror/mode/cypher/cypher.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// By the Neo4j Team and contributors.
-// https://github.com/neo4j-contrib/CodeMirror
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-  var wordRegexp = function(words) {
-    return new RegExp("^(?:" + words.join("|") + ")$", "i");
-  };
-
-  CodeMirror.defineMode("cypher", function(config) {
-    var tokenBase = function(stream/*, state*/) {
-      var ch = stream.next(), curPunc = null;
-      if (ch === "\"" || ch === "'") {
-        stream.match(/.+?["']/);
-        return "string";
-      }
-      if (/[{}\(\),\.;\[\]]/.test(ch)) {
-        curPunc = ch;
-        return "node";
-      } else if (ch === "/" && stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      } else if (operatorChars.test(ch)) {
-        stream.eatWhile(operatorChars);
-        return null;
-      } else {
-        stream.eatWhile(/[_\w\d]/);
-        if (stream.eat(":")) {
-          stream.eatWhile(/[\w\d_\-]/);
-          return "atom";
-        }
-        var word = stream.current();
-        if (funcs.test(word)) return "builtin";
-        if (preds.test(word)) return "def";
-        if (keywords.test(word)) return "keyword";
-        return "variable";
-      }
-    };
-    var pushContext = function(state, type, col) {
-      return state.context = {
-        prev: state.context,
-        indent: state.indent,
-        col: col,
-        type: type
-      };
-    };
-    var popContext = function(state) {
-      state.indent = state.context.indent;
-      return state.context = state.context.prev;
-    };
-    var indentUnit = config.indentUnit;
-    var curPunc;
-    var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "right", "round", "rtrim", "shortestPath", "sign", "sin", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "trim", "type", "upper"]);
-    var preds = wordRegexp(["all", "and", "any", "has", "in", "none", "not", "or", "single", "xor"]);
-    var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "distinct", "drop", "else", "end", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "remove", "return", "scan", "set", "skip", "start", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]);
-    var operatorChars = /[*+\-<>=&|~%^]/;
-
-    return {
-      startState: function(/*base*/) {
-        return {
-          tokenize: tokenBase,
-          context: null,
-          indent: 0,
-          col: 0
-        };
-      },
-      token: function(stream, state) {
-        if (stream.sol()) {
-          if (state.context && (state.context.align == null)) {
-            state.context.align = false;
-          }
-          state.indent = stream.indentation();
-        }
-        if (stream.eatSpace()) {
-          return null;
-        }
-        var style = state.tokenize(stream, state);
-        if (style !== "comment" && state.context && (state.context.align == null) && state.context.type !== "pattern") {
-          state.context.align = true;
-        }
-        if (curPunc === "(") {
-          pushContext(state, ")", stream.column());
-        } else if (curPunc === "[") {
-          pushContext(state, "]", stream.column());
-        } else if (curPunc === "{") {
-          pushContext(state, "}", stream.column());
-        } else if (/[\]\}\)]/.test(curPunc)) {
-          while (state.context && state.context.type === "pattern") {
-            popContext(state);
-          }
-          if (state.context && curPunc === state.context.type) {
-            popContext(state);
-          }
-        } else if (curPunc === "." && state.context && state.context.type === "pattern") {
-          popContext(state);
-        } else if (/atom|string|variable/.test(style) && state.context) {
-          if (/[\}\]]/.test(state.context.type)) {
-            pushContext(state, "pattern", stream.column());
-          } else if (state.context.type === "pattern" && !state.context.align) {
-            state.context.align = true;
-            state.context.col = stream.column();
-          }
-        }
-        return style;
-      },
-      indent: function(state, textAfter) {
-        var firstChar = textAfter && textAfter.charAt(0);
-        var context = state.context;
-        if (/[\]\}]/.test(firstChar)) {
-          while (context && context.type === "pattern") {
-            context = context.prev;
-          }
-        }
-        var closing = context && firstChar === context.type;
-        if (!context) return 0;
-        if (context.type === "keywords") return CodeMirror.commands.newlineAndIndent;
-        if (context.align) return context.col + (closing ? 0 : 1);
-        return context.indent + (closing ? 0 : indentUnit);
-      }
-    };
-  });
-
-  CodeMirror.modeExtensions["cypher"] = {
-    autoFormatLineBreaks: function(text) {
-      var i, lines, reProcessedPortion;
-      var lines = text.split("\n");
-      var reProcessedPortion = /\s+\b(return|where|order by|match|with|skip|limit|create|delete|set)\b\s/g;
-      for (var i = 0; i < lines.length; i++)
-        lines[i] = lines[i].replace(reProcessedPortion, " \n$1 ").trim();
-      return lines.join("\n");
-    }
-  };
-
-  CodeMirror.defineMIME("application/x-cypher-query", "cypher");
-
-});
--- a/kallithea/public/codemirror/mode/d/d.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("d", function(config, parserConfig) {
-  var indentUnit = config.indentUnit,
-      statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
-      keywords = parserConfig.keywords || {},
-      builtin = parserConfig.builtin || {},
-      blockKeywords = parserConfig.blockKeywords || {},
-      atoms = parserConfig.atoms || {},
-      hooks = parserConfig.hooks || {},
-      multiLineStrings = parserConfig.multiLineStrings;
-  var isOperatorChar = /[+\-*&%=<>!?|\/]/;
-
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (hooks[ch]) {
-      var result = hooks[ch](stream, state);
-      if (result !== false) return result;
-    }
-    if (ch == '"' || ch == "'" || ch == "`") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("+")) {
-        state.tokenize = tokenComment;
-        return tokenNestedComment(stream, state);
-      }
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_\xa1-\uffff]/);
-    var cur = stream.current();
-    if (keywords.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "keyword";
-    }
-    if (builtin.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "builtin";
-    }
-    if (atoms.propertyIsEnumerable(cur)) return "atom";
-    return "variable";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || multiLineStrings))
-        state.tokenize = null;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function tokenNestedComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch == "+");
-    }
-    return "comment";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    var indent = state.indented;
-    if (state.context && state.context.type == "statement")
-      indent = state.context.indented;
-    return state.context = new Context(indent, col, type, null, state.context);
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      return {
-        tokenize: null,
-        context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
-      else if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "}") {
-        while (ctx.type == "statement") ctx = popContext(state);
-        if (ctx.type == "}") ctx = popContext(state);
-        while (ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == ctx.type) popContext(state);
-      else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement"))
-        pushContext(state, stream.column(), "statement");
-      state.startOfLine = false;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
-      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
-      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
-      var closing = firstChar == ctx.type;
-      if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
-      else if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricChars: "{}"
-  };
-});
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  var blockKeywords = "body catch class do else enum for foreach foreach_reverse if in interface mixin " +
-                      "out scope struct switch try union unittest version while with";
-
-  CodeMirror.defineMIME("text/x-d", {
-    name: "d",
-    keywords: words("abstract alias align asm assert auto break case cast cdouble cent cfloat const continue " +
-                    "debug default delegate delete deprecated export extern final finally function goto immutable " +
-                    "import inout invariant is lazy macro module new nothrow override package pragma private " +
-                    "protected public pure ref return shared short static super synchronized template this " +
-                    "throw typedef typeid typeof volatile __FILE__ __LINE__ __gshared __traits __vector __parameters " +
-                    blockKeywords),
-    blockKeywords: words(blockKeywords),
-    builtin: words("bool byte char creal dchar double float idouble ifloat int ireal long real short ubyte " +
-                   "ucent uint ulong ushort wchar wstring void size_t sizediff_t"),
-    atoms: words("exit failure success true false null"),
-    hooks: {
-      "@": function(stream, _state) {
-        stream.eatWhile(/[\w\$_]/);
-        return "meta";
-      }
-    }
-  });
-
-});
--- a/kallithea/public/codemirror/mode/diff/diff.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("diff", function() {
-
-  var TOKEN_NAMES = {
-    '+': 'positive',
-    '-': 'negative',
-    '@': 'meta'
-  };
-
-  return {
-    token: function(stream) {
-      var tw_pos = stream.string.search(/[\t ]+?$/);
-
-      if (!stream.sol() || tw_pos === 0) {
-        stream.skipToEnd();
-        return ("error " + (
-          TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, '');
-      }
-
-      var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd();
-
-      if (tw_pos === -1) {
-        stream.skipToEnd();
-      } else {
-        stream.pos = tw_pos;
-      }
-
-      return token_name;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-diff", "diff");
-
-});
--- a/kallithea/public/codemirror/mode/django/django.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"),
-        require("../../addon/mode/overlay"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed",
-            "../../addon/mode/overlay"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.defineMode("django:inner", function() {
-    var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false",
-                    "loop", "none", "self", "super", "if", "endif", "as", "not", "and",
-                    "else", "import", "with", "endwith", "without", "context", "ifequal", "endifequal",
-                    "ifnotequal", "endifnotequal", "extends", "include", "load", "length", "comment",
-                    "endcomment", "empty"];
-    keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");
-
-    function tokenBase (stream, state) {
-      stream.eatWhile(/[^\{]/);
-      var ch = stream.next();
-      if (ch == "{") {
-        if (ch = stream.eat(/\{|%|#/)) {
-          state.tokenize = inTag(ch);
-          return "tag";
-        }
-      }
-    }
-    function inTag (close) {
-      if (close == "{") {
-        close = "}";
-      }
-      return function (stream, state) {
-        var ch = stream.next();
-        if ((ch == close) && stream.eat("}")) {
-          state.tokenize = tokenBase;
-          return "tag";
-        }
-        if (stream.match(keywords)) {
-          return "keyword";
-        }
-        return close == "#" ? "comment" : "string";
-      };
-    }
-    return {
-      startState: function () {
-        return {tokenize: tokenBase};
-      },
-      token: function (stream, state) {
-        return state.tokenize(stream, state);
-      }
-    };
-  });
-
-  CodeMirror.defineMode("django", function(config) {
-    var htmlBase = CodeMirror.getMode(config, "text/html");
-    var djangoInner = CodeMirror.getMode(config, "django:inner");
-    return CodeMirror.overlayMode(htmlBase, djangoInner);
-  });
-
-  CodeMirror.defineMIME("text/x-django", "django");
-});
--- a/kallithea/public/codemirror/mode/dtd/dtd.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
-  DTD mode
-  Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>
-  Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues
-  GitHub: @peterkroon
-*/
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("dtd", function(config) {
-  var indentUnit = config.indentUnit, type;
-  function ret(style, tp) {type = tp; return style;}
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-
-    if (ch == "<" && stream.eat("!") ) {
-      if (stream.eatWhile(/[\-]/)) {
-        state.tokenize = tokenSGMLComment;
-        return tokenSGMLComment(stream, state);
-      } else if (stream.eatWhile(/[\w]/)) return ret("keyword", "doindent");
-    } else if (ch == "<" && stream.eat("?")) { //xml declaration
-      state.tokenize = inBlock("meta", "?>");
-      return ret("meta", ch);
-    } else if (ch == "#" && stream.eatWhile(/[\w]/)) return ret("atom", "tag");
-    else if (ch == "|") return ret("keyword", "seperator");
-    else if (ch.match(/[\(\)\[\]\-\.,\+\?>]/)) return ret(null, ch);//if(ch === ">") return ret(null, "endtag"); else
-    else if (ch.match(/[\[\]]/)) return ret("rule", ch);
-    else if (ch == "\"" || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    } else if (stream.eatWhile(/[a-zA-Z\?\+\d]/)) {
-      var sc = stream.current();
-      if( sc.substr(sc.length-1,sc.length).match(/\?|\+/) !== null )stream.backUp(1);
-      return ret("tag", "tag");
-    } else if (ch == "%" || ch == "*" ) return ret("number", "number");
-    else {
-      stream.eatWhile(/[\w\\\-_%.{,]/);
-      return ret(null, null);
-    }
-  }
-
-  function tokenSGMLComment(stream, state) {
-    var dashes = 0, ch;
-    while ((ch = stream.next()) != null) {
-      if (dashes >= 2 && ch == ">") {
-        state.tokenize = tokenBase;
-        break;
-      }
-      dashes = (ch == "-") ? dashes + 1 : 0;
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      return ret("string", "tag");
-    };
-  }
-
-  function inBlock(style, terminator) {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        if (stream.match(terminator)) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        stream.next();
-      }
-      return style;
-    };
-  }
-
-  return {
-    startState: function(base) {
-      return {tokenize: tokenBase,
-              baseIndent: base || 0,
-              stack: []};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-
-      var context = state.stack[state.stack.length-1];
-      if (stream.current() == "[" || type === "doindent" || type == "[") state.stack.push("rule");
-      else if (type === "endtag") state.stack[state.stack.length-1] = "endtag";
-      else if (stream.current() == "]" || type == "]" || (type == ">" && context == "rule")) state.stack.pop();
-      else if (type == "[") state.stack.push("[");
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var n = state.stack.length;
-
-      if( textAfter.match(/\]\s+|\]/) )n=n-1;
-      else if(textAfter.substr(textAfter.length-1, textAfter.length) === ">"){
-        if(textAfter.substr(0,1) === "<")n;
-        else if( type == "doindent" && textAfter.length > 1 )n;
-        else if( type == "doindent")n--;
-        else if( type == ">" && textAfter.length > 1)n;
-        else if( type == "tag" && textAfter !== ">")n;
-        else if( type == "tag" && state.stack[state.stack.length-1] == "rule")n--;
-        else if( type == "tag")n++;
-        else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule" && type === ">")n--;
-        else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule")n;
-        else if( textAfter.substr(0,1) !== "<" && textAfter.substr(0,1) === ">" )n=n-1;
-        else if( textAfter === ">")n;
-        else n=n-1;
-        //over rule them all
-        if(type == null || type == "]")n--;
-      }
-
-      return state.baseIndent + n * indentUnit;
-    },
-
-    electricChars: "]>"
-  };
-});
-
-CodeMirror.defineMIME("application/xml-dtd", "dtd");
-
-});
--- a/kallithea/public/codemirror/mode/dylan/dylan.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,299 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("dylan", function(_config) {
-  // Words
-  var words = {
-    // Words that introduce unnamed definitions like "define interface"
-    unnamedDefinition: ["interface"],
-
-    // Words that introduce simple named definitions like "define library"
-    namedDefinition: ["module", "library", "macro",
-                      "C-struct", "C-union",
-                      "C-function", "C-callable-wrapper"
-                     ],
-
-    // Words that introduce type definitions like "define class".
-    // These are also parameterized like "define method" and are
-    // appended to otherParameterizedDefinitionWords
-    typeParameterizedDefinition: ["class", "C-subtype", "C-mapped-subtype"],
-
-    // Words that introduce trickier definitions like "define method".
-    // These require special definitions to be added to startExpressions
-    otherParameterizedDefinition: ["method", "function",
-                                   "C-variable", "C-address"
-                                  ],
-
-    // Words that introduce module constant definitions.
-    // These must also be simple definitions and are
-    // appended to otherSimpleDefinitionWords
-    constantSimpleDefinition: ["constant"],
-
-    // Words that introduce module variable definitions.
-    // These must also be simple definitions and are
-    // appended to otherSimpleDefinitionWords
-    variableSimpleDefinition: ["variable"],
-
-    // Other words that introduce simple definitions
-    // (without implicit bodies).
-    otherSimpleDefinition: ["generic", "domain",
-                            "C-pointer-type",
-                            "table"
-                           ],
-
-    // Words that begin statements with implicit bodies.
-    statement: ["if", "block", "begin", "method", "case",
-                "for", "select", "when", "unless", "until",
-                "while", "iterate", "profiling", "dynamic-bind"
-               ],
-
-    // Patterns that act as separators in compound statements.
-    // This may include any general pattern that must be indented
-    // specially.
-    separator: ["finally", "exception", "cleanup", "else",
-                "elseif", "afterwards"
-               ],
-
-    // Keywords that do not require special indentation handling,
-    // but which should be highlighted
-    other: ["above", "below", "by", "from", "handler", "in",
-            "instance", "let", "local", "otherwise", "slot",
-            "subclass", "then", "to", "keyed-by", "virtual"
-           ],
-
-    // Condition signaling function calls
-    signalingCalls: ["signal", "error", "cerror",
-                     "break", "check-type", "abort"
-                    ]
-  };
-
-  words["otherDefinition"] =
-    words["unnamedDefinition"]
-    .concat(words["namedDefinition"])
-    .concat(words["otherParameterizedDefinition"]);
-
-  words["definition"] =
-    words["typeParameterizedDefinition"]
-    .concat(words["otherDefinition"]);
-
-  words["parameterizedDefinition"] =
-    words["typeParameterizedDefinition"]
-    .concat(words["otherParameterizedDefinition"]);
-
-  words["simpleDefinition"] =
-    words["constantSimpleDefinition"]
-    .concat(words["variableSimpleDefinition"])
-    .concat(words["otherSimpleDefinition"]);
-
-  words["keyword"] =
-    words["statement"]
-    .concat(words["separator"])
-    .concat(words["other"]);
-
-  // Patterns
-  var symbolPattern = "[-_a-zA-Z?!*@<>$%]+";
-  var symbol = new RegExp("^" + symbolPattern);
-  var patterns = {
-    // Symbols with special syntax
-    symbolKeyword: symbolPattern + ":",
-    symbolClass: "<" + symbolPattern + ">",
-    symbolGlobal: "\\*" + symbolPattern + "\\*",
-    symbolConstant: "\\$" + symbolPattern
-  };
-  var patternStyles = {
-    symbolKeyword: "atom",
-    symbolClass: "tag",
-    symbolGlobal: "variable-2",
-    symbolConstant: "variable-3"
-  };
-
-  // Compile all patterns to regular expressions
-  for (var patternName in patterns)
-    if (patterns.hasOwnProperty(patternName))
-      patterns[patternName] = new RegExp("^" + patterns[patternName]);
-
-  // Names beginning "with-" and "without-" are commonly
-  // used as statement macro
-  patterns["keyword"] = [/^with(?:out)?-[-_a-zA-Z?!*@<>$%]+/];
-
-  var styles = {};
-  styles["keyword"] = "keyword";
-  styles["definition"] = "def";
-  styles["simpleDefinition"] = "def";
-  styles["signalingCalls"] = "builtin";
-
-  // protected words lookup table
-  var wordLookup = {};
-  var styleLookup = {};
-
-  [
-    "keyword",
-    "definition",
-    "simpleDefinition",
-    "signalingCalls"
-  ].forEach(function(type) {
-    words[type].forEach(function(word) {
-      wordLookup[word] = type;
-      styleLookup[word] = styles[type];
-    });
-  });
-
-
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-
-  var type, content;
-
-  function ret(_type, style, _content) {
-    type = _type;
-    content = _content;
-    return style;
-  }
-
-  function tokenBase(stream, state) {
-    // String
-    var ch = stream.peek();
-    if (ch == "'" || ch == '"') {
-      stream.next();
-      return chain(stream, state, tokenString(ch, "string", "string"));
-    }
-    // Comment
-    else if (ch == "/") {
-      stream.next();
-      if (stream.eat("*")) {
-        return chain(stream, state, tokenComment);
-      } else if (stream.eat("/")) {
-        stream.skipToEnd();
-        return ret("comment", "comment");
-      } else {
-        stream.skipTo(" ");
-        return ret("operator", "operator");
-      }
-    }
-    // Decimal
-    else if (/\d/.test(ch)) {
-      stream.match(/^\d*(?:\.\d*)?(?:e[+\-]?\d+)?/);
-      return ret("number", "number");
-    }
-    // Hash
-    else if (ch == "#") {
-      stream.next();
-      // Symbol with string syntax
-      ch = stream.peek();
-      if (ch == '"') {
-        stream.next();
-        return chain(stream, state, tokenString('"', "symbol", "string-2"));
-      }
-      // Binary number
-      else if (ch == "b") {
-        stream.next();
-        stream.eatWhile(/[01]/);
-        return ret("number", "number");
-      }
-      // Hex number
-      else if (ch == "x") {
-        stream.next();
-        stream.eatWhile(/[\da-f]/i);
-        return ret("number", "number");
-      }
-      // Octal number
-      else if (ch == "o") {
-        stream.next();
-        stream.eatWhile(/[0-7]/);
-        return ret("number", "number");
-      }
-      // Hash symbol
-      else {
-        stream.eatWhile(/[-a-zA-Z]/);
-        return ret("hash", "keyword");
-      }
-    } else if (stream.match("end")) {
-      return ret("end", "keyword");
-    }
-    for (var name in patterns) {
-      if (patterns.hasOwnProperty(name)) {
-        var pattern = patterns[name];
-        if ((pattern instanceof Array && pattern.some(function(p) {
-          return stream.match(p);
-        })) || stream.match(pattern))
-          return ret(name, patternStyles[name], stream.current());
-      }
-    }
-    if (stream.match("define")) {
-      return ret("definition", "def");
-    } else {
-      stream.eatWhile(/[\w\-]/);
-      // Keyword
-      if (wordLookup[stream.current()]) {
-        return ret(wordLookup[stream.current()], styleLookup[stream.current()], stream.current());
-      } else if (stream.current().match(symbol)) {
-        return ret("variable", "variable");
-      } else {
-        stream.next();
-        return ret("other", "variable-2");
-      }
-    }
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false,
-    ch;
-    while ((ch = stream.next())) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenString(quote, type, style) {
-    return function(stream, state) {
-      var next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote) {
-          end = true;
-          break;
-        }
-      }
-      if (end)
-        state.tokenize = tokenBase;
-      return ret(type, style);
-    };
-  }
-
-  // Interface
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        currentIndent: 0
-      };
-    },
-    token: function(stream, state) {
-      if (stream.eatSpace())
-        return null;
-      var style = state.tokenize(stream, state);
-      return style;
-    },
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/"
-  };
-});
-
-CodeMirror.defineMIME("text/x-dylan", "dylan");
-
-});
--- a/kallithea/public/codemirror/mode/ecl/ecl.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("ecl", function(config) {
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  function metaHook(stream, state) {
-    if (!state.startOfLine) return false;
-    stream.skipToEnd();
-    return "meta";
-  }
-
-  var indentUnit = config.indentUnit;
-  var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode");
-  var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait");
-  var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath");
-  var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode");
-  var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when");
-  var blockKeywords = words("catch class do else finally for if switch try while");
-  var atoms = words("true false null");
-  var hooks = {"#": metaHook};
-  var multiLineStrings;
-  var isOperatorChar = /[+\-*&%=<>!?|\/]/;
-
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (hooks[ch]) {
-      var result = hooks[ch](stream, state);
-      if (result !== false) return result;
-    }
-    if (ch == '"' || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_]/);
-    var cur = stream.current().toLowerCase();
-    if (keyword.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "keyword";
-    } else if (variable.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "variable";
-    } else if (variable_2.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "variable-2";
-    } else if (variable_3.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "variable-3";
-    } else if (builtin.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "builtin";
-    } else { //Data types are of from KEYWORD##
-                var i = cur.length - 1;
-                while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_'))
-                        --i;
-
-                if (i > 0) {
-                        var cur2 = cur.substr(0, i + 1);
-                if (variable_3.propertyIsEnumerable(cur2)) {
-                        if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement";
-                        return "variable-3";
-                }
-            }
-    }
-    if (atoms.propertyIsEnumerable(cur)) return "atom";
-    return null;
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || multiLineStrings))
-        state.tokenize = tokenBase;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    return state.context = new Context(state.indented, col, type, null, state.context);
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      return {
-        tokenize: null,
-        context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
-      else if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "}") {
-        while (ctx.type == "statement") ctx = popContext(state);
-        if (ctx.type == "}") ctx = popContext(state);
-        while (ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == ctx.type) popContext(state);
-      else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
-        pushContext(state, stream.column(), "statement");
-      state.startOfLine = false;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase && state.tokenize != null) return 0;
-      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
-      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
-      var closing = firstChar == ctx.type;
-      if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
-      else if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricChars: "{}"
-  };
-});
-
-CodeMirror.defineMIME("text/x-ecl", "ecl");
-
-});
--- a/kallithea/public/codemirror/mode/eiffel/eiffel.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("eiffel", function() {
-  function wordObj(words) {
-    var o = {};
-    for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
-    return o;
-  }
-  var keywords = wordObj([
-    'note',
-    'across',
-    'when',
-    'variant',
-    'until',
-    'unique',
-    'undefine',
-    'then',
-    'strip',
-    'select',
-    'retry',
-    'rescue',
-    'require',
-    'rename',
-    'reference',
-    'redefine',
-    'prefix',
-    'once',
-    'old',
-    'obsolete',
-    'loop',
-    'local',
-    'like',
-    'is',
-    'inspect',
-    'infix',
-    'include',
-    'if',
-    'frozen',
-    'from',
-    'external',
-    'export',
-    'ensure',
-    'end',
-    'elseif',
-    'else',
-    'do',
-    'creation',
-    'create',
-    'check',
-    'alias',
-    'agent',
-    'separate',
-    'invariant',
-    'inherit',
-    'indexing',
-    'feature',
-    'expanded',
-    'deferred',
-    'class',
-    'Void',
-    'True',
-    'Result',
-    'Precursor',
-    'False',
-    'Current',
-    'create',
-    'attached',
-    'detachable',
-    'as',
-    'and',
-    'implies',
-    'not',
-    'or'
-  ]);
-  var operators = wordObj([":=", "and then","and", "or","<<",">>"]);
-  var curPunc;
-
-  function chain(newtok, stream, state) {
-    state.tokenize.push(newtok);
-    return newtok(stream, state);
-  }
-
-  function tokenBase(stream, state) {
-    curPunc = null;
-    if (stream.eatSpace()) return null;
-    var ch = stream.next();
-    if (ch == '"'||ch == "'") {
-      return chain(readQuoted(ch, "string"), stream, state);
-    } else if (ch == "-"&&stream.eat("-")) {
-      stream.skipToEnd();
-      return "comment";
-    } else if (ch == ":"&&stream.eat("=")) {
-      return "operator";
-    } else if (/[0-9]/.test(ch)) {
-      stream.eatWhile(/[xXbBCc0-9\.]/);
-      stream.eat(/[\?\!]/);
-      return "ident";
-    } else if (/[a-zA-Z_0-9]/.test(ch)) {
-      stream.eatWhile(/[a-zA-Z_0-9]/);
-      stream.eat(/[\?\!]/);
-      return "ident";
-    } else if (/[=+\-\/*^%<>~]/.test(ch)) {
-      stream.eatWhile(/[=+\-\/*^%<>~]/);
-      return "operator";
-    } else {
-      return null;
-    }
-  }
-
-  function readQuoted(quote, style,  unescaped) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && (unescaped || !escaped)) {
-          state.tokenize.pop();
-          break;
-        }
-        escaped = !escaped && ch == "%";
-      }
-      return style;
-    };
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: [tokenBase]};
-    },
-
-    token: function(stream, state) {
-      var style = state.tokenize[state.tokenize.length-1](stream, state);
-      if (style == "ident") {
-        var word = stream.current();
-        style = keywords.propertyIsEnumerable(stream.current()) ? "keyword"
-          : operators.propertyIsEnumerable(stream.current()) ? "operator"
-          : /^[A-Z][A-Z_0-9]*$/g.test(word) ? "tag"
-          : /^0[bB][0-1]+$/g.test(word) ? "number"
-          : /^0[cC][0-7]+$/g.test(word) ? "number"
-          : /^0[xX][a-fA-F0-9]+$/g.test(word) ? "number"
-          : /^([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+)$/g.test(word) ? "number"
-          : /^[0-9]+$/g.test(word) ? "number"
-          : "variable";
-      }
-      return style;
-    },
-    lineComment: "--"
-  };
-});
-
-CodeMirror.defineMIME("text/x-eiffel", "eiffel");
-
-});
--- a/kallithea/public/codemirror/mode/erlang/erlang.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,622 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*jshint unused:true, eqnull:true, curly:true, bitwise:true */
-/*jshint undef:true, latedef:true, trailing:true */
-/*global CodeMirror:true */
-
-// erlang mode.
-// tokenizer -> token types -> CodeMirror styles
-// tokenizer maintains a parse stack
-// indenter uses the parse stack
-
-// TODO indenter:
-//   bit syntax
-//   old guard/bif/conversion clashes (e.g. "float/1")
-//   type/spec/opaque
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMIME("text/x-erlang", "erlang");
-
-CodeMirror.defineMode("erlang", function(cmCfg) {
-  "use strict";
-
-/////////////////////////////////////////////////////////////////////////////
-// constants
-
-  var typeWords = [
-    "-type", "-spec", "-export_type", "-opaque"];
-
-  var keywordWords = [
-    "after","begin","catch","case","cond","end","fun","if",
-    "let","of","query","receive","try","when"];
-
-  var separatorRE    = /[\->,;]/;
-  var separatorWords = [
-    "->",";",","];
-
-  var operatorAtomWords = [
-    "and","andalso","band","bnot","bor","bsl","bsr","bxor",
-    "div","not","or","orelse","rem","xor"];
-
-  var operatorSymbolRE    = /[\+\-\*\/<>=\|:!]/;
-  var operatorSymbolWords = [
-    "=","+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-","!"];
-
-  var openParenRE    = /[<\(\[\{]/;
-  var openParenWords = [
-    "<<","(","[","{"];
-
-  var closeParenRE    = /[>\)\]\}]/;
-  var closeParenWords = [
-    "}","]",")",">>"];
-
-  var guardWords = [
-    "is_atom","is_binary","is_bitstring","is_boolean","is_float",
-    "is_function","is_integer","is_list","is_number","is_pid",
-    "is_port","is_record","is_reference","is_tuple",
-    "atom","binary","bitstring","boolean","function","integer","list",
-    "number","pid","port","record","reference","tuple"];
-
-  var bifWords = [
-    "abs","adler32","adler32_combine","alive","apply","atom_to_binary",
-    "atom_to_list","binary_to_atom","binary_to_existing_atom",
-    "binary_to_list","binary_to_term","bit_size","bitstring_to_list",
-    "byte_size","check_process_code","contact_binary","crc32",
-    "crc32_combine","date","decode_packet","delete_module",
-    "disconnect_node","element","erase","exit","float","float_to_list",
-    "garbage_collect","get","get_keys","group_leader","halt","hd",
-    "integer_to_list","internal_bif","iolist_size","iolist_to_binary",
-    "is_alive","is_atom","is_binary","is_bitstring","is_boolean",
-    "is_float","is_function","is_integer","is_list","is_number","is_pid",
-    "is_port","is_process_alive","is_record","is_reference","is_tuple",
-    "length","link","list_to_atom","list_to_binary","list_to_bitstring",
-    "list_to_existing_atom","list_to_float","list_to_integer",
-    "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded",
-    "monitor_node","node","node_link","node_unlink","nodes","notalive",
-    "now","open_port","pid_to_list","port_close","port_command",
-    "port_connect","port_control","pre_loaded","process_flag",
-    "process_info","processes","purge_module","put","register",
-    "registered","round","self","setelement","size","spawn","spawn_link",
-    "spawn_monitor","spawn_opt","split_binary","statistics",
-    "term_to_binary","time","throw","tl","trunc","tuple_size",
-    "tuple_to_list","unlink","unregister","whereis"];
-
-// upper case: [A-Z] [Ø-Þ] [À-Ö]
-// lower case: [a-z] [ß-ö] [ø-ÿ]
-  var anumRE       = /[\w@Ø-ÞÀ-Öß-öø-ÿ]/;
-  var escapesRE    =
-    /[0-7]{1,3}|[bdefnrstv\\"']|\^[a-zA-Z]|x[0-9a-zA-Z]{2}|x{[0-9a-zA-Z]+}/;
-
-/////////////////////////////////////////////////////////////////////////////
-// tokenizer
-
-  function tokenizer(stream,state) {
-    // in multi-line string
-    if (state.in_string) {
-      state.in_string = (!doubleQuote(stream));
-      return rval(state,stream,"string");
-    }
-
-    // in multi-line atom
-    if (state.in_atom) {
-      state.in_atom = (!singleQuote(stream));
-      return rval(state,stream,"atom");
-    }
-
-    // whitespace
-    if (stream.eatSpace()) {
-      return rval(state,stream,"whitespace");
-    }
-
-    // attributes and type specs
-    if (!peekToken(state) &&
-        stream.match(/-\s*[a-zß-öø-ÿ][\wØ-ÞÀ-Öß-öø-ÿ]*/)) {
-      if (is_member(stream.current(),typeWords)) {
-        return rval(state,stream,"type");
-      }else{
-        return rval(state,stream,"attribute");
-      }
-    }
-
-    var ch = stream.next();
-
-    // comment
-    if (ch == '%') {
-      stream.skipToEnd();
-      return rval(state,stream,"comment");
-    }
-
-    // colon
-    if (ch == ":") {
-      return rval(state,stream,"colon");
-    }
-
-    // macro
-    if (ch == '?') {
-      stream.eatSpace();
-      stream.eatWhile(anumRE);
-      return rval(state,stream,"macro");
-    }
-
-    // record
-    if (ch == "#") {
-      stream.eatSpace();
-      stream.eatWhile(anumRE);
-      return rval(state,stream,"record");
-    }
-
-    // dollar escape
-    if (ch == "$") {
-      if (stream.next() == "\\" && !stream.match(escapesRE)) {
-        return rval(state,stream,"error");
-      }
-      return rval(state,stream,"number");
-    }
-
-    // dot
-    if (ch == ".") {
-      return rval(state,stream,"dot");
-    }
-
-    // quoted atom
-    if (ch == '\'') {
-      if (!(state.in_atom = (!singleQuote(stream)))) {
-        if (stream.match(/\s*\/\s*[0-9]/,false)) {
-          stream.match(/\s*\/\s*[0-9]/,true);
-          return rval(state,stream,"fun");      // 'f'/0 style fun
-        }
-        if (stream.match(/\s*\(/,false) || stream.match(/\s*:/,false)) {
-          return rval(state,stream,"function");
-        }
-      }
-      return rval(state,stream,"atom");
-    }
-
-    // string
-    if (ch == '"') {
-      state.in_string = (!doubleQuote(stream));
-      return rval(state,stream,"string");
-    }
-
-    // variable
-    if (/[A-Z_Ø-ÞÀ-Ö]/.test(ch)) {
-      stream.eatWhile(anumRE);
-      return rval(state,stream,"variable");
-    }
-
-    // atom/keyword/BIF/function
-    if (/[a-z_ß-öø-ÿ]/.test(ch)) {
-      stream.eatWhile(anumRE);
-
-      if (stream.match(/\s*\/\s*[0-9]/,false)) {
-        stream.match(/\s*\/\s*[0-9]/,true);
-        return rval(state,stream,"fun");      // f/0 style fun
-      }
-
-      var w = stream.current();
-
-      if (is_member(w,keywordWords)) {
-        return rval(state,stream,"keyword");
-      }else if (is_member(w,operatorAtomWords)) {
-        return rval(state,stream,"operator");
-      }else if (stream.match(/\s*\(/,false)) {
-        // 'put' and 'erlang:put' are bifs, 'foo:put' is not
-        if (is_member(w,bifWords) &&
-            ((peekToken(state).token != ":") ||
-             (peekToken(state,2).token == "erlang"))) {
-          return rval(state,stream,"builtin");
-        }else if (is_member(w,guardWords)) {
-          return rval(state,stream,"guard");
-        }else{
-          return rval(state,stream,"function");
-        }
-      }else if (is_member(w,operatorAtomWords)) {
-        return rval(state,stream,"operator");
-      }else if (lookahead(stream) == ":") {
-        if (w == "erlang") {
-          return rval(state,stream,"builtin");
-        } else {
-          return rval(state,stream,"function");
-        }
-      }else if (is_member(w,["true","false"])) {
-        return rval(state,stream,"boolean");
-      }else if (is_member(w,["true","false"])) {
-        return rval(state,stream,"boolean");
-      }else{
-        return rval(state,stream,"atom");
-      }
-    }
-
-    // number
-    var digitRE      = /[0-9]/;
-    var radixRE      = /[0-9a-zA-Z]/;         // 36#zZ style int
-    if (digitRE.test(ch)) {
-      stream.eatWhile(digitRE);
-      if (stream.eat('#')) {                // 36#aZ  style integer
-        if (!stream.eatWhile(radixRE)) {
-          stream.backUp(1);                 //"36#" - syntax error
-        }
-      } else if (stream.eat('.')) {       // float
-        if (!stream.eatWhile(digitRE)) {
-          stream.backUp(1);        // "3." - probably end of function
-        } else {
-          if (stream.eat(/[eE]/)) {        // float with exponent
-            if (stream.eat(/[-+]/)) {
-              if (!stream.eatWhile(digitRE)) {
-                stream.backUp(2);            // "2e-" - syntax error
-              }
-            } else {
-              if (!stream.eatWhile(digitRE)) {
-                stream.backUp(1);            // "2e" - syntax error
-              }
-            }
-          }
-        }
-      }
-      return rval(state,stream,"number");   // normal integer
-    }
-
-    // open parens
-    if (nongreedy(stream,openParenRE,openParenWords)) {
-      return rval(state,stream,"open_paren");
-    }
-
-    // close parens
-    if (nongreedy(stream,closeParenRE,closeParenWords)) {
-      return rval(state,stream,"close_paren");
-    }
-
-    // separators
-    if (greedy(stream,separatorRE,separatorWords)) {
-      return rval(state,stream,"separator");
-    }
-
-    // operators
-    if (greedy(stream,operatorSymbolRE,operatorSymbolWords)) {
-      return rval(state,stream,"operator");
-    }
-
-    return rval(state,stream,null);
-  }
-
-/////////////////////////////////////////////////////////////////////////////
-// utilities
-  function nongreedy(stream,re,words) {
-    if (stream.current().length == 1 && re.test(stream.current())) {
-      stream.backUp(1);
-      while (re.test(stream.peek())) {
-        stream.next();
-        if (is_member(stream.current(),words)) {
-          return true;
-        }
-      }
-      stream.backUp(stream.current().length-1);
-    }
-    return false;
-  }
-
-  function greedy(stream,re,words) {
-    if (stream.current().length == 1 && re.test(stream.current())) {
-      while (re.test(stream.peek())) {
-        stream.next();
-      }
-      while (0 < stream.current().length) {
-        if (is_member(stream.current(),words)) {
-          return true;
-        }else{
-          stream.backUp(1);
-        }
-      }
-      stream.next();
-    }
-    return false;
-  }
-
-  function doubleQuote(stream) {
-    return quote(stream, '"', '\\');
-  }
-
-  function singleQuote(stream) {
-    return quote(stream,'\'','\\');
-  }
-
-  function quote(stream,quoteChar,escapeChar) {
-    while (!stream.eol()) {
-      var ch = stream.next();
-      if (ch == quoteChar) {
-        return true;
-      }else if (ch == escapeChar) {
-        stream.next();
-      }
-    }
-    return false;
-  }
-
-  function lookahead(stream) {
-    var m = stream.match(/([\n\s]+|%[^\n]*\n)*(.)/,false);
-    return m ? m.pop() : "";
-  }
-
-  function is_member(element,list) {
-    return (-1 < list.indexOf(element));
-  }
-
-  function rval(state,stream,type) {
-
-    // parse stack
-    pushToken(state,realToken(type,stream));
-
-    // map erlang token type to CodeMirror style class
-    //     erlang             -> CodeMirror tag
-    switch (type) {
-      case "atom":        return "atom";
-      case "attribute":   return "attribute";
-      case "boolean":     return "atom";
-      case "builtin":     return "builtin";
-      case "close_paren": return null;
-      case "colon":       return null;
-      case "comment":     return "comment";
-      case "dot":         return null;
-      case "error":       return "error";
-      case "fun":         return "meta";
-      case "function":    return "tag";
-      case "guard":       return "property";
-      case "keyword":     return "keyword";
-      case "macro":       return "variable-2";
-      case "number":      return "number";
-      case "open_paren":  return null;
-      case "operator":    return "operator";
-      case "record":      return "bracket";
-      case "separator":   return null;
-      case "string":      return "string";
-      case "type":        return "def";
-      case "variable":    return "variable";
-      default:            return null;
-    }
-  }
-
-  function aToken(tok,col,ind,typ) {
-    return {token:  tok,
-            column: col,
-            indent: ind,
-            type:   typ};
-  }
-
-  function realToken(type,stream) {
-    return aToken(stream.current(),
-                 stream.column(),
-                 stream.indentation(),
-                 type);
-  }
-
-  function fakeToken(type) {
-    return aToken(type,0,0,type);
-  }
-
-  function peekToken(state,depth) {
-    var len = state.tokenStack.length;
-    var dep = (depth ? depth : 1);
-
-    if (len < dep) {
-      return false;
-    }else{
-      return state.tokenStack[len-dep];
-    }
-  }
-
-  function pushToken(state,token) {
-
-    if (!(token.type == "comment" || token.type == "whitespace")) {
-      state.tokenStack = maybe_drop_pre(state.tokenStack,token);
-      state.tokenStack = maybe_drop_post(state.tokenStack);
-    }
-  }
-
-  function maybe_drop_pre(s,token) {
-    var last = s.length-1;
-
-    if (0 < last && s[last].type === "record" && token.type === "dot") {
-      s.pop();
-    }else if (0 < last && s[last].type === "group") {
-      s.pop();
-      s.push(token);
-    }else{
-      s.push(token);
-    }
-    return s;
-  }
-
-  function maybe_drop_post(s) {
-    var last = s.length-1;
-
-    if (s[last].type === "dot") {
-      return [];
-    }
-    if (s[last].type === "fun" && s[last-1].token === "fun") {
-      return s.slice(0,last-1);
-    }
-    switch (s[s.length-1].token) {
-      case "}":    return d(s,{g:["{"]});
-      case "]":    return d(s,{i:["["]});
-      case ")":    return d(s,{i:["("]});
-      case ">>":   return d(s,{i:["<<"]});
-      case "end":  return d(s,{i:["begin","case","fun","if","receive","try"]});
-      case ",":    return d(s,{e:["begin","try","when","->",
-                                  ",","(","[","{","<<"]});
-      case "->":   return d(s,{r:["when"],
-                               m:["try","if","case","receive"]});
-      case ";":    return d(s,{E:["case","fun","if","receive","try","when"]});
-      case "catch":return d(s,{e:["try"]});
-      case "of":   return d(s,{e:["case"]});
-      case "after":return d(s,{e:["receive","try"]});
-      default:     return s;
-    }
-  }
-
-  function d(stack,tt) {
-    // stack is a stack of Token objects.
-    // tt is an object; {type:tokens}
-    // type is a char, tokens is a list of token strings.
-    // The function returns (possibly truncated) stack.
-    // It will descend the stack, looking for a Token such that Token.token
-    //  is a member of tokens. If it does not find that, it will normally (but
-    //  see "E" below) return stack. If it does find a match, it will remove
-    //  all the Tokens between the top and the matched Token.
-    // If type is "m", that is all it does.
-    // If type is "i", it will also remove the matched Token and the top Token.
-    // If type is "g", like "i", but add a fake "group" token at the top.
-    // If type is "r", it will remove the matched Token, but not the top Token.
-    // If type is "e", it will keep the matched Token but not the top Token.
-    // If type is "E", it behaves as for type "e", except if there is no match,
-    //  in which case it will return an empty stack.
-
-    for (var type in tt) {
-      var len = stack.length-1;
-      var tokens = tt[type];
-      for (var i = len-1; -1 < i ; i--) {
-        if (is_member(stack[i].token,tokens)) {
-          var ss = stack.slice(0,i);
-          switch (type) {
-              case "m": return ss.concat(stack[i]).concat(stack[len]);
-              case "r": return ss.concat(stack[len]);
-              case "i": return ss;
-              case "g": return ss.concat(fakeToken("group"));
-              case "E": return ss.concat(stack[i]);
-              case "e": return ss.concat(stack[i]);
-          }
-        }
-      }
-    }
-    return (type == "E" ? [] : stack);
-  }
-
-/////////////////////////////////////////////////////////////////////////////
-// indenter
-
-  function indenter(state,textAfter) {
-    var t;
-    var unit = cmCfg.indentUnit;
-    var wordAfter = wordafter(textAfter);
-    var currT = peekToken(state,1);
-    var prevT = peekToken(state,2);
-
-    if (state.in_string || state.in_atom) {
-      return CodeMirror.Pass;
-    }else if (!prevT) {
-      return 0;
-    }else if (currT.token == "when") {
-      return currT.column+unit;
-    }else if (wordAfter === "when" && prevT.type === "function") {
-      return prevT.indent+unit;
-    }else if (wordAfter === "(" && currT.token === "fun") {
-      return  currT.column+3;
-    }else if (wordAfter === "catch" && (t = getToken(state,["try"]))) {
-      return t.column;
-    }else if (is_member(wordAfter,["end","after","of"])) {
-      t = getToken(state,["begin","case","fun","if","receive","try"]);
-      return t ? t.column : CodeMirror.Pass;
-    }else if (is_member(wordAfter,closeParenWords)) {
-      t = getToken(state,openParenWords);
-      return t ? t.column : CodeMirror.Pass;
-    }else if (is_member(currT.token,[",","|","||"]) ||
-              is_member(wordAfter,[",","|","||"])) {
-      t = postcommaToken(state);
-      return t ? t.column+t.token.length : unit;
-    }else if (currT.token == "->") {
-      if (is_member(prevT.token, ["receive","case","if","try"])) {
-        return prevT.column+unit+unit;
-      }else{
-        return prevT.column+unit;
-      }
-    }else if (is_member(currT.token,openParenWords)) {
-      return currT.column+currT.token.length;
-    }else{
-      t = defaultToken(state);
-      return truthy(t) ? t.column+unit : 0;
-    }
-  }
-
-  function wordafter(str) {
-    var m = str.match(/,|[a-z]+|\}|\]|\)|>>|\|+|\(/);
-
-    return truthy(m) && (m.index === 0) ? m[0] : "";
-  }
-
-  function postcommaToken(state) {
-    var objs = state.tokenStack.slice(0,-1);
-    var i = getTokenIndex(objs,"type",["open_paren"]);
-
-    return truthy(objs[i]) ? objs[i] : false;
-  }
-
-  function defaultToken(state) {
-    var objs = state.tokenStack;
-    var stop = getTokenIndex(objs,"type",["open_paren","separator","keyword"]);
-    var oper = getTokenIndex(objs,"type",["operator"]);
-
-    if (truthy(stop) && truthy(oper) && stop < oper) {
-      return objs[stop+1];
-    } else if (truthy(stop)) {
-      return objs[stop];
-    } else {
-      return false;
-    }
-  }
-
-  function getToken(state,tokens) {
-    var objs = state.tokenStack;
-    var i = getTokenIndex(objs,"token",tokens);
-
-    return truthy(objs[i]) ? objs[i] : false;
-  }
-
-  function getTokenIndex(objs,propname,propvals) {
-
-    for (var i = objs.length-1; -1 < i ; i--) {
-      if (is_member(objs[i][propname],propvals)) {
-        return i;
-      }
-    }
-    return false;
-  }
-
-  function truthy(x) {
-    return (x !== false) && (x != null);
-  }
-
-/////////////////////////////////////////////////////////////////////////////
-// this object defines the mode
-
-  return {
-    startState:
-      function() {
-        return {tokenStack: [],
-                in_string:  false,
-                in_atom:    false};
-      },
-
-    token:
-      function(stream, state) {
-        return tokenizer(stream, state);
-      },
-
-    indent:
-      function(state, textAfter) {
-        return indenter(state,textAfter);
-      },
-
-    lineComment: "%"
-  };
-});
-
-});
--- a/kallithea/public/codemirror/mode/fortran/fortran.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("fortran", function() {
-  function words(array) {
-    var keys = {};
-    for (var i = 0; i < array.length; ++i) {
-      keys[array[i]] = true;
-    }
-    return keys;
-  }
-
-  var keywords = words([
-                  "abstract", "accept", "allocatable", "allocate",
-                  "array", "assign", "asynchronous", "backspace",
-                  "bind", "block", "byte", "call", "case",
-                  "class", "close", "common", "contains",
-                  "continue", "cycle", "data", "deallocate",
-                  "decode", "deferred", "dimension", "do",
-                  "elemental", "else", "encode", "end",
-                  "endif", "entry", "enumerator", "equivalence",
-                  "exit", "external", "extrinsic", "final",
-                  "forall", "format", "function", "generic",
-                  "go", "goto", "if", "implicit", "import", "include",
-                  "inquire", "intent", "interface", "intrinsic",
-                  "module", "namelist", "non_intrinsic",
-                  "non_overridable", "none", "nopass",
-                  "nullify", "open", "optional", "options",
-                  "parameter", "pass", "pause", "pointer",
-                  "print", "private", "program", "protected",
-                  "public", "pure", "read", "recursive", "result",
-                  "return", "rewind", "save", "select", "sequence",
-                  "stop", "subroutine", "target", "then", "to", "type",
-                  "use", "value", "volatile", "where", "while",
-                  "write"]);
-  var builtins = words(["abort", "abs", "access", "achar", "acos",
-                          "adjustl", "adjustr", "aimag", "aint", "alarm",
-                          "all", "allocated", "alog", "amax", "amin",
-                          "amod", "and", "anint", "any", "asin",
-                          "associated", "atan", "besj", "besjn", "besy",
-                          "besyn", "bit_size", "btest", "cabs", "ccos",
-                          "ceiling", "cexp", "char", "chdir", "chmod",
-                          "clog", "cmplx", "command_argument_count",
-                          "complex", "conjg", "cos", "cosh", "count",
-                          "cpu_time", "cshift", "csin", "csqrt", "ctime",
-                          "c_funloc", "c_loc", "c_associated", "c_null_ptr",
-                          "c_null_funptr", "c_f_pointer", "c_null_char",
-                          "c_alert", "c_backspace", "c_form_feed",
-                          "c_new_line", "c_carriage_return",
-                          "c_horizontal_tab", "c_vertical_tab", "dabs",
-                          "dacos", "dasin", "datan", "date_and_time",
-                          "dbesj", "dbesj", "dbesjn", "dbesy", "dbesy",
-                          "dbesyn", "dble", "dcos", "dcosh", "ddim", "derf",
-                          "derfc", "dexp", "digits", "dim", "dint", "dlog",
-                          "dlog", "dmax", "dmin", "dmod", "dnint",
-                          "dot_product", "dprod", "dsign", "dsinh",
-                          "dsin", "dsqrt", "dtanh", "dtan", "dtime",
-                          "eoshift", "epsilon", "erf", "erfc", "etime",
-                          "exit", "exp", "exponent", "extends_type_of",
-                          "fdate", "fget", "fgetc", "float", "floor",
-                          "flush", "fnum", "fputc", "fput", "fraction",
-                          "fseek", "fstat", "ftell", "gerror", "getarg",
-                          "get_command", "get_command_argument",
-                          "get_environment_variable", "getcwd",
-                          "getenv", "getgid", "getlog", "getpid",
-                          "getuid", "gmtime", "hostnm", "huge", "iabs",
-                          "iachar", "iand", "iargc", "ibclr", "ibits",
-                          "ibset", "ichar", "idate", "idim", "idint",
-                          "idnint", "ieor", "ierrno", "ifix", "imag",
-                          "imagpart", "index", "int", "ior", "irand",
-                          "isatty", "ishft", "ishftc", "isign",
-                          "iso_c_binding", "is_iostat_end", "is_iostat_eor",
-                          "itime", "kill", "kind", "lbound", "len", "len_trim",
-                          "lge", "lgt", "link", "lle", "llt", "lnblnk", "loc",
-                          "log", "logical", "long", "lshift", "lstat", "ltime",
-                          "matmul", "max", "maxexponent", "maxloc", "maxval",
-                          "mclock", "merge", "move_alloc", "min", "minexponent",
-                          "minloc", "minval", "mod", "modulo", "mvbits",
-                          "nearest", "new_line", "nint", "not", "or", "pack",
-                          "perror", "precision", "present", "product", "radix",
-                          "rand", "random_number", "random_seed", "range",
-                          "real", "realpart", "rename", "repeat", "reshape",
-                          "rrspacing", "rshift", "same_type_as", "scale",
-                          "scan", "second", "selected_int_kind",
-                          "selected_real_kind", "set_exponent", "shape",
-                          "short", "sign", "signal", "sinh", "sin", "sleep",
-                          "sngl", "spacing", "spread", "sqrt", "srand", "stat",
-                          "sum", "symlnk", "system", "system_clock", "tan",
-                          "tanh", "time", "tiny", "transfer", "transpose",
-                          "trim", "ttynam", "ubound", "umask", "unlink",
-                          "unpack", "verify", "xor", "zabs", "zcos", "zexp",
-                          "zlog", "zsin", "zsqrt"]);
-
-    var dataTypes =  words(["c_bool", "c_char", "c_double", "c_double_complex",
-                     "c_float", "c_float_complex", "c_funptr", "c_int",
-                     "c_int16_t", "c_int32_t", "c_int64_t", "c_int8_t",
-                     "c_int_fast16_t", "c_int_fast32_t", "c_int_fast64_t",
-                     "c_int_fast8_t", "c_int_least16_t", "c_int_least32_t",
-                     "c_int_least64_t", "c_int_least8_t", "c_intmax_t",
-                     "c_intptr_t", "c_long", "c_long_double",
-                     "c_long_double_complex", "c_long_long", "c_ptr",
-                     "c_short", "c_signed_char", "c_size_t", "character",
-                     "complex", "double", "integer", "logical", "real"]);
-  var isOperatorChar = /[+\-*&=<>\/\:]/;
-  var litOperator = new RegExp("(\.and\.|\.or\.|\.eq\.|\.lt\.|\.le\.|\.gt\.|\.ge\.|\.ne\.|\.not\.|\.eqv\.|\.neqv\.)", "i");
-
-  function tokenBase(stream, state) {
-
-    if (stream.match(litOperator)){
-        return 'operator';
-    }
-
-    var ch = stream.next();
-    if (ch == "!") {
-      stream.skipToEnd();
-      return "comment";
-    }
-    if (ch == '"' || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (/[\[\]\(\),]/.test(ch)) {
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_]/);
-    var word = stream.current().toLowerCase();
-
-    if (keywords.hasOwnProperty(word)){
-            return 'keyword';
-    }
-    if (builtins.hasOwnProperty(word) || dataTypes.hasOwnProperty(word)) {
-            return 'builtin';
-    }
-    return "variable";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {
-            end = true;
-            break;
-        }
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !escaped) state.tokenize = null;
-      return "string";
-    };
-  }
-
-  // Interface
-
-  return {
-    startState: function() {
-      return {tokenize: null};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta") return style;
-      return style;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-fortran", "fortran");
-
-});
--- a/kallithea/public/codemirror/mode/gas/gas.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,345 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("gas", function(_config, parserConfig) {
-  'use strict';
-
-  // If an architecture is specified, its initialization function may
-  // populate this array with custom parsing functions which will be
-  // tried in the event that the standard functions do not find a match.
-  var custom = [];
-
-  // The symbol used to start a line comment changes based on the target
-  // architecture.
-  // If no architecture is pased in "parserConfig" then only multiline
-  // comments will have syntax support.
-  var lineCommentStartSymbol = "";
-
-  // These directives are architecture independent.
-  // Machine specific directives should go in their respective
-  // architecture initialization function.
-  // Reference:
-  // http://sourceware.org/binutils/docs/as/Pseudo-Ops.html#Pseudo-Ops
-  var directives = {
-    ".abort" : "builtin",
-    ".align" : "builtin",
-    ".altmacro" : "builtin",
-    ".ascii" : "builtin",
-    ".asciz" : "builtin",
-    ".balign" : "builtin",
-    ".balignw" : "builtin",
-    ".balignl" : "builtin",
-    ".bundle_align_mode" : "builtin",
-    ".bundle_lock" : "builtin",
-    ".bundle_unlock" : "builtin",
-    ".byte" : "builtin",
-    ".cfi_startproc" : "builtin",
-    ".comm" : "builtin",
-    ".data" : "builtin",
-    ".def" : "builtin",
-    ".desc" : "builtin",
-    ".dim" : "builtin",
-    ".double" : "builtin",
-    ".eject" : "builtin",
-    ".else" : "builtin",
-    ".elseif" : "builtin",
-    ".end" : "builtin",
-    ".endef" : "builtin",
-    ".endfunc" : "builtin",
-    ".endif" : "builtin",
-    ".equ" : "builtin",
-    ".equiv" : "builtin",
-    ".eqv" : "builtin",
-    ".err" : "builtin",
-    ".error" : "builtin",
-    ".exitm" : "builtin",
-    ".extern" : "builtin",
-    ".fail" : "builtin",
-    ".file" : "builtin",
-    ".fill" : "builtin",
-    ".float" : "builtin",
-    ".func" : "builtin",
-    ".global" : "builtin",
-    ".gnu_attribute" : "builtin",
-    ".hidden" : "builtin",
-    ".hword" : "builtin",
-    ".ident" : "builtin",
-    ".if" : "builtin",
-    ".incbin" : "builtin",
-    ".include" : "builtin",
-    ".int" : "builtin",
-    ".internal" : "builtin",
-    ".irp" : "builtin",
-    ".irpc" : "builtin",
-    ".lcomm" : "builtin",
-    ".lflags" : "builtin",
-    ".line" : "builtin",
-    ".linkonce" : "builtin",
-    ".list" : "builtin",
-    ".ln" : "builtin",
-    ".loc" : "builtin",
-    ".loc_mark_labels" : "builtin",
-    ".local" : "builtin",
-    ".long" : "builtin",
-    ".macro" : "builtin",
-    ".mri" : "builtin",
-    ".noaltmacro" : "builtin",
-    ".nolist" : "builtin",
-    ".octa" : "builtin",
-    ".offset" : "builtin",
-    ".org" : "builtin",
-    ".p2align" : "builtin",
-    ".popsection" : "builtin",
-    ".previous" : "builtin",
-    ".print" : "builtin",
-    ".protected" : "builtin",
-    ".psize" : "builtin",
-    ".purgem" : "builtin",
-    ".pushsection" : "builtin",
-    ".quad" : "builtin",
-    ".reloc" : "builtin",
-    ".rept" : "builtin",
-    ".sbttl" : "builtin",
-    ".scl" : "builtin",
-    ".section" : "builtin",
-    ".set" : "builtin",
-    ".short" : "builtin",
-    ".single" : "builtin",
-    ".size" : "builtin",
-    ".skip" : "builtin",
-    ".sleb128" : "builtin",
-    ".space" : "builtin",
-    ".stab" : "builtin",
-    ".string" : "builtin",
-    ".struct" : "builtin",
-    ".subsection" : "builtin",
-    ".symver" : "builtin",
-    ".tag" : "builtin",
-    ".text" : "builtin",
-    ".title" : "builtin",
-    ".type" : "builtin",
-    ".uleb128" : "builtin",
-    ".val" : "builtin",
-    ".version" : "builtin",
-    ".vtable_entry" : "builtin",
-    ".vtable_inherit" : "builtin",
-    ".warning" : "builtin",
-    ".weak" : "builtin",
-    ".weakref" : "builtin",
-    ".word" : "builtin"
-  };
-
-  var registers = {};
-
-  function x86(_parserConfig) {
-    lineCommentStartSymbol = "#";
-
-    registers.ax  = "variable";
-    registers.eax = "variable-2";
-    registers.rax = "variable-3";
-
-    registers.bx  = "variable";
-    registers.ebx = "variable-2";
-    registers.rbx = "variable-3";
-
-    registers.cx  = "variable";
-    registers.ecx = "variable-2";
-    registers.rcx = "variable-3";
-
-    registers.dx  = "variable";
-    registers.edx = "variable-2";
-    registers.rdx = "variable-3";
-
-    registers.si  = "variable";
-    registers.esi = "variable-2";
-    registers.rsi = "variable-3";
-
-    registers.di  = "variable";
-    registers.edi = "variable-2";
-    registers.rdi = "variable-3";
-
-    registers.sp  = "variable";
-    registers.esp = "variable-2";
-    registers.rsp = "variable-3";
-
-    registers.bp  = "variable";
-    registers.ebp = "variable-2";
-    registers.rbp = "variable-3";
-
-    registers.ip  = "variable";
-    registers.eip = "variable-2";
-    registers.rip = "variable-3";
-
-    registers.cs  = "keyword";
-    registers.ds  = "keyword";
-    registers.ss  = "keyword";
-    registers.es  = "keyword";
-    registers.fs  = "keyword";
-    registers.gs  = "keyword";
-  }
-
-  function armv6(_parserConfig) {
-    // Reference:
-    // http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf
-    // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0301h/DDI0301H_arm1176jzfs_r0p7_trm.pdf
-    lineCommentStartSymbol = "@";
-    directives.syntax = "builtin";
-
-    registers.r0  = "variable";
-    registers.r1  = "variable";
-    registers.r2  = "variable";
-    registers.r3  = "variable";
-    registers.r4  = "variable";
-    registers.r5  = "variable";
-    registers.r6  = "variable";
-    registers.r7  = "variable";
-    registers.r8  = "variable";
-    registers.r9  = "variable";
-    registers.r10 = "variable";
-    registers.r11 = "variable";
-    registers.r12 = "variable";
-
-    registers.sp  = "variable-2";
-    registers.lr  = "variable-2";
-    registers.pc  = "variable-2";
-    registers.r13 = registers.sp;
-    registers.r14 = registers.lr;
-    registers.r15 = registers.pc;
-
-    custom.push(function(ch, stream) {
-      if (ch === '#') {
-        stream.eatWhile(/\w/);
-        return "number";
-      }
-    });
-  }
-
-  var arch = (parserConfig.architecture || "x86").toLowerCase();
-  if (arch === "x86") {
-    x86(parserConfig);
-  } else if (arch === "arm" || arch === "armv6") {
-    armv6(parserConfig);
-  }
-
-  function nextUntilUnescaped(stream, end) {
-    var escaped = false, next;
-    while ((next = stream.next()) != null) {
-      if (next === end && !escaped) {
-        return false;
-      }
-      escaped = !escaped && next === "\\";
-    }
-    return escaped;
-  }
-
-  function clikeComment(stream, state) {
-    var maybeEnd = false, ch;
-    while ((ch = stream.next()) != null) {
-      if (ch === "/" && maybeEnd) {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch === "*");
-    }
-    return "comment";
-  }
-
-  return {
-    startState: function() {
-      return {
-        tokenize: null
-      };
-    },
-
-    token: function(stream, state) {
-      if (state.tokenize) {
-        return state.tokenize(stream, state);
-      }
-
-      if (stream.eatSpace()) {
-        return null;
-      }
-
-      var style, cur, ch = stream.next();
-
-      if (ch === "/") {
-        if (stream.eat("*")) {
-          state.tokenize = clikeComment;
-          return clikeComment(stream, state);
-        }
-      }
-
-      if (ch === lineCommentStartSymbol) {
-        stream.skipToEnd();
-        return "comment";
-      }
-
-      if (ch === '"') {
-        nextUntilUnescaped(stream, '"');
-        return "string";
-      }
-
-      if (ch === '.') {
-        stream.eatWhile(/\w/);
-        cur = stream.current().toLowerCase();
-        style = directives[cur];
-        return style || null;
-      }
-
-      if (ch === '=') {
-        stream.eatWhile(/\w/);
-        return "tag";
-      }
-
-      if (ch === '{') {
-        return "braket";
-      }
-
-      if (ch === '}') {
-        return "braket";
-      }
-
-      if (/\d/.test(ch)) {
-        if (ch === "0" && stream.eat("x")) {
-          stream.eatWhile(/[0-9a-fA-F]/);
-          return "number";
-        }
-        stream.eatWhile(/\d/);
-        return "number";
-      }
-
-      if (/\w/.test(ch)) {
-        stream.eatWhile(/\w/);
-        if (stream.eat(":")) {
-          return 'tag';
-        }
-        cur = stream.current().toLowerCase();
-        style = registers[cur];
-        return style || null;
-      }
-
-      for (var i = 0; i < custom.length; i++) {
-        style = custom[i](ch, stream, state);
-        if (style) {
-          return style;
-        }
-      }
-    },
-
-    lineComment: lineCommentStartSymbol,
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/"
-  };
-});
-
-});
--- a/kallithea/public/codemirror/mode/gfm/gfm.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../markdown/markdown"), require("../../addon/mode/overlay"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../markdown/markdown", "../../addon/mode/overlay"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("gfm", function(config, modeConfig) {
-  var codeDepth = 0;
-  function blankLine(state) {
-    state.code = false;
-    return null;
-  }
-  var gfmOverlay = {
-    startState: function() {
-      return {
-        code: false,
-        codeBlock: false,
-        ateSpace: false
-      };
-    },
-    copyState: function(s) {
-      return {
-        code: s.code,
-        codeBlock: s.codeBlock,
-        ateSpace: s.ateSpace
-      };
-    },
-    token: function(stream, state) {
-      state.combineTokens = null;
-
-      // Hack to prevent formatting override inside code blocks (block and inline)
-      if (state.codeBlock) {
-        if (stream.match(/^```/)) {
-          state.codeBlock = false;
-          return null;
-        }
-        stream.skipToEnd();
-        return null;
-      }
-      if (stream.sol()) {
-        state.code = false;
-      }
-      if (stream.sol() && stream.match(/^```/)) {
-        stream.skipToEnd();
-        state.codeBlock = true;
-        return null;
-      }
-      // If this block is changed, it may need to be updated in Markdown mode
-      if (stream.peek() === '`') {
-        stream.next();
-        var before = stream.pos;
-        stream.eatWhile('`');
-        var difference = 1 + stream.pos - before;
-        if (!state.code) {
-          codeDepth = difference;
-          state.code = true;
-        } else {
-          if (difference === codeDepth) { // Must be exact
-            state.code = false;
-          }
-        }
-        return null;
-      } else if (state.code) {
-        stream.next();
-        return null;
-      }
-      // Check if space. If so, links can be formatted later on
-      if (stream.eatSpace()) {
-        state.ateSpace = true;
-        return null;
-      }
-      if (stream.sol() || state.ateSpace) {
-        state.ateSpace = false;
-        if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
-          // User/Project@SHA
-          // User@SHA
-          // SHA
-          state.combineTokens = true;
-          return "link";
-        } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
-          // User/Project#Num
-          // User#Num
-          // #Num
-          state.combineTokens = true;
-          return "link";
-        }
-      }
-      if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i) &&
-         stream.string.slice(stream.start - 2, stream.start) != "](") {
-        // URLs
-        // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
-        // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
-        state.combineTokens = true;
-        return "link";
-      }
-      stream.next();
-      return null;
-    },
-    blankLine: blankLine
-  };
-
-  var markdownConfig = {
-    underscoresBreakWords: false,
-    taskLists: true,
-    fencedCodeBlocks: true
-  };
-  for (var attr in modeConfig) {
-    markdownConfig[attr] = modeConfig[attr];
-  }
-  markdownConfig.name = "markdown";
-  CodeMirror.defineMIME("gfmBase", markdownConfig);
-  return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
-}, "markdown");
-
-});
--- a/kallithea/public/codemirror/mode/gherkin/gherkin.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
-Gherkin mode - http://www.cukes.info/
-Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues
-*/
-
-// Following Objs from Brackets implementation: https://github.com/tregusti/brackets-gherkin/blob/master/main.js
-//var Quotes = {
-//  SINGLE: 1,
-//  DOUBLE: 2
-//};
-
-//var regex = {
-//  keywords: /(Feature| {2}(Scenario|In order to|As|I)| {4}(Given|When|Then|And))/
-//};
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("gherkin", function () {
-  return {
-    startState: function () {
-      return {
-        lineNumber: 0,
-        tableHeaderLine: false,
-        allowFeature: true,
-        allowBackground: false,
-        allowScenario: false,
-        allowSteps: false,
-        allowPlaceholders: false,
-        allowMultilineArgument: false,
-        inMultilineString: false,
-        inMultilineTable: false,
-        inKeywordLine: false
-      };
-    },
-    token: function (stream, state) {
-      if (stream.sol()) {
-        state.lineNumber++;
-        state.inKeywordLine = false;
-        if (state.inMultilineTable) {
-            state.tableHeaderLine = false;
-            if (!stream.match(/\s*\|/, false)) {
-              state.allowMultilineArgument = false;
-              state.inMultilineTable = false;
-            }
-        }
-      }
-
-      stream.eatSpace();
-
-      if (state.allowMultilineArgument) {
-
-        // STRING
-        if (state.inMultilineString) {
-          if (stream.match('"""')) {
-            state.inMultilineString = false;
-            state.allowMultilineArgument = false;
-          } else {
-            stream.match(/.*/);
-          }
-          return "string";
-        }
-
-        // TABLE
-        if (state.inMultilineTable) {
-          if (stream.match(/\|\s*/)) {
-            return "bracket";
-          } else {
-            stream.match(/[^\|]*/);
-            return state.tableHeaderLine ? "header" : "string";
-          }
-        }
-
-        // DETECT START
-        if (stream.match('"""')) {
-          // String
-          state.inMultilineString = true;
-          return "string";
-        } else if (stream.match("|")) {
-          // Table
-          state.inMultilineTable = true;
-          state.tableHeaderLine = true;
-          return "bracket";
-        }
-
-      }
-
-      // LINE COMMENT
-      if (stream.match(/#.*/)) {
-        return "comment";
-
-      // TAG
-      } else if (!state.inKeywordLine && stream.match(/@\S+/)) {
-        return "tag";
-
-      // FEATURE
-      } else if (!state.inKeywordLine && state.allowFeature && stream.match(/(機能|功能|フィーチャ|기능|โครงหลัก|ความสามารถ|ความต้องการทางธุรกิจ|ಹೆಚ್ಚಳ|గుణము|ਮੁਹਾਂਦਰਾ|ਨਕਸ਼ ਨੁਹਾਰ|ਖਾਸੀਅਤ|रूप लेख|وِیژگی|خاصية|תכונה|Функціонал|Функция|Функционалност|Функционал|Үзенчәлеклелек|Свойство|Особина|Мөмкинлек|Могућност|Λειτουργία|Δυνατότητα|Właściwość|Vlastnosť|Trajto|Tính năng|Savybė|Pretty much|Požiadavka|Požadavek|Potrzeba biznesowa|Özellik|Osobina|Ominaisuus|Omadus|OH HAI|Mogućnost|Mogucnost|Jellemző|Hwæt|Hwaet|Funzionalità|Funktionalitéit|Funktionalität|Funkcja|Funkcionalnost|Funkcionalitāte|Funkcia|Fungsi|Functionaliteit|Funcționalitate|Funcţionalitate|Functionalitate|Funcionalitat|Funcionalidade|Fonctionnalité|Fitur|Fīča|Feature|Eiginleiki|Egenskap|Egenskab|Característica|Caracteristica|Business Need|Aspekt|Arwedd|Ahoy matey!|Ability):/)) {
-        state.allowScenario = true;
-        state.allowBackground = true;
-        state.allowPlaceholders = false;
-        state.allowSteps = false;
-        state.allowMultilineArgument = false;
-        state.inKeywordLine = true;
-        return "keyword";
-
-      // BACKGROUND
-      } else if (!state.inKeywordLine && state.allowBackground && stream.match(/(背景|배경|แนวคิด|ಹಿನ್ನೆಲೆ|నేపథ్యం|ਪਿਛੋਕੜ|पृष्ठभूमि|زمینه|الخلفية|רקע|Тарих|Предыстория|Предистория|Позадина|Передумова|Основа|Контекст|Кереш|Υπόβαθρο|Założenia|Yo\-ho\-ho|Tausta|Taust|Situācija|Rerefons|Pozadina|Pozadie|Pozadí|Osnova|Latar Belakang|Kontext|Konteksts|Kontekstas|Kontekst|Háttér|Hannergrond|Grundlage|Geçmiş|Fundo|Fono|First off|Dis is what went down|Dasar|Contexto|Contexte|Context|Contesto|Cenário de Fundo|Cenario de Fundo|Cefndir|Bối cảnh|Bakgrunnur|Bakgrunn|Bakgrund|Baggrund|Background|B4|Antecedents|Antecedentes|Ær|Aer|Achtergrond):/)) {
-        state.allowPlaceholders = false;
-        state.allowSteps = true;
-        state.allowBackground = false;
-        state.allowMultilineArgument = false;
-        state.inKeywordLine = true;
-        return "keyword";
-
-      // SCENARIO OUTLINE
-      } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景大綱|场景大纲|劇本大綱|剧本大纲|テンプレ|シナリオテンプレート|シナリオテンプレ|シナリオアウトライン|시나리오 개요|สรุปเหตุการณ์|โครงสร้างของเหตุการณ์|ವಿವರಣೆ|కథనం|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ|ਪਟਕਥਾ ਢਾਂਚਾ|परिदृश्य रूपरेखा|سيناريو مخطط|الگوی سناریو|תבנית תרחיש|Сценарийның төзелеше|Сценарий структураси|Структура сценарію|Структура сценария|Структура сценарија|Скица|Рамка на сценарий|Концепт|Περιγραφή Σεναρίου|Wharrimean is|Template Situai|Template Senario|Template Keadaan|Tapausaihio|Szenariogrundriss|Szablon scenariusza|Swa hwær swa|Swa hwaer swa|Struktura scenarija|Structură scenariu|Structura scenariu|Skica|Skenario konsep|Shiver me timbers|Senaryo taslağı|Schema dello scenario|Scenariomall|Scenariomal|Scenario Template|Scenario Outline|Scenario Amlinellol|Scenārijs pēc parauga|Scenarijaus šablonas|Reckon it's like|Raamstsenaarium|Plang vum Szenario|Plan du Scénario|Plan du scénario|Osnova scénáře|Osnova Scenára|Náčrt Scenáru|Náčrt Scénáře|Náčrt Scenára|MISHUN SRSLY|Menggariskan Senario|Lýsing Dæma|Lýsing Atburðarásar|Konturo de la scenaro|Koncept|Khung tình huống|Khung kịch bản|Forgatókönyv vázlat|Esquema do Cenário|Esquema do Cenario|Esquema del escenario|Esquema de l'escenari|Esbozo do escenario|Delineação do Cenário|Delineacao do Cenario|All y'all|Abstrakt Scenario|Abstract Scenario):/)) {
-        state.allowPlaceholders = true;
-        state.allowSteps = true;
-        state.allowMultilineArgument = false;
-        state.inKeywordLine = true;
-        return "keyword";
-
-      // EXAMPLES
-      } else if (state.allowScenario && stream.match(/(例子|例|サンプル|예|ชุดของเหตุการณ์|ชุดของตัวอย่าง|ಉದಾಹರಣೆಗಳು|ఉదాహరణలు|ਉਦਾਹਰਨਾਂ|उदाहरण|نمونه ها|امثلة|דוגמאות|Үрнәкләр|Сценарији|Примеры|Примери|Приклади|Мисоллар|Мисаллар|Σενάρια|Παραδείγματα|You'll wanna|Voorbeelden|Variantai|Tapaukset|Se þe|Se the|Se ðe|Scenarios|Scenariji|Scenarijai|Przykłady|Primjeri|Primeri|Příklady|Príklady|Piemēri|Példák|Pavyzdžiai|Paraugs|Örnekler|Juhtumid|Exemplos|Exemples|Exemple|Exempel|EXAMPLZ|Examples|Esempi|Enghreifftiau|Ekzemploj|Eksempler|Ejemplos|Dữ liệu|Dead men tell no tales|Dæmi|Contoh|Cenários|Cenarios|Beispiller|Beispiele|Atburðarásir):/)) {
-        state.allowPlaceholders = false;
-        state.allowSteps = true;
-        state.allowBackground = false;
-        state.allowMultilineArgument = true;
-        return "keyword";
-
-      // SCENARIO
-      } else if (!state.inKeywordLine && state.allowScenario && stream.match(/(場景|场景|劇本|剧本|シナリオ|시나리오|เหตุการณ์|ಕಥಾಸಾರಾಂಶ|సన్నివేశం|ਪਟਕਥਾ|परिदृश्य|سيناريو|سناریو|תרחיש|Сценарій|Сценарио|Сценарий|Пример|Σενάριο|Tình huống|The thing of it is|Tapaus|Szenario|Swa|Stsenaarium|Skenario|Situai|Senaryo|Senario|Scenaro|Scenariusz|Scenariu|Scénario|Scenario|Scenarijus|Scenārijs|Scenarij|Scenarie|Scénář|Scenár|Primer|MISHUN|Kịch bản|Keadaan|Heave to|Forgatókönyv|Escenario|Escenari|Cenário|Cenario|Awww, look mate|Atburðarás):/)) {
-        state.allowPlaceholders = false;
-        state.allowSteps = true;
-        state.allowBackground = false;
-        state.allowMultilineArgument = false;
-        state.inKeywordLine = true;
-        return "keyword";
-
-      // STEPS
-      } else if (!state.inKeywordLine && state.allowSteps && stream.match(/(那麼|那么|而且|當|当|并且|同時|同时|前提|假设|假設|假定|假如|但是|但し|並且|もし|ならば|ただし|しかし|かつ|하지만|조건|먼저|만일|만약|단|그리고|그러면|และ |เมื่อ |แต่ |ดังนั้น |กำหนดให้ |ಸ್ಥಿತಿಯನ್ನು |ಮತ್ತು |ನೀಡಿದ |ನಂತರ |ಆದರೆ |మరియు |చెప్పబడినది |కాని |ఈ పరిస్థితిలో |అప్పుడు |ਪਰ |ਤਦ |ਜੇਕਰ |ਜਿਵੇਂ ਕਿ |ਜਦੋਂ |ਅਤੇ |यदि |परन्तु |पर |तब |तदा |तथा |जब |चूंकि |किन्तु |कदा |और |अगर |و |هنگامی |متى |لكن |عندما |ثم |بفرض |با فرض |اما |اذاً |آنگاه |כאשר |וגם |בהינתן |אזי |אז |אבל |Якщо |Һәм |Унда |Тоді |Тогда |То |Также |Та |Пусть |Припустимо, що |Припустимо |Онда |Но |Нехай |Нәтиҗәдә |Лекин |Ләкин |Коли |Когда |Когато |Када |Кад |К тому же |І |И |Задато |Задати |Задате |Если |Допустим |Дано |Дадено |Вә |Ва |Бирок |Әмма |Әйтик |Әгәр |Аммо |Али |Але |Агар |А також |А |Τότε |Όταν |Και |Δεδομένου |Αλλά |Þurh |Þegar |Þa þe |Þá |Þa |Zatati |Zakładając |Zadato |Zadate |Zadano |Zadani |Zadan |Za předpokladu |Za predpokladu |Youse know when youse got |Youse know like when |Yna |Yeah nah |Y'know |Y |Wun |Wtedy |When y'all |When |Wenn |WEN |wann |Ve |Và |Und |Un |ugeholl |Too right |Thurh |Thì |Then y'all |Then |Tha the |Tha |Tetapi |Tapi |Tak |Tada |Tad |Stel |Soit |Siis |Și |Şi |Si |Sed |Se |Så |Quando |Quand |Quan |Pryd |Potom |Pokud |Pokiaľ |Però |Pero |Pak |Oraz |Onda |Ond |Oletetaan |Og |Och |O zaman |Niin |Nhưng |När |Når |Mutta |Men |Mas |Maka |Majd |Mając |Mais |Maar |mä |Ma |Lorsque |Lorsqu'|Logo |Let go and haul |Kun |Kuid |Kui |Kiedy |Khi |Ketika |Kemudian |Keď |Když |Kaj |Kai |Kada |Kad |Jeżeli |Jeśli |Ja |It's just unbelievable |Ir |I CAN HAZ |I |Ha |Givun |Givet |Given y'all |Given |Gitt |Gegeven |Gegeben seien |Gegeben sei |Gdy |Gangway! |Fakat |Étant donnés |Etant donnés |Étant données |Etant données |Étant donnée |Etant donnée |Étant donné |Etant donné |Et |És |Entonces |Entón |Então |Entao |En |Eğer ki |Ef |Eeldades |E |Ðurh |Duota |Dun |Donitaĵo |Donat |Donada |Do |Diyelim ki |Diberi |Dengan |Den youse gotta |DEN |De |Dato |Dați fiind |Daţi fiind |Dati fiind |Dati |Date fiind |Date |Data |Dat fiind |Dar |Dann |dann |Dan |Dados |Dado |Dadas |Dada |Ða ðe |Ða |Cuando |Cho |Cando |Când |Cand |Cal |But y'all |But at the end of the day I reckon |BUT |But |Buh |Blimey! |Biết |Bet |Bagi |Aye |awer |Avast! |Atunci |Atesa |Atès |Apabila |Anrhegedig a |Angenommen |And y'all |And |AN |An |an |Amikor |Amennyiben |Ama |Als |Alors |Allora |Ali |Aleshores |Ale |Akkor |Ak |Adott |Ac |Aber |A zároveň |A tiež |A taktiež |A také |A |a |7 |\* )/)) {
-        state.inStep = true;
-        state.allowPlaceholders = true;
-        state.allowMultilineArgument = true;
-        state.inKeywordLine = true;
-        return "keyword";
-
-      // INLINE STRING
-      } else if (stream.match(/"[^"]*"?/)) {
-        return "string";
-
-      // PLACEHOLDER
-      } else if (state.allowPlaceholders && stream.match(/<[^>]*>?/)) {
-        return "variable";
-
-      // Fall through
-      } else {
-        stream.next();
-        stream.eatWhile(/[^@"<#]/);
-        return null;
-      }
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-feature", "gherkin");
-
-});
--- a/kallithea/public/codemirror/mode/go/go.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("go", function(config) {
-  var indentUnit = config.indentUnit;
-
-  var keywords = {
-    "break":true, "case":true, "chan":true, "const":true, "continue":true,
-    "default":true, "defer":true, "else":true, "fallthrough":true, "for":true,
-    "func":true, "go":true, "goto":true, "if":true, "import":true,
-    "interface":true, "map":true, "package":true, "range":true, "return":true,
-    "select":true, "struct":true, "switch":true, "type":true, "var":true,
-    "bool":true, "byte":true, "complex64":true, "complex128":true,
-    "float32":true, "float64":true, "int8":true, "int16":true, "int32":true,
-    "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true,
-    "uint64":true, "int":true, "uint":true, "uintptr":true
-  };
-
-  var atoms = {
-    "true":true, "false":true, "iota":true, "nil":true, "append":true,
-    "cap":true, "close":true, "complex":true, "copy":true, "imag":true,
-    "len":true, "make":true, "new":true, "panic":true, "print":true,
-    "println":true, "real":true, "recover":true
-  };
-
-  var isOperatorChar = /[+\-*&^%:=<>!|\/]/;
-
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"' || ch == "'" || ch == "`") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (/[\d\.]/.test(ch)) {
-      if (ch == ".") {
-        stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/);
-      } else if (ch == "0") {
-        stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);
-      } else {
-        stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/);
-      }
-      return "number";
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_\xa1-\uffff]/);
-    var cur = stream.current();
-    if (keywords.propertyIsEnumerable(cur)) {
-      if (cur == "case" || cur == "default") curPunc = "case";
-      return "keyword";
-    }
-    if (atoms.propertyIsEnumerable(cur)) return "atom";
-    return "variable";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || quote == "`"))
-        state.tokenize = tokenBase;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    return state.context = new Context(state.indented, col, type, null, state.context);
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      return {
-        tokenize: null,
-        context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-        if (ctx.type == "case") ctx.type = "}";
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "case") ctx.type = "case";
-      else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state);
-      else if (curPunc == ctx.type) popContext(state);
-      state.startOfLine = false;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase && state.tokenize != null) return 0;
-      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
-      if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) {
-        state.context.type = "}";
-        return ctx.indented;
-      }
-      var closing = firstChar == ctx.type;
-      if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricChars: "{}):",
-    fold: "brace",
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: "//"
-  };
-});
-
-CodeMirror.defineMIME("text/x-go", "go");
-
-});
--- a/kallithea/public/codemirror/mode/groovy/groovy.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,226 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("groovy", function(config) {
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var keywords = words(
-    "abstract as assert boolean break byte case catch char class const continue def default " +
-    "do double else enum extends final finally float for goto if implements import in " +
-    "instanceof int interface long native new package private protected public return " +
-    "short static strictfp super switch synchronized threadsafe throw throws transient " +
-    "try void volatile while");
-  var blockKeywords = words("catch class do else finally for if switch try while enum interface def");
-  var atoms = words("null true false this");
-
-  var curPunc;
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"' || ch == "'") {
-      return startString(ch, stream, state);
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize.push(tokenComment);
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      if (expectExpression(state.lastToken)) {
-        return startString(ch, stream, state);
-      }
-    }
-    if (ch == "-" && stream.eat(">")) {
-      curPunc = "->";
-      return null;
-    }
-    if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
-      stream.eatWhile(/[+\-*&%=<>|~]/);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_]/);
-    if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
-    if (state.lastToken == ".") return "property";
-    if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
-    var cur = stream.current();
-    if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
-    if (keywords.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "keyword";
-    }
-    return "variable";
-  }
-  tokenBase.isBase = true;
-
-  function startString(quote, stream, state) {
-    var tripleQuoted = false;
-    if (quote != "/" && stream.eat(quote)) {
-      if (stream.eat(quote)) tripleQuoted = true;
-      else return "string";
-    }
-    function t(stream, state) {
-      var escaped = false, next, end = !tripleQuoted;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {
-          if (!tripleQuoted) { break; }
-          if (stream.match(quote + quote)) { end = true; break; }
-        }
-        if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
-          state.tokenize.push(tokenBaseUntilBrace());
-          return "string";
-        }
-        escaped = !escaped && next == "\\";
-      }
-      if (end) state.tokenize.pop();
-      return "string";
-    }
-    state.tokenize.push(t);
-    return t(stream, state);
-  }
-
-  function tokenBaseUntilBrace() {
-    var depth = 1;
-    function t(stream, state) {
-      if (stream.peek() == "}") {
-        depth--;
-        if (depth == 0) {
-          state.tokenize.pop();
-          return state.tokenize[state.tokenize.length-1](stream, state);
-        }
-      } else if (stream.peek() == "{") {
-        depth++;
-      }
-      return tokenBase(stream, state);
-    }
-    t.isBase = true;
-    return t;
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize.pop();
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function expectExpression(last) {
-    return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
-      last == "newstatement" || last == "keyword" || last == "proplabel";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    return state.context = new Context(state.indented, col, type, null, state.context);
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      return {
-        tokenize: [tokenBase],
-        context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true,
-        lastToken: null
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-        // Automatic semicolon insertion
-        if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
-          popContext(state); ctx = state.context;
-        }
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = state.tokenize[state.tokenize.length-1](stream, state);
-      if (style == "comment") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
-      // Handle indentation for {x -> \n ... }
-      else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
-        popContext(state);
-        state.context.align = false;
-      }
-      else if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "}") {
-        while (ctx.type == "statement") ctx = popContext(state);
-        if (ctx.type == "}") ctx = popContext(state);
-        while (ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == ctx.type) popContext(state);
-      else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
-        pushContext(state, stream.column(), "statement");
-      state.startOfLine = false;
-      state.lastToken = curPunc || style;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (!state.tokenize[state.tokenize.length-1].isBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
-      if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
-      var closing = firstChar == ctx.type;
-      if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
-      else if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indented + (closing ? 0 : config.indentUnit);
-    },
-
-    electricChars: "{}",
-    fold: "brace"
-  };
-});
-
-CodeMirror.defineMIME("text/x-groovy", "groovy");
-
-});
--- a/kallithea/public/codemirror/mode/haml/haml.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-  // full haml mode. This handled embeded ruby and html fragments too
-  CodeMirror.defineMode("haml", function(config) {
-    var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"});
-    var rubyMode = CodeMirror.getMode(config, "ruby");
-
-    function rubyInQuote(endQuote) {
-      return function(stream, state) {
-        var ch = stream.peek();
-        if (ch == endQuote && state.rubyState.tokenize.length == 1) {
-          // step out of ruby context as it seems to complete processing all the braces
-          stream.next();
-          state.tokenize = html;
-          return "closeAttributeTag";
-        } else {
-          return ruby(stream, state);
-        }
-      };
-    }
-
-    function ruby(stream, state) {
-      if (stream.match("-#")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      return rubyMode.token(stream, state.rubyState);
-    }
-
-    function html(stream, state) {
-      var ch = stream.peek();
-
-      // handle haml declarations. All declarations that cant be handled here
-      // will be passed to html mode
-      if (state.previousToken.style == "comment" ) {
-        if (state.indented > state.previousToken.indented) {
-          stream.skipToEnd();
-          return "commentLine";
-        }
-      }
-
-      if (state.startOfLine) {
-        if (ch == "!" && stream.match("!!")) {
-          stream.skipToEnd();
-          return "tag";
-        } else if (stream.match(/^%[\w:#\.]+=/)) {
-          state.tokenize = ruby;
-          return "hamlTag";
-        } else if (stream.match(/^%[\w:]+/)) {
-          return "hamlTag";
-        } else if (ch == "/" ) {
-          stream.skipToEnd();
-          return "comment";
-        }
-      }
-
-      if (state.startOfLine || state.previousToken.style == "hamlTag") {
-        if ( ch == "#" || ch == ".") {
-          stream.match(/[\w-#\.]*/);
-          return "hamlAttribute";
-        }
-      }
-
-      // donot handle --> as valid ruby, make it HTML close comment instead
-      if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) {
-        state.tokenize = ruby;
-        return state.tokenize(stream, state);
-      }
-
-      if (state.previousToken.style == "hamlTag" ||
-          state.previousToken.style == "closeAttributeTag" ||
-          state.previousToken.style == "hamlAttribute") {
-        if (ch == "(") {
-          state.tokenize = rubyInQuote(")");
-          return state.tokenize(stream, state);
-        } else if (ch == "{") {
-          state.tokenize = rubyInQuote("}");
-          return state.tokenize(stream, state);
-        }
-      }
-
-      return htmlMode.token(stream, state.htmlState);
-    }
-
-    return {
-      // default to html mode
-      startState: function() {
-        var htmlState = htmlMode.startState();
-        var rubyState = rubyMode.startState();
-        return {
-          htmlState: htmlState,
-          rubyState: rubyState,
-          indented: 0,
-          previousToken: { style: null, indented: 0},
-          tokenize: html
-        };
-      },
-
-      copyState: function(state) {
-        return {
-          htmlState : CodeMirror.copyState(htmlMode, state.htmlState),
-          rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
-          indented: state.indented,
-          previousToken: state.previousToken,
-          tokenize: state.tokenize
-        };
-      },
-
-      token: function(stream, state) {
-        if (stream.sol()) {
-          state.indented = stream.indentation();
-          state.startOfLine = true;
-        }
-        if (stream.eatSpace()) return null;
-        var style = state.tokenize(stream, state);
-        state.startOfLine = false;
-        // dont record comment line as we only want to measure comment line with
-        // the opening comment block
-        if (style && style != "commentLine") {
-          state.previousToken = { style: style, indented: state.indented };
-        }
-        // if current state is ruby and the previous token is not `,` reset the
-        // tokenize to html
-        if (stream.eol() && state.tokenize == ruby) {
-          stream.backUp(1);
-          var ch = stream.peek();
-          stream.next();
-          if (ch && ch != ",") {
-            state.tokenize = html;
-          }
-        }
-        // reprocess some of the specific style tag when finish setting previousToken
-        if (style == "hamlTag") {
-          style = "tag";
-        } else if (style == "commentLine") {
-          style = "comment";
-        } else if (style == "hamlAttribute") {
-          style = "attribute";
-        } else if (style == "closeAttributeTag") {
-          style = null;
-        }
-        return style;
-      }
-    };
-  }, "htmlmixed", "ruby");
-
-  CodeMirror.defineMIME("text/x-haml", "haml");
-});
--- a/kallithea/public/codemirror/mode/haskell/haskell.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,267 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("haskell", function(_config, modeConfig) {
-
-  function switchState(source, setState, f) {
-    setState(f);
-    return f(source, setState);
-  }
-
-  // These should all be Unicode extended, as per the Haskell 2010 report
-  var smallRE = /[a-z_]/;
-  var largeRE = /[A-Z]/;
-  var digitRE = /\d/;
-  var hexitRE = /[0-9A-Fa-f]/;
-  var octitRE = /[0-7]/;
-  var idRE = /[a-z_A-Z0-9'\xa1-\uffff]/;
-  var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/;
-  var specialRE = /[(),;[\]`{}]/;
-  var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer
-
-  function normal(source, setState) {
-    if (source.eatWhile(whiteCharRE)) {
-      return null;
-    }
-
-    var ch = source.next();
-    if (specialRE.test(ch)) {
-      if (ch == '{' && source.eat('-')) {
-        var t = "comment";
-        if (source.eat('#')) {
-          t = "meta";
-        }
-        return switchState(source, setState, ncomment(t, 1));
-      }
-      return null;
-    }
-
-    if (ch == '\'') {
-      if (source.eat('\\')) {
-        source.next();  // should handle other escapes here
-      }
-      else {
-        source.next();
-      }
-      if (source.eat('\'')) {
-        return "string";
-      }
-      return "error";
-    }
-
-    if (ch == '"') {
-      return switchState(source, setState, stringLiteral);
-    }
-
-    if (largeRE.test(ch)) {
-      source.eatWhile(idRE);
-      if (source.eat('.')) {
-        return "qualifier";
-      }
-      return "variable-2";
-    }
-
-    if (smallRE.test(ch)) {
-      source.eatWhile(idRE);
-      return "variable";
-    }
-
-    if (digitRE.test(ch)) {
-      if (ch == '0') {
-        if (source.eat(/[xX]/)) {
-          source.eatWhile(hexitRE); // should require at least 1
-          return "integer";
-        }
-        if (source.eat(/[oO]/)) {
-          source.eatWhile(octitRE); // should require at least 1
-          return "number";
-        }
-      }
-      source.eatWhile(digitRE);
-      var t = "number";
-      if (source.match(/^\.\d+/)) {
-        t = "number";
-      }
-      if (source.eat(/[eE]/)) {
-        t = "number";
-        source.eat(/[-+]/);
-        source.eatWhile(digitRE); // should require at least 1
-      }
-      return t;
-    }
-
-    if (ch == "." && source.eat("."))
-      return "keyword";
-
-    if (symbolRE.test(ch)) {
-      if (ch == '-' && source.eat(/-/)) {
-        source.eatWhile(/-/);
-        if (!source.eat(symbolRE)) {
-          source.skipToEnd();
-          return "comment";
-        }
-      }
-      var t = "variable";
-      if (ch == ':') {
-        t = "variable-2";
-      }
-      source.eatWhile(symbolRE);
-      return t;
-    }
-
-    return "error";
-  }
-
-  function ncomment(type, nest) {
-    if (nest == 0) {
-      return normal;
-    }
-    return function(source, setState) {
-      var currNest = nest;
-      while (!source.eol()) {
-        var ch = source.next();
-        if (ch == '{' && source.eat('-')) {
-          ++currNest;
-        }
-        else if (ch == '-' && source.eat('}')) {
-          --currNest;
-          if (currNest == 0) {
-            setState(normal);
-            return type;
-          }
-        }
-      }
-      setState(ncomment(type, currNest));
-      return type;
-    };
-  }
-
-  function stringLiteral(source, setState) {
-    while (!source.eol()) {
-      var ch = source.next();
-      if (ch == '"') {
-        setState(normal);
-        return "string";
-      }
-      if (ch == '\\') {
-        if (source.eol() || source.eat(whiteCharRE)) {
-          setState(stringGap);
-          return "string";
-        }
-        if (source.eat('&')) {
-        }
-        else {
-          source.next(); // should handle other escapes here
-        }
-      }
-    }
-    setState(normal);
-    return "error";
-  }
-
-  function stringGap(source, setState) {
-    if (source.eat('\\')) {
-      return switchState(source, setState, stringLiteral);
-    }
-    source.next();
-    setState(normal);
-    return "error";
-  }
-
-
-  var wellKnownWords = (function() {
-    var wkw = {};
-    function setType(t) {
-      return function () {
-        for (var i = 0; i < arguments.length; i++)
-          wkw[arguments[i]] = t;
-      };
-    }
-
-    setType("keyword")(
-      "case", "class", "data", "default", "deriving", "do", "else", "foreign",
-      "if", "import", "in", "infix", "infixl", "infixr", "instance", "let",
-      "module", "newtype", "of", "then", "type", "where", "_");
-
-    setType("keyword")(
-      "\.\.", ":", "::", "=", "\\", "\"", "<-", "->", "@", "~", "=>");
-
-    setType("builtin")(
-      "!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<=", "=<<",
-      "==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "**");
-
-    setType("builtin")(
-      "Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq",
-      "False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT",
-      "IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left",
-      "Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read",
-      "ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS",
-      "String", "True");
-
-    setType("builtin")(
-      "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf",
-      "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling",
-      "compare", "concat", "concatMap", "const", "cos", "cosh", "curry",
-      "cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either",
-      "elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo",
-      "enumFromTo", "error", "even", "exp", "exponent", "fail", "filter",
-      "flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap",
-      "foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger",
-      "fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents",
-      "getLine", "head", "id", "init", "interact", "ioError", "isDenormalized",
-      "isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last",
-      "lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map",
-      "mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound",
-      "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or",
-      "otherwise", "pi", "pred", "print", "product", "properFraction",
-      "putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile",
-      "readIO", "readList", "readLn", "readParen", "reads", "readsPrec",
-      "realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse",
-      "round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq",
-      "sequence", "sequence_", "show", "showChar", "showList", "showParen",
-      "showString", "shows", "showsPrec", "significand", "signum", "sin",
-      "sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum",
-      "tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger",
-      "toRational", "truncate", "uncurry", "undefined", "unlines", "until",
-      "unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip",
-      "zip3", "zipWith", "zipWith3");
-
-    var override = modeConfig.overrideKeywords;
-    if (override) for (var word in override) if (override.hasOwnProperty(word))
-      wkw[word] = override[word];
-
-    return wkw;
-  })();
-
-
-
-  return {
-    startState: function ()  { return { f: normal }; },
-    copyState:  function (s) { return { f: s.f }; },
-
-    token: function(stream, state) {
-      var t = state.f(stream, function(s) { state.f = s; });
-      var w = stream.current();
-      return wellKnownWords.hasOwnProperty(w) ? wellKnownWords[w] : t;
-    },
-
-    blockCommentStart: "{-",
-    blockCommentEnd: "-}",
-    lineComment: "--"
-  };
-
-});
-
-CodeMirror.defineMIME("text/x-haskell", "haskell");
-
-});
--- a/kallithea/public/codemirror/mode/haxe/haxe.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,518 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("haxe", function(config, parserConfig) {
-  var indentUnit = config.indentUnit;
-
-  // Tokenizer
-
-  var keywords = function(){
-    function kw(type) {return {type: type, style: "keyword"};}
-    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
-    var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"};
-  var type = kw("typedef");
-    return {
-      "if": A, "while": A, "else": B, "do": B, "try": B,
-      "return": C, "break": C, "continue": C, "new": C, "throw": C,
-      "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
-    "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"),
-      "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
-      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
-      "in": operator, "never": kw("property_access"), "trace":kw("trace"),
-    "class": type, "abstract":type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type,
-      "true": atom, "false": atom, "null": atom
-    };
-  }();
-
-  var isOperatorChar = /[+\-*&%=<>!?|]/;
-
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-
-  function nextUntilUnescaped(stream, end) {
-    var escaped = false, next;
-    while ((next = stream.next()) != null) {
-      if (next == end && !escaped)
-        return false;
-      escaped = !escaped && next == "\\";
-    }
-    return escaped;
-  }
-
-  // Used as scratch variables to communicate multiple values without
-  // consing up tons of objects.
-  var type, content;
-  function ret(tp, style, cont) {
-    type = tp; content = cont;
-    return style;
-  }
-
-  function haxeTokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"' || ch == "'")
-      return chain(stream, state, haxeTokenString(ch));
-    else if (/[\[\]{}\(\),;\:\.]/.test(ch))
-      return ret(ch);
-    else if (ch == "0" && stream.eat(/x/i)) {
-      stream.eatWhile(/[\da-f]/i);
-      return ret("number", "number");
-    }
-    else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
-      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
-      return ret("number", "number");
-    }
-    else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
-      nextUntilUnescaped(stream, "/");
-      stream.eatWhile(/[gimsu]/);
-      return ret("regexp", "string-2");
-    }
-    else if (ch == "/") {
-      if (stream.eat("*")) {
-        return chain(stream, state, haxeTokenComment);
-      }
-      else if (stream.eat("/")) {
-        stream.skipToEnd();
-        return ret("comment", "comment");
-      }
-      else {
-        stream.eatWhile(isOperatorChar);
-        return ret("operator", null, stream.current());
-      }
-    }
-    else if (ch == "#") {
-        stream.skipToEnd();
-        return ret("conditional", "meta");
-    }
-    else if (ch == "@") {
-      stream.eat(/:/);
-      stream.eatWhile(/[\w_]/);
-      return ret ("metadata", "meta");
-    }
-    else if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return ret("operator", null, stream.current());
-    }
-    else {
-    var word;
-    if(/[A-Z]/.test(ch))
-    {
-      stream.eatWhile(/[\w_<>]/);
-      word = stream.current();
-      return ret("type", "variable-3", word);
-    }
-    else
-    {
-        stream.eatWhile(/[\w_]/);
-        var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
-        return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
-                       ret("variable", "variable", word);
-    }
-    }
-  }
-
-  function haxeTokenString(quote) {
-    return function(stream, state) {
-      if (!nextUntilUnescaped(stream, quote))
-        state.tokenize = haxeTokenBase;
-      return ret("string", "string");
-    };
-  }
-
-  function haxeTokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = haxeTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return ret("comment", "comment");
-  }
-
-  // Parser
-
-  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
-
-  function HaxeLexical(indented, column, type, align, prev, info) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.prev = prev;
-    this.info = info;
-    if (align != null) this.align = align;
-  }
-
-  function inScope(state, varname) {
-    for (var v = state.localVars; v; v = v.next)
-      if (v.name == varname) return true;
-  }
-
-  function parseHaxe(state, style, type, content, stream) {
-    var cc = state.cc;
-    // Communicate our context to the combinators.
-    // (Less wasteful than consing up a hundred closures on every call.)
-    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
-
-    if (!state.lexical.hasOwnProperty("align"))
-      state.lexical.align = true;
-
-    while(true) {
-      var combinator = cc.length ? cc.pop() : statement;
-      if (combinator(type, content)) {
-        while(cc.length && cc[cc.length - 1].lex)
-          cc.pop()();
-        if (cx.marked) return cx.marked;
-        if (type == "variable" && inScope(state, content)) return "variable-2";
-    if (type == "variable" && imported(state, content)) return "variable-3";
-        return style;
-      }
-    }
-  }
-
-  function imported(state, typename)
-  {
-  if (/[a-z]/.test(typename.charAt(0)))
-    return false;
-  var len = state.importedtypes.length;
-  for (var i = 0; i<len; i++)
-    if(state.importedtypes[i]==typename) return true;
-  }
-
-
-  function registerimport(importname) {
-  var state = cx.state;
-  for (var t = state.importedtypes; t; t = t.next)
-    if(t.name == importname) return;
-  state.importedtypes = { name: importname, next: state.importedtypes };
-  }
-  // Combinator utils
-
-  var cx = {state: null, column: null, marked: null, cc: null};
-  function pass() {
-    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
-  }
-  function cont() {
-    pass.apply(null, arguments);
-    return true;
-  }
-  function register(varname) {
-    var state = cx.state;
-    if (state.context) {
-      cx.marked = "def";
-      for (var v = state.localVars; v; v = v.next)
-        if (v.name == varname) return;
-      state.localVars = {name: varname, next: state.localVars};
-    }
-  }
-
-  // Combinators
-
-  var defaultVars = {name: "this", next: null};
-  function pushcontext() {
-    if (!cx.state.context) cx.state.localVars = defaultVars;
-    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
-  }
-  function popcontext() {
-    cx.state.localVars = cx.state.context.vars;
-    cx.state.context = cx.state.context.prev;
-  }
-  function pushlex(type, info) {
-    var result = function() {
-      var state = cx.state;
-      state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info);
-    };
-    result.lex = true;
-    return result;
-  }
-  function poplex() {
-    var state = cx.state;
-    if (state.lexical.prev) {
-      if (state.lexical.type == ")")
-        state.indented = state.lexical.indented;
-      state.lexical = state.lexical.prev;
-    }
-  }
-  poplex.lex = true;
-
-  function expect(wanted) {
-    function f(type) {
-      if (type == wanted) return cont();
-      else if (wanted == ";") return pass();
-      else return cont(f);
-    };
-    return f;
-  }
-
-  function statement(type) {
-    if (type == "@") return cont(metadef);
-    if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
-    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
-    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
-    if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext);
-    if (type == ";") return cont();
-    if (type == "attribute") return cont(maybeattribute);
-    if (type == "function") return cont(functiondef);
-    if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
-                                      poplex, statement, poplex);
-    if (type == "variable") return cont(pushlex("stat"), maybelabel);
-    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
-                                         block, poplex, poplex);
-    if (type == "case") return cont(expression, expect(":"));
-    if (type == "default") return cont(expect(":"));
-    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
-                                        statement, poplex, popcontext);
-    if (type == "import") return cont(importdef, expect(";"));
-    if (type == "typedef") return cont(typedef);
-    return pass(pushlex("stat"), expression, expect(";"), poplex);
-  }
-  function expression(type) {
-    if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
-    if (type == "function") return cont(functiondef);
-    if (type == "keyword c") return cont(maybeexpression);
-    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
-    if (type == "operator") return cont(expression);
-    if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
-    if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
-    return cont();
-  }
-  function maybeexpression(type) {
-    if (type.match(/[;\}\)\],]/)) return pass();
-    return pass(expression);
-  }
-
-  function maybeoperator(type, value) {
-    if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
-    if (type == "operator" || type == ":") return cont(expression);
-    if (type == ";") return;
-    if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
-    if (type == ".") return cont(property, maybeoperator);
-    if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
-  }
-
-  function maybeattribute(type) {
-    if (type == "attribute") return cont(maybeattribute);
-    if (type == "function") return cont(functiondef);
-    if (type == "var") return cont(vardef1);
-  }
-
-  function metadef(type) {
-    if(type == ":") return cont(metadef);
-    if(type == "variable") return cont(metadef);
-    if(type == "(") return cont(pushlex(")"), commasep(metaargs, ")"), poplex, statement);
-  }
-  function metaargs(type) {
-    if(type == "variable") return cont();
-  }
-
-  function importdef (type, value) {
-  if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
-  else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef);
-  }
-
-  function typedef (type, value)
-  {
-  if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
-  else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); }
-  }
-
-  function maybelabel(type) {
-    if (type == ":") return cont(poplex, statement);
-    return pass(maybeoperator, expect(";"), poplex);
-  }
-  function property(type) {
-    if (type == "variable") {cx.marked = "property"; return cont();}
-  }
-  function objprop(type) {
-    if (type == "variable") cx.marked = "property";
-    if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
-  }
-  function commasep(what, end) {
-    function proceed(type) {
-      if (type == ",") return cont(what, proceed);
-      if (type == end) return cont();
-      return cont(expect(end));
-    }
-    return function(type) {
-      if (type == end) return cont();
-      else return pass(what, proceed);
-    };
-  }
-  function block(type) {
-    if (type == "}") return cont();
-    return pass(statement, block);
-  }
-  function vardef1(type, value) {
-    if (type == "variable"){register(value); return cont(typeuse, vardef2);}
-    return cont();
-  }
-  function vardef2(type, value) {
-    if (value == "=") return cont(expression, vardef2);
-    if (type == ",") return cont(vardef1);
-  }
-  function forspec1(type, value) {
-  if (type == "variable") {
-    register(value);
-  }
-  return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext);
-  }
-  function forin(_type, value) {
-    if (value == "in") return cont();
-  }
-  function functiondef(type, value) {
-    if (type == "variable") {register(value); return cont(functiondef);}
-    if (value == "new") return cont(functiondef);
-    if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext);
-  }
-  function typeuse(type) {
-    if(type == ":") return cont(typestring);
-  }
-  function typestring(type) {
-    if(type == "type") return cont();
-    if(type == "variable") return cont();
-    if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex);
-  }
-  function typeprop(type) {
-    if(type == "variable") return cont(typeuse);
-  }
-  function funarg(type, value) {
-    if (type == "variable") {register(value); return cont(typeuse);}
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-    var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
-      return {
-        tokenize: haxeTokenBase,
-        reAllowed: true,
-        kwAllowed: true,
-        cc: [],
-        lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false),
-        localVars: parserConfig.localVars,
-    importedtypes: defaulttypes,
-        context: parserConfig.localVars && {vars: parserConfig.localVars},
-        indented: 0
-      };
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (!state.lexical.hasOwnProperty("align"))
-          state.lexical.align = false;
-        state.indented = stream.indentation();
-      }
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      if (type == "comment") return style;
-      state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
-      state.kwAllowed = type != '.';
-      return parseHaxe(state, style, type, content, stream);
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != haxeTokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
-      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
-      var type = lexical.type, closing = firstChar == type;
-      if (type == "vardef") return lexical.indented + 4;
-      else if (type == "form" && firstChar == "{") return lexical.indented;
-      else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
-      else if (lexical.info == "switch" && !closing)
-        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
-      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
-      else return lexical.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricChars: "{}",
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: "//"
-  };
-});
-
-CodeMirror.defineMIME("text/x-haxe", "haxe");
-
-CodeMirror.defineMode("hxml", function () {
-
-  return {
-    startState: function () {
-      return {
-        define: false,
-        inString: false
-      };
-    },
-    token: function (stream, state) {
-      var ch = stream.peek();
-      var sol = stream.sol();
-
-      ///* comments */
-      if (ch == "#") {
-        stream.skipToEnd();
-        return "comment";
-      }
-      if (sol && ch == "-") {
-        var style = "variable-2";
-
-        stream.eat(/-/);
-
-        if (stream.peek() == "-") {
-          stream.eat(/-/);
-          style = "keyword a";
-        }
-
-        if (stream.peek() == "D") {
-          stream.eat(/[D]/);
-          style = "keyword c";
-          state.define = true;
-        }
-
-        stream.eatWhile(/[A-Z]/i);
-        return style;
-      }
-
-      var ch = stream.peek();
-
-      if (state.inString == false && ch == "'") {
-        state.inString = true;
-        ch = stream.next();
-      }
-
-      if (state.inString == true) {
-        if (stream.skipTo("'")) {
-
-        } else {
-          stream.skipToEnd();
-        }
-
-        if (stream.peek() == "'") {
-          stream.next();
-          state.inString = false;
-        }
-
-        return "string";
-      }
-
-      stream.next();
-      return null;
-    },
-    lineComment: "#"
-  };
-});
-
-CodeMirror.defineMIME("text/x-hxml", "hxml");
-
-});
--- a/kallithea/public/codemirror/mode/htmlembedded/htmlembedded.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
-
-  //config settings
-  var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i,
-      scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i;
-
-  //inner modes
-  var scriptingMode, htmlMixedMode;
-
-  //tokenizer when in html mode
-  function htmlDispatch(stream, state) {
-      if (stream.match(scriptStartRegex, false)) {
-          state.token=scriptingDispatch;
-          return scriptingMode.token(stream, state.scriptState);
-          }
-      else
-          return htmlMixedMode.token(stream, state.htmlState);
-    }
-
-  //tokenizer when in scripting mode
-  function scriptingDispatch(stream, state) {
-      if (stream.match(scriptEndRegex, false))  {
-          state.token=htmlDispatch;
-          return htmlMixedMode.token(stream, state.htmlState);
-         }
-      else
-          return scriptingMode.token(stream, state.scriptState);
-         }
-
-
-  return {
-    startState: function() {
-      scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec);
-      htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed");
-      return {
-          token :  parserConfig.startOpen ? scriptingDispatch : htmlDispatch,
-          htmlState : CodeMirror.startState(htmlMixedMode),
-          scriptState : CodeMirror.startState(scriptingMode)
-      };
-    },
-
-    token: function(stream, state) {
-      return state.token(stream, state);
-    },
-
-    indent: function(state, textAfter) {
-      if (state.token == htmlDispatch)
-        return htmlMixedMode.indent(state.htmlState, textAfter);
-      else if (scriptingMode.indent)
-        return scriptingMode.indent(state.scriptState, textAfter);
-    },
-
-    copyState: function(state) {
-      return {
-       token : state.token,
-       htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState),
-       scriptState : CodeMirror.copyState(scriptingMode, state.scriptState)
-      };
-    },
-
-    innerMode: function(state) {
-      if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode};
-      else return {state: state.htmlState, mode: htmlMixedMode};
-    }
-  };
-}, "htmlmixed");
-
-CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"});
-CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"});
-CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"});
-CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"});
-
-});
--- a/kallithea/public/codemirror/mode/htmlmixed/htmlmixed.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
-  var htmlMode = CodeMirror.getMode(config, {name: "xml",
-                                             htmlMode: true,
-                                             multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
-                                             multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
-  var cssMode = CodeMirror.getMode(config, "css");
-
-  var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
-  scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
-                    mode: CodeMirror.getMode(config, "javascript")});
-  if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
-    var conf = scriptTypesConf[i];
-    scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
-  }
-  scriptTypes.push({matches: /./,
-                    mode: CodeMirror.getMode(config, "text/plain")});
-
-  function html(stream, state) {
-    var tagName = state.htmlState.tagName;
-    if (tagName) tagName = tagName.toLowerCase();
-    var style = htmlMode.token(stream, state.htmlState);
-    if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
-      // Script block: mode to change to depends on type attribute
-      var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
-      scriptType = scriptType ? scriptType[1] : "";
-      if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
-      for (var i = 0; i < scriptTypes.length; ++i) {
-        var tp = scriptTypes[i];
-        if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
-          if (tp.mode) {
-            state.token = script;
-            state.localMode = tp.mode;
-            state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
-          }
-          break;
-        }
-      }
-    } else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
-      state.token = css;
-      state.localMode = cssMode;
-      state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
-    }
-    return style;
-  }
-  function maybeBackup(stream, pat, style) {
-    var cur = stream.current();
-    var close = cur.search(pat), m;
-    if (close > -1) stream.backUp(cur.length - close);
-    else if (m = cur.match(/<\/?$/)) {
-      stream.backUp(cur.length);
-      if (!stream.match(pat, false)) stream.match(cur);
-    }
-    return style;
-  }
-  function script(stream, state) {
-    if (stream.match(/^<\/\s*script\s*>/i, false)) {
-      state.token = html;
-      state.localState = state.localMode = null;
-      return html(stream, state);
-    }
-    return maybeBackup(stream, /<\/\s*script\s*>/,
-                       state.localMode.token(stream, state.localState));
-  }
-  function css(stream, state) {
-    if (stream.match(/^<\/\s*style\s*>/i, false)) {
-      state.token = html;
-      state.localState = state.localMode = null;
-      return html(stream, state);
-    }
-    return maybeBackup(stream, /<\/\s*style\s*>/,
-                       cssMode.token(stream, state.localState));
-  }
-
-  return {
-    startState: function() {
-      var state = htmlMode.startState();
-      return {token: html, localMode: null, localState: null, htmlState: state};
-    },
-
-    copyState: function(state) {
-      if (state.localState)
-        var local = CodeMirror.copyState(state.localMode, state.localState);
-      return {token: state.token, localMode: state.localMode, localState: local,
-              htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
-    },
-
-    token: function(stream, state) {
-      return state.token(stream, state);
-    },
-
-    indent: function(state, textAfter) {
-      if (!state.localMode || /^\s*<\//.test(textAfter))
-        return htmlMode.indent(state.htmlState, textAfter);
-      else if (state.localMode.indent)
-        return state.localMode.indent(state.localState, textAfter);
-      else
-        return CodeMirror.Pass;
-    },
-
-    innerMode: function(state) {
-      return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
-    }
-  };
-}, "xml", "javascript", "css");
-
-CodeMirror.defineMIME("text/html", "htmlmixed");
-
-});
--- a/kallithea/public/codemirror/mode/http/http.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("http", function() {
-  function failFirstLine(stream, state) {
-    stream.skipToEnd();
-    state.cur = header;
-    return "error";
-  }
-
-  function start(stream, state) {
-    if (stream.match(/^HTTP\/\d\.\d/)) {
-      state.cur = responseStatusCode;
-      return "keyword";
-    } else if (stream.match(/^[A-Z]+/) && /[ \t]/.test(stream.peek())) {
-      state.cur = requestPath;
-      return "keyword";
-    } else {
-      return failFirstLine(stream, state);
-    }
-  }
-
-  function responseStatusCode(stream, state) {
-    var code = stream.match(/^\d+/);
-    if (!code) return failFirstLine(stream, state);
-
-    state.cur = responseStatusText;
-    var status = Number(code[0]);
-    if (status >= 100 && status < 200) {
-      return "positive informational";
-    } else if (status >= 200 && status < 300) {
-      return "positive success";
-    } else if (status >= 300 && status < 400) {
-      return "positive redirect";
-    } else if (status >= 400 && status < 500) {
-      return "negative client-error";
-    } else if (status >= 500 && status < 600) {
-      return "negative server-error";
-    } else {
-      return "error";
-    }
-  }
-
-  function responseStatusText(stream, state) {
-    stream.skipToEnd();
-    state.cur = header;
-    return null;
-  }
-
-  function requestPath(stream, state) {
-    stream.eatWhile(/\S/);
-    state.cur = requestProtocol;
-    return "string-2";
-  }
-
-  function requestProtocol(stream, state) {
-    if (stream.match(/^HTTP\/\d\.\d$/)) {
-      state.cur = header;
-      return "keyword";
-    } else {
-      return failFirstLine(stream, state);
-    }
-  }
-
-  function header(stream) {
-    if (stream.sol() && !stream.eat(/[ \t]/)) {
-      if (stream.match(/^.*?:/)) {
-        return "atom";
-      } else {
-        stream.skipToEnd();
-        return "error";
-      }
-    } else {
-      stream.skipToEnd();
-      return "string";
-    }
-  }
-
-  function body(stream) {
-    stream.skipToEnd();
-    return null;
-  }
-
-  return {
-    token: function(stream, state) {
-      var cur = state.cur;
-      if (cur != header && cur != body && stream.eatSpace()) return null;
-      return cur(stream, state);
-    },
-
-    blankLine: function(state) {
-      state.cur = body;
-    },
-
-    startState: function() {
-      return {cur: start};
-    }
-  };
-});
-
-CodeMirror.defineMIME("message/http", "http");
-
-});
--- a/kallithea/public/codemirror/mode/jade/jade.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,590 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../javascript/javascript"), require("../css/css"), require("../htmlmixed/htmlmixed"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../javascript/javascript", "../css/css", "../htmlmixed/htmlmixed"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('jade', function (config) {
-  // token types
-  var KEYWORD = 'keyword';
-  var DOCTYPE = 'meta';
-  var ID = 'builtin';
-  var CLASS = 'qualifier';
-
-  var ATTRS_NEST = {
-    '{': '}',
-    '(': ')',
-    '[': ']'
-  };
-
-  var jsMode = CodeMirror.getMode(config, 'javascript');
-
-  function State() {
-    this.javaScriptLine = false;
-    this.javaScriptLineExcludesColon = false;
-
-    this.javaScriptArguments = false;
-    this.javaScriptArgumentsDepth = 0;
-
-    this.isInterpolating = false;
-    this.interpolationNesting = 0;
-
-    this.jsState = jsMode.startState();
-
-    this.restOfLine = '';
-
-    this.isIncludeFiltered = false;
-    this.isEach = false;
-
-    this.lastTag = '';
-    this.scriptType = '';
-
-    // Attributes Mode
-    this.isAttrs = false;
-    this.attrsNest = [];
-    this.inAttributeName = true;
-    this.attributeIsType = false;
-    this.attrValue = '';
-
-    // Indented Mode
-    this.indentOf = Infinity;
-    this.indentToken = '';
-
-    this.innerMode = null;
-    this.innerState = null;
-
-    this.innerModeForLine = false;
-  }
-  /**
-   * Safely copy a state
-   *
-   * @return {State}
-   */
-  State.prototype.copy = function () {
-    var res = new State();
-    res.javaScriptLine = this.javaScriptLine;
-    res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon;
-    res.javaScriptArguments = this.javaScriptArguments;
-    res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth;
-    res.isInterpolating = this.isInterpolating;
-    res.interpolationNesting = this.intpolationNesting;
-
-    res.jsState = CodeMirror.copyState(jsMode, this.jsState);
-
-    res.innerMode = this.innerMode;
-    if (this.innerMode && this.innerState) {
-      res.innerState = CodeMirror.copyState(this.innerMode, this.innerState);
-    }
-
-    res.restOfLine = this.restOfLine;
-
-    res.isIncludeFiltered = this.isIncludeFiltered;
-    res.isEach = this.isEach;
-    res.lastTag = this.lastTag;
-    res.scriptType = this.scriptType;
-    res.isAttrs = this.isAttrs;
-    res.attrsNest = this.attrsNest.slice();
-    res.inAttributeName = this.inAttributeName;
-    res.attributeIsType = this.attributeIsType;
-    res.attrValue = this.attrValue;
-    res.indentOf = this.indentOf;
-    res.indentToken = this.indentToken;
-
-    res.innerModeForLine = this.innerModeForLine;
-
-    return res;
-  };
-
-  function javaScript(stream, state) {
-    if (stream.sol()) {
-      // if javaScriptLine was set at end of line, ignore it
-      state.javaScriptLine = false;
-      state.javaScriptLineExcludesColon = false;
-    }
-    if (state.javaScriptLine) {
-      if (state.javaScriptLineExcludesColon && stream.peek() === ':') {
-        state.javaScriptLine = false;
-        state.javaScriptLineExcludesColon = false;
-        return;
-      }
-      var tok = jsMode.token(stream, state.jsState);
-      if (stream.eol()) state.javaScriptLine = false;
-      return tok || true;
-    }
-  }
-  function javaScriptArguments(stream, state) {
-    if (state.javaScriptArguments) {
-      if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') {
-        state.javaScriptArguments = false;
-        return;
-      }
-      if (stream.peek() === '(') {
-        state.javaScriptArgumentsDepth++;
-      } else if (stream.peek() === ')') {
-        state.javaScriptArgumentsDepth--;
-      }
-      if (state.javaScriptArgumentsDepth === 0) {
-        state.javaScriptArguments = false;
-        return;
-      }
-
-      var tok = jsMode.token(stream, state.jsState);
-      return tok || true;
-    }
-  }
-
-  function yieldStatement(stream) {
-    if (stream.match(/^yield\b/)) {
-        return 'keyword';
-    }
-  }
-
-  function doctype(stream) {
-    if (stream.match(/^(?:doctype) *([^\n]+)?/)) {
-        return DOCTYPE;
-    }
-  }
-
-  function interpolation(stream, state) {
-    if (stream.match('#{')) {
-      state.isInterpolating = true;
-      state.interpolationNesting = 0;
-      return 'punctuation';
-    }
-  }
-
-  function interpolationContinued(stream, state) {
-    if (state.isInterpolating) {
-      if (stream.peek() === '}') {
-        state.interpolationNesting--;
-        if (state.interpolationNesting < 0) {
-          stream.next();
-          state.isInterpolating = false;
-          return 'puncutation';
-        }
-      } else if (stream.peek() === '{') {
-        state.interpolationNesting++;
-      }
-      return jsMode.token(stream, state.jsState) || true;
-    }
-  }
-
-  function caseStatement(stream, state) {
-    if (stream.match(/^case\b/)) {
-      state.javaScriptLine = true;
-      return KEYWORD;
-    }
-  }
-
-  function when(stream, state) {
-    if (stream.match(/^when\b/)) {
-      state.javaScriptLine = true;
-      state.javaScriptLineExcludesColon = true;
-      return KEYWORD;
-    }
-  }
-
-  function defaultStatement(stream) {
-    if (stream.match(/^default\b/)) {
-      return KEYWORD;
-    }
-  }
-
-  function extendsStatement(stream, state) {
-    if (stream.match(/^extends?\b/)) {
-      state.restOfLine = 'string';
-      return KEYWORD;
-    }
-  }
-
-  function append(stream, state) {
-    if (stream.match(/^append\b/)) {
-      state.restOfLine = 'variable';
-      return KEYWORD;
-    }
-  }
-  function prepend(stream, state) {
-    if (stream.match(/^prepend\b/)) {
-      state.restOfLine = 'variable';
-      return KEYWORD;
-    }
-  }
-  function block(stream, state) {
-    if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) {
-      state.restOfLine = 'variable';
-      return KEYWORD;
-    }
-  }
-
-  function include(stream, state) {
-    if (stream.match(/^include\b/)) {
-      state.restOfLine = 'string';
-      return KEYWORD;
-    }
-  }
-
-  function includeFiltered(stream, state) {
-    if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) {
-      state.isIncludeFiltered = true;
-      return KEYWORD;
-    }
-  }
-
-  function includeFilteredContinued(stream, state) {
-    if (state.isIncludeFiltered) {
-      var tok = filter(stream, state);
-      state.isIncludeFiltered = false;
-      state.restOfLine = 'string';
-      return tok;
-    }
-  }
-
-  function mixin(stream, state) {
-    if (stream.match(/^mixin\b/)) {
-      state.javaScriptLine = true;
-      return KEYWORD;
-    }
-  }
-
-  function call(stream, state) {
-    if (stream.match(/^\+([-\w]+)/)) {
-      if (!stream.match(/^\( *[-\w]+ *=/, false)) {
-        state.javaScriptArguments = true;
-        state.javaScriptArgumentsDepth = 0;
-      }
-      return 'variable';
-    }
-    if (stream.match(/^\+#{/, false)) {
-      stream.next();
-      state.mixinCallAfter = true;
-      return interpolation(stream, state);
-    }
-  }
-  function callArguments(stream, state) {
-    if (state.mixinCallAfter) {
-      state.mixinCallAfter = false;
-      if (!stream.match(/^\( *[-\w]+ *=/, false)) {
-        state.javaScriptArguments = true;
-        state.javaScriptArgumentsDepth = 0;
-      }
-      return true;
-    }
-  }
-
-  function conditional(stream, state) {
-    if (stream.match(/^(if|unless|else if|else)\b/)) {
-      state.javaScriptLine = true;
-      return KEYWORD;
-    }
-  }
-
-  function each(stream, state) {
-    if (stream.match(/^(- *)?(each|for)\b/)) {
-      state.isEach = true;
-      return KEYWORD;
-    }
-  }
-  function eachContinued(stream, state) {
-    if (state.isEach) {
-      if (stream.match(/^ in\b/)) {
-        state.javaScriptLine = true;
-        state.isEach = false;
-        return KEYWORD;
-      } else if (stream.sol() || stream.eol()) {
-        state.isEach = false;
-      } else if (stream.next()) {
-        while (!stream.match(/^ in\b/, false) && stream.next());
-        return 'variable';
-      }
-    }
-  }
-
-  function whileStatement(stream, state) {
-    if (stream.match(/^while\b/)) {
-      state.javaScriptLine = true;
-      return KEYWORD;
-    }
-  }
-
-  function tag(stream, state) {
-    var captures;
-    if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) {
-      state.lastTag = captures[1].toLowerCase();
-      if (state.lastTag === 'script') {
-        state.scriptType = 'application/javascript';
-      }
-      return 'tag';
-    }
-  }
-
-  function filter(stream, state) {
-    if (stream.match(/^:([\w\-]+)/)) {
-      var innerMode;
-      if (config && config.innerModes) {
-        innerMode = config.innerModes(stream.current().substring(1));
-      }
-      if (!innerMode) {
-        innerMode = stream.current().substring(1);
-      }
-      if (typeof innerMode === 'string') {
-        innerMode = CodeMirror.getMode(config, innerMode);
-      }
-      setInnerMode(stream, state, innerMode);
-      return 'atom';
-    }
-  }
-
-  function code(stream, state) {
-    if (stream.match(/^(!?=|-)/)) {
-      state.javaScriptLine = true;
-      return 'punctuation';
-    }
-  }
-
-  function id(stream) {
-    if (stream.match(/^#([\w-]+)/)) {
-      return ID;
-    }
-  }
-
-  function className(stream) {
-    if (stream.match(/^\.([\w-]+)/)) {
-      return CLASS;
-    }
-  }
-
-  function attrs(stream, state) {
-    if (stream.peek() == '(') {
-      stream.next();
-      state.isAttrs = true;
-      state.attrsNest = [];
-      state.inAttributeName = true;
-      state.attrValue = '';
-      state.attributeIsType = false;
-      return 'punctuation';
-    }
-  }
-
-  function attrsContinued(stream, state) {
-    if (state.isAttrs) {
-      if (ATTRS_NEST[stream.peek()]) {
-        state.attrsNest.push(ATTRS_NEST[stream.peek()]);
-      }
-      if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) {
-        state.attrsNest.pop();
-      } else  if (stream.eat(')')) {
-        state.isAttrs = false;
-        return 'punctuation';
-      }
-      if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) {
-        if (stream.peek() === '=' || stream.peek() === '!') {
-          state.inAttributeName = false;
-          state.jsState = jsMode.startState();
-          if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') {
-            state.attributeIsType = true;
-          } else {
-            state.attributeIsType = false;
-          }
-        }
-        return 'attribute';
-      }
-
-      var tok = jsMode.token(stream, state.jsState);
-      if (state.attributeIsType && tok === 'string') {
-        state.scriptType = stream.current().toString();
-      }
-      if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) {
-        try {
-          Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, ''));
-          state.inAttributeName = true;
-          state.attrValue = '';
-          stream.backUp(stream.current().length);
-          return attrsContinued(stream, state);
-        } catch (ex) {
-          //not the end of an attribute
-        }
-      }
-      state.attrValue += stream.current();
-      return tok || true;
-    }
-  }
-
-  function attributesBlock(stream, state) {
-    if (stream.match(/^&attributes\b/)) {
-      state.javaScriptArguments = true;
-      state.javaScriptArgumentsDepth = 0;
-      return 'keyword';
-    }
-  }
-
-  function indent(stream) {
-    if (stream.sol() && stream.eatSpace()) {
-      return 'indent';
-    }
-  }
-
-  function comment(stream, state) {
-    if (stream.match(/^ *\/\/(-)?([^\n]*)/)) {
-      state.indentOf = stream.indentation();
-      state.indentToken = 'comment';
-      return 'comment';
-    }
-  }
-
-  function colon(stream) {
-    if (stream.match(/^: */)) {
-      return 'colon';
-    }
-  }
-
-  function text(stream, state) {
-    if (stream.match(/^(?:\| ?| )([^\n]+)/)) {
-      return 'string';
-    }
-    if (stream.match(/^(<[^\n]*)/, false)) {
-      // html string
-      setInnerMode(stream, state, 'htmlmixed');
-      state.innerModeForLine = true;
-      return innerMode(stream, state, true);
-    }
-  }
-
-  function dot(stream, state) {
-    if (stream.eat('.')) {
-      var innerMode = null;
-      if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) {
-        innerMode = state.scriptType.toLowerCase().replace(/"|'/g, '');
-      } else if (state.lastTag === 'style') {
-        innerMode = 'css';
-      }
-      setInnerMode(stream, state, innerMode);
-      return 'dot';
-    }
-  }
-
-  function fail(stream) {
-    stream.next();
-    return null;
-  }
-
-
-  function setInnerMode(stream, state, mode) {
-    mode = CodeMirror.mimeModes[mode] || mode;
-    mode = config.innerModes ? config.innerModes(mode) || mode : mode;
-    mode = CodeMirror.mimeModes[mode] || mode;
-    mode = CodeMirror.getMode(config, mode);
-    state.indentOf = stream.indentation();
-
-    if (mode && mode.name !== 'null') {
-      state.innerMode = mode;
-    } else {
-      state.indentToken = 'string';
-    }
-  }
-  function innerMode(stream, state, force) {
-    if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) {
-      if (state.innerMode) {
-        if (!state.innerState) {
-          state.innerState = state.innerMode.startState ? state.innerMode.startState(stream.indentation()) : {};
-        }
-        return stream.hideFirstChars(state.indentOf + 2, function () {
-          return state.innerMode.token(stream, state.innerState) || true;
-        });
-      } else {
-        stream.skipToEnd();
-        return state.indentToken;
-      }
-    } else if (stream.sol()) {
-      state.indentOf = Infinity;
-      state.indentToken = null;
-      state.innerMode = null;
-      state.innerState = null;
-    }
-  }
-  function restOfLine(stream, state) {
-    if (stream.sol()) {
-      // if restOfLine was set at end of line, ignore it
-      state.restOfLine = '';
-    }
-    if (state.restOfLine) {
-      stream.skipToEnd();
-      var tok = state.restOfLine;
-      state.restOfLine = '';
-      return tok;
-    }
-  }
-
-
-  function startState() {
-    return new State();
-  }
-  function copyState(state) {
-    return state.copy();
-  }
-  /**
-   * Get the next token in the stream
-   *
-   * @param {Stream} stream
-   * @param {State} state
-   */
-  function nextToken(stream, state) {
-    var tok = innerMode(stream, state)
-      || restOfLine(stream, state)
-      || interpolationContinued(stream, state)
-      || includeFilteredContinued(stream, state)
-      || eachContinued(stream, state)
-      || attrsContinued(stream, state)
-      || javaScript(stream, state)
-      || javaScriptArguments(stream, state)
-      || callArguments(stream, state)
-
-      || yieldStatement(stream, state)
-      || doctype(stream, state)
-      || interpolation(stream, state)
-      || caseStatement(stream, state)
-      || when(stream, state)
-      || defaultStatement(stream, state)
-      || extendsStatement(stream, state)
-      || append(stream, state)
-      || prepend(stream, state)
-      || block(stream, state)
-      || include(stream, state)
-      || includeFiltered(stream, state)
-      || mixin(stream, state)
-      || call(stream, state)
-      || conditional(stream, state)
-      || each(stream, state)
-      || whileStatement(stream, state)
-      || tag(stream, state)
-      || filter(stream, state)
-      || code(stream, state)
-      || id(stream, state)
-      || className(stream, state)
-      || attrs(stream, state)
-      || attributesBlock(stream, state)
-      || indent(stream, state)
-      || text(stream, state)
-      || comment(stream, state)
-      || colon(stream, state)
-      || dot(stream, state)
-      || fail(stream, state);
-
-    return tok === true ? null : tok;
-  }
-  return {
-    startState: startState,
-    copyState: copyState,
-    token: nextToken
-  };
-});
-
-CodeMirror.defineMIME('text/x-jade', 'jade');
-
-});
--- a/kallithea/public/codemirror/mode/javascript/javascript.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,684 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// TODO actually recognize syntax of TypeScript constructs
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("javascript", function(config, parserConfig) {
-  var indentUnit = config.indentUnit;
-  var statementIndent = parserConfig.statementIndent;
-  var jsonldMode = parserConfig.jsonld;
-  var jsonMode = parserConfig.json || jsonldMode;
-  var isTS = parserConfig.typescript;
-  var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
-
-  // Tokenizer
-
-  var keywords = function(){
-    function kw(type) {return {type: type, style: "keyword"};}
-    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
-    var operator = kw("operator"), atom = {type: "atom", style: "atom"};
-
-    var jsKeywords = {
-      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
-      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
-      "var": kw("var"), "const": kw("var"), "let": kw("var"),
-      "function": kw("function"), "catch": kw("catch"),
-      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
-      "in": operator, "typeof": operator, "instanceof": operator,
-      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
-      "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
-      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
-    };
-
-    // Extend the 'normal' keywords with the TypeScript language extensions
-    if (isTS) {
-      var type = {type: "variable", style: "variable-3"};
-      var tsKeywords = {
-        // object-like things
-        "interface": kw("interface"),
-        "extends": kw("extends"),
-        "constructor": kw("constructor"),
-
-        // scope modifiers
-        "public": kw("public"),
-        "private": kw("private"),
-        "protected": kw("protected"),
-        "static": kw("static"),
-
-        // types
-        "string": type, "number": type, "bool": type, "any": type
-      };
-
-      for (var attr in tsKeywords) {
-        jsKeywords[attr] = tsKeywords[attr];
-      }
-    }
-
-    return jsKeywords;
-  }();
-
-  var isOperatorChar = /[+\-*&%=<>!?|~^]/;
-  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
-
-  function readRegexp(stream) {
-    var escaped = false, next, inSet = false;
-    while ((next = stream.next()) != null) {
-      if (!escaped) {
-        if (next == "/" && !inSet) return;
-        if (next == "[") inSet = true;
-        else if (inSet && next == "]") inSet = false;
-      }
-      escaped = !escaped && next == "\\";
-    }
-  }
-
-  // Used as scratch variables to communicate multiple values without
-  // consing up tons of objects.
-  var type, content;
-  function ret(tp, style, cont) {
-    type = tp; content = cont;
-    return style;
-  }
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"' || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
-      return ret("number", "number");
-    } else if (ch == "." && stream.match("..")) {
-      return ret("spread", "meta");
-    } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      return ret(ch);
-    } else if (ch == "=" && stream.eat(">")) {
-      return ret("=>", "operator");
-    } else if (ch == "0" && stream.eat(/x/i)) {
-      stream.eatWhile(/[\da-f]/i);
-      return ret("number", "number");
-    } else if (/\d/.test(ch)) {
-      stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
-      return ret("number", "number");
-    } else if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      } else if (stream.eat("/")) {
-        stream.skipToEnd();
-        return ret("comment", "comment");
-      } else if (state.lastType == "operator" || state.lastType == "keyword c" ||
-               state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
-        readRegexp(stream);
-        stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
-        return ret("regexp", "string-2");
-      } else {
-        stream.eatWhile(isOperatorChar);
-        return ret("operator", "operator", stream.current());
-      }
-    } else if (ch == "`") {
-      state.tokenize = tokenQuasi;
-      return tokenQuasi(stream, state);
-    } else if (ch == "#") {
-      stream.skipToEnd();
-      return ret("error", "error");
-    } else if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return ret("operator", "operator", stream.current());
-    } else if (wordRE.test(ch)) {
-      stream.eatWhile(wordRE);
-      var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
-      return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
-                     ret("variable", "variable", word);
-    }
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next;
-      if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
-        state.tokenize = tokenBase;
-        return ret("jsonld-keyword", "meta");
-      }
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) break;
-        escaped = !escaped && next == "\\";
-      }
-      if (!escaped) state.tokenize = tokenBase;
-      return ret("string", "string");
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenQuasi(stream, state) {
-    var escaped = false, next;
-    while ((next = stream.next()) != null) {
-      if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      escaped = !escaped && next == "\\";
-    }
-    return ret("quasi", "string-2", stream.current());
-  }
-
-  var brackets = "([{}])";
-  // This is a crude lookahead trick to try and notice that we're
-  // parsing the argument patterns for a fat-arrow function before we
-  // actually hit the arrow token. It only works if the arrow is on
-  // the same line as the arguments and there's no strange noise
-  // (comments) in between. Fallback is to only notice when we hit the
-  // arrow, and not declare the arguments as locals for the arrow
-  // body.
-  function findFatArrow(stream, state) {
-    if (state.fatArrowAt) state.fatArrowAt = null;
-    var arrow = stream.string.indexOf("=>", stream.start);
-    if (arrow < 0) return;
-
-    var depth = 0, sawSomething = false;
-    for (var pos = arrow - 1; pos >= 0; --pos) {
-      var ch = stream.string.charAt(pos);
-      var bracket = brackets.indexOf(ch);
-      if (bracket >= 0 && bracket < 3) {
-        if (!depth) { ++pos; break; }
-        if (--depth == 0) break;
-      } else if (bracket >= 3 && bracket < 6) {
-        ++depth;
-      } else if (wordRE.test(ch)) {
-        sawSomething = true;
-      } else if (sawSomething && !depth) {
-        ++pos;
-        break;
-      }
-    }
-    if (sawSomething && !depth) state.fatArrowAt = pos;
-  }
-
-  // Parser
-
-  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
-
-  function JSLexical(indented, column, type, align, prev, info) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.prev = prev;
-    this.info = info;
-    if (align != null) this.align = align;
-  }
-
-  function inScope(state, varname) {
-    for (var v = state.localVars; v; v = v.next)
-      if (v.name == varname) return true;
-    for (var cx = state.context; cx; cx = cx.prev) {
-      for (var v = cx.vars; v; v = v.next)
-        if (v.name == varname) return true;
-    }
-  }
-
-  function parseJS(state, style, type, content, stream) {
-    var cc = state.cc;
-    // Communicate our context to the combinators.
-    // (Less wasteful than consing up a hundred closures on every call.)
-    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
-
-    if (!state.lexical.hasOwnProperty("align"))
-      state.lexical.align = true;
-
-    while(true) {
-      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
-      if (combinator(type, content)) {
-        while(cc.length && cc[cc.length - 1].lex)
-          cc.pop()();
-        if (cx.marked) return cx.marked;
-        if (type == "variable" && inScope(state, content)) return "variable-2";
-        return style;
-      }
-    }
-  }
-
-  // Combinator utils
-
-  var cx = {state: null, column: null, marked: null, cc: null};
-  function pass() {
-    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
-  }
-  function cont() {
-    pass.apply(null, arguments);
-    return true;
-  }
-  function register(varname) {
-    function inList(list) {
-      for (var v = list; v; v = v.next)
-        if (v.name == varname) return true;
-      return false;
-    }
-    var state = cx.state;
-    if (state.context) {
-      cx.marked = "def";
-      if (inList(state.localVars)) return;
-      state.localVars = {name: varname, next: state.localVars};
-    } else {
-      if (inList(state.globalVars)) return;
-      if (parserConfig.globalVars)
-        state.globalVars = {name: varname, next: state.globalVars};
-    }
-  }
-
-  // Combinators
-
-  var defaultVars = {name: "this", next: {name: "arguments"}};
-  function pushcontext() {
-    cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
-    cx.state.localVars = defaultVars;
-  }
-  function popcontext() {
-    cx.state.localVars = cx.state.context.vars;
-    cx.state.context = cx.state.context.prev;
-  }
-  function pushlex(type, info) {
-    var result = function() {
-      var state = cx.state, indent = state.indented;
-      if (state.lexical.type == "stat") indent = state.lexical.indented;
-      else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
-        indent = outer.indented;
-      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
-    };
-    result.lex = true;
-    return result;
-  }
-  function poplex() {
-    var state = cx.state;
-    if (state.lexical.prev) {
-      if (state.lexical.type == ")")
-        state.indented = state.lexical.indented;
-      state.lexical = state.lexical.prev;
-    }
-  }
-  poplex.lex = true;
-
-  function expect(wanted) {
-    function exp(type) {
-      if (type == wanted) return cont();
-      else if (wanted == ";") return pass();
-      else return cont(exp);
-    };
-    return exp;
-  }
-
-  function statement(type, value) {
-    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
-    if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
-    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
-    if (type == "{") return cont(pushlex("}"), block, poplex);
-    if (type == ";") return cont();
-    if (type == "if") {
-      if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
-        cx.state.cc.pop()();
-      return cont(pushlex("form"), expression, statement, poplex, maybeelse);
-    }
-    if (type == "function") return cont(functiondef);
-    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
-    if (type == "variable") return cont(pushlex("stat"), maybelabel);
-    if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
-                                      block, poplex, poplex);
-    if (type == "case") return cont(expression, expect(":"));
-    if (type == "default") return cont(expect(":"));
-    if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
-                                     statement, poplex, popcontext);
-    if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
-    if (type == "class") return cont(pushlex("form"), className, poplex);
-    if (type == "export") return cont(pushlex("form"), afterExport, poplex);
-    if (type == "import") return cont(pushlex("form"), afterImport, poplex);
-    return pass(pushlex("stat"), expression, expect(";"), poplex);
-  }
-  function expression(type) {
-    return expressionInner(type, false);
-  }
-  function expressionNoComma(type) {
-    return expressionInner(type, true);
-  }
-  function expressionInner(type, noComma) {
-    if (cx.state.fatArrowAt == cx.stream.start) {
-      var body = noComma ? arrowBodyNoComma : arrowBody;
-      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
-      else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
-    }
-
-    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
-    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
-    if (type == "function") return cont(functiondef, maybeop);
-    if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
-    if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
-    if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
-    if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
-    if (type == "{") return contCommasep(objprop, "}", null, maybeop);
-    if (type == "quasi") { return pass(quasi, maybeop); }
-    return cont();
-  }
-  function maybeexpression(type) {
-    if (type.match(/[;\}\)\],]/)) return pass();
-    return pass(expression);
-  }
-  function maybeexpressionNoComma(type) {
-    if (type.match(/[;\}\)\],]/)) return pass();
-    return pass(expressionNoComma);
-  }
-
-  function maybeoperatorComma(type, value) {
-    if (type == ",") return cont(expression);
-    return maybeoperatorNoComma(type, value, false);
-  }
-  function maybeoperatorNoComma(type, value, noComma) {
-    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
-    var expr = noComma == false ? expression : expressionNoComma;
-    if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
-    if (type == "operator") {
-      if (/\+\+|--/.test(value)) return cont(me);
-      if (value == "?") return cont(expression, expect(":"), expr);
-      return cont(expr);
-    }
-    if (type == "quasi") { return pass(quasi, me); }
-    if (type == ";") return;
-    if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
-    if (type == ".") return cont(property, me);
-    if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
-  }
-  function quasi(type, value) {
-    if (type != "quasi") return pass();
-    if (value.slice(value.length - 2) != "${") return cont(quasi);
-    return cont(expression, continueQuasi);
-  }
-  function continueQuasi(type) {
-    if (type == "}") {
-      cx.marked = "string-2";
-      cx.state.tokenize = tokenQuasi;
-      return cont(quasi);
-    }
-  }
-  function arrowBody(type) {
-    findFatArrow(cx.stream, cx.state);
-    return pass(type == "{" ? statement : expression);
-  }
-  function arrowBodyNoComma(type) {
-    findFatArrow(cx.stream, cx.state);
-    return pass(type == "{" ? statement : expressionNoComma);
-  }
-  function maybelabel(type) {
-    if (type == ":") return cont(poplex, statement);
-    return pass(maybeoperatorComma, expect(";"), poplex);
-  }
-  function property(type) {
-    if (type == "variable") {cx.marked = "property"; return cont();}
-  }
-  function objprop(type, value) {
-    if (type == "variable" || cx.style == "keyword") {
-      cx.marked = "property";
-      if (value == "get" || value == "set") return cont(getterSetter);
-      return cont(afterprop);
-    } else if (type == "number" || type == "string") {
-      cx.marked = jsonldMode ? "property" : (cx.style + " property");
-      return cont(afterprop);
-    } else if (type == "jsonld-keyword") {
-      return cont(afterprop);
-    } else if (type == "[") {
-      return cont(expression, expect("]"), afterprop);
-    }
-  }
-  function getterSetter(type) {
-    if (type != "variable") return pass(afterprop);
-    cx.marked = "property";
-    return cont(functiondef);
-  }
-  function afterprop(type) {
-    if (type == ":") return cont(expressionNoComma);
-    if (type == "(") return pass(functiondef);
-  }
-  function commasep(what, end) {
-    function proceed(type) {
-      if (type == ",") {
-        var lex = cx.state.lexical;
-        if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
-        return cont(what, proceed);
-      }
-      if (type == end) return cont();
-      return cont(expect(end));
-    }
-    return function(type) {
-      if (type == end) return cont();
-      return pass(what, proceed);
-    };
-  }
-  function contCommasep(what, end, info) {
-    for (var i = 3; i < arguments.length; i++)
-      cx.cc.push(arguments[i]);
-    return cont(pushlex(end, info), commasep(what, end), poplex);
-  }
-  function block(type) {
-    if (type == "}") return cont();
-    return pass(statement, block);
-  }
-  function maybetype(type) {
-    if (isTS && type == ":") return cont(typedef);
-  }
-  function typedef(type) {
-    if (type == "variable"){cx.marked = "variable-3"; return cont();}
-  }
-  function vardef() {
-    return pass(pattern, maybetype, maybeAssign, vardefCont);
-  }
-  function pattern(type, value) {
-    if (type == "variable") { register(value); return cont(); }
-    if (type == "[") return contCommasep(pattern, "]");
-    if (type == "{") return contCommasep(proppattern, "}");
-  }
-  function proppattern(type, value) {
-    if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
-      register(value);
-      return cont(maybeAssign);
-    }
-    if (type == "variable") cx.marked = "property";
-    return cont(expect(":"), pattern, maybeAssign);
-  }
-  function maybeAssign(_type, value) {
-    if (value == "=") return cont(expressionNoComma);
-  }
-  function vardefCont(type) {
-    if (type == ",") return cont(vardef);
-  }
-  function maybeelse(type, value) {
-    if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
-  }
-  function forspec(type) {
-    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
-  }
-  function forspec1(type) {
-    if (type == "var") return cont(vardef, expect(";"), forspec2);
-    if (type == ";") return cont(forspec2);
-    if (type == "variable") return cont(formaybeinof);
-    return pass(expression, expect(";"), forspec2);
-  }
-  function formaybeinof(_type, value) {
-    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
-    return cont(maybeoperatorComma, forspec2);
-  }
-  function forspec2(type, value) {
-    if (type == ";") return cont(forspec3);
-    if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
-    return pass(expression, expect(";"), forspec3);
-  }
-  function forspec3(type) {
-    if (type != ")") cont(expression);
-  }
-  function functiondef(type, value) {
-    if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
-    if (type == "variable") {register(value); return cont(functiondef);}
-    if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
-  }
-  function funarg(type) {
-    if (type == "spread") return cont(funarg);
-    return pass(pattern, maybetype);
-  }
-  function className(type, value) {
-    if (type == "variable") {register(value); return cont(classNameAfter);}
-  }
-  function classNameAfter(type, value) {
-    if (value == "extends") return cont(expression, classNameAfter);
-    if (type == "{") return cont(pushlex("}"), classBody, poplex);
-  }
-  function classBody(type, value) {
-    if (type == "variable" || cx.style == "keyword") {
-      cx.marked = "property";
-      if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
-      return cont(functiondef, classBody);
-    }
-    if (value == "*") {
-      cx.marked = "keyword";
-      return cont(classBody);
-    }
-    if (type == ";") return cont(classBody);
-    if (type == "}") return cont();
-  }
-  function classGetterSetter(type) {
-    if (type != "variable") return pass();
-    cx.marked = "property";
-    return cont();
-  }
-  function afterModule(type, value) {
-    if (type == "string") return cont(statement);
-    if (type == "variable") { register(value); return cont(maybeFrom); }
-  }
-  function afterExport(_type, value) {
-    if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
-    if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
-    return pass(statement);
-  }
-  function afterImport(type) {
-    if (type == "string") return cont();
-    return pass(importSpec, maybeFrom);
-  }
-  function importSpec(type, value) {
-    if (type == "{") return contCommasep(importSpec, "}");
-    if (type == "variable") register(value);
-    return cont();
-  }
-  function maybeFrom(_type, value) {
-    if (value == "from") { cx.marked = "keyword"; return cont(expression); }
-  }
-  function arrayLiteral(type) {
-    if (type == "]") return cont();
-    return pass(expressionNoComma, maybeArrayComprehension);
-  }
-  function maybeArrayComprehension(type) {
-    if (type == "for") return pass(comprehension, expect("]"));
-    if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
-    return pass(commasep(expressionNoComma, "]"));
-  }
-  function comprehension(type) {
-    if (type == "for") return cont(forspec, comprehension);
-    if (type == "if") return cont(expression, comprehension);
-  }
-
-  // Interface
-
-  return {
-    startState: function(basecolumn) {
-      var state = {
-        tokenize: tokenBase,
-        lastType: "sof",
-        cc: [],
-        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
-        localVars: parserConfig.localVars,
-        context: parserConfig.localVars && {vars: parserConfig.localVars},
-        indented: 0
-      };
-      if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
-        state.globalVars = parserConfig.globalVars;
-      return state;
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (!state.lexical.hasOwnProperty("align"))
-          state.lexical.align = false;
-        state.indented = stream.indentation();
-        findFatArrow(stream, state);
-      }
-      if (state.tokenize != tokenComment && stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      if (type == "comment") return style;
-      state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
-      return parseJS(state, style, type, content, stream);
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize == tokenComment) return CodeMirror.Pass;
-      if (state.tokenize != tokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
-      // Kludge to prevent 'maybelse' from blocking lexical scope pops
-      if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
-        var c = state.cc[i];
-        if (c == poplex) lexical = lexical.prev;
-        else if (c != maybeelse) break;
-      }
-      if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
-      if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
-        lexical = lexical.prev;
-      var type = lexical.type, closing = firstChar == type;
-
-      if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
-      else if (type == "form" && firstChar == "{") return lexical.indented;
-      else if (type == "form") return lexical.indented + indentUnit;
-      else if (type == "stat")
-        return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0);
-      else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
-        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
-      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
-      else return lexical.indented + (closing ? 0 : indentUnit);
-    },
-
-    electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
-    blockCommentStart: jsonMode ? null : "/*",
-    blockCommentEnd: jsonMode ? null : "*/",
-    lineComment: jsonMode ? null : "//",
-    fold: "brace",
-
-    helperType: jsonMode ? "json" : "javascript",
-    jsonldMode: jsonldMode,
-    jsonMode: jsonMode
-  };
-});
-
-CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
-
-CodeMirror.defineMIME("text/javascript", "javascript");
-CodeMirror.defineMIME("text/ecmascript", "javascript");
-CodeMirror.defineMIME("application/javascript", "javascript");
-CodeMirror.defineMIME("application/x-javascript", "javascript");
-CodeMirror.defineMIME("application/ecmascript", "javascript");
-CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
-CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
-CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
-CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
-
-});
--- a/kallithea/public/codemirror/mode/jinja2/jinja2.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.defineMode("jinja2", function() {
-    var keywords = ["and", "as", "block", "endblock", "by", "cycle", "debug", "else", "elif",
-      "extends", "filter", "endfilter", "firstof", "for",
-      "endfor", "if", "endif", "ifchanged", "endifchanged",
-      "ifequal", "endifequal", "ifnotequal",
-      "endifnotequal", "in", "include", "load", "not", "now", "or",
-      "parsed", "regroup", "reversed", "spaceless",
-      "endspaceless", "ssi", "templatetag", "openblock",
-      "closeblock", "openvariable", "closevariable",
-      "openbrace", "closebrace", "opencomment",
-      "closecomment", "widthratio", "url", "with", "endwith",
-      "get_current_language", "trans", "endtrans", "noop", "blocktrans",
-      "endblocktrans", "get_available_languages",
-      "get_current_language_bidi", "plural"],
-    operator = /^[+\-*&%=<>!?|~^]/,
-    sign = /^[:\[\(\{]/,
-    atom = ["true", "false"],
-    number = /^(\d[+\-\*\/])?\d+(\.\d+)?/;
-
-    keywords = new RegExp("((" + keywords.join(")|(") + "))\\b");
-    atom = new RegExp("((" + atom.join(")|(") + "))\\b");
-
-    function tokenBase (stream, state) {
-      var ch = stream.peek();
-
-      //Comment
-      if (state.incomment) {
-        if(!stream.skipTo("#}")) {
-          stream.skipToEnd();
-        } else {
-          stream.eatWhile(/\#|}/);
-          state.incomment = false;
-        }
-        return "comment";
-      //Tag
-      } else if (state.intag) {
-        //After operator
-        if(state.operator) {
-          state.operator = false;
-          if(stream.match(atom)) {
-            return "atom";
-          }
-          if(stream.match(number)) {
-            return "number";
-          }
-        }
-        //After sign
-        if(state.sign) {
-          state.sign = false;
-          if(stream.match(atom)) {
-            return "atom";
-          }
-          if(stream.match(number)) {
-            return "number";
-          }
-        }
-
-        if(state.instring) {
-          if(ch == state.instring) {
-            state.instring = false;
-          }
-          stream.next();
-          return "string";
-        } else if(ch == "'" || ch == '"') {
-          state.instring = ch;
-          stream.next();
-          return "string";
-        } else if(stream.match(state.intag + "}") || stream.eat("-") && stream.match(state.intag + "}")) {
-          state.intag = false;
-          return "tag";
-        } else if(stream.match(operator)) {
-          state.operator = true;
-          return "operator";
-        } else if(stream.match(sign)) {
-          state.sign = true;
-        } else {
-          if(stream.eat(" ") || stream.sol()) {
-            if(stream.match(keywords)) {
-              return "keyword";
-            }
-            if(stream.match(atom)) {
-              return "atom";
-            }
-            if(stream.match(number)) {
-              return "number";
-            }
-            if(stream.sol()) {
-              stream.next();
-            }
-          } else {
-            stream.next();
-          }
-
-        }
-        return "variable";
-      } else if (stream.eat("{")) {
-        if (ch = stream.eat("#")) {
-          state.incomment = true;
-          if(!stream.skipTo("#}")) {
-            stream.skipToEnd();
-          } else {
-            stream.eatWhile(/\#|}/);
-            state.incomment = false;
-          }
-          return "comment";
-        //Open tag
-        } else if (ch = stream.eat(/\{|%/)) {
-          //Cache close tag
-          state.intag = ch;
-          if(ch == "{") {
-            state.intag = "}";
-          }
-          stream.eat("-");
-          return "tag";
-        }
-      }
-      stream.next();
-    };
-
-    return {
-      startState: function () {
-        return {tokenize: tokenBase};
-      },
-      token: function (stream, state) {
-        return state.tokenize(stream, state);
-      }
-    };
-  });
-});
--- a/kallithea/public/codemirror/mode/julia/julia.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,301 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("julia", function(_conf, parserConf) {
-  var ERRORCLASS = 'error';
-
-  function wordRegexp(words) {
-    return new RegExp("^((" + words.join(")|(") + "))\\b");
-  }
-
-  var operators = parserConf.operators || /^\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b/;
-  var delimiters = parserConf.delimiters || /^[;,()[\]{}]/;
-  var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*!*/;
-  var blockOpeners = ["begin", "function", "type", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch", "do"];
-  var blockClosers = ["end", "else", "elseif", "catch", "finally"];
-  var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype', 'ccall'];
-  var builtinList = ['true', 'false', 'enumerate', 'open', 'close', 'nothing', 'NaN', 'Inf', 'print', 'println', 'Int', 'Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32', 'Int64', 'Uint64', 'Int128', 'Uint128', 'Bool', 'Char', 'Float16', 'Float32', 'Float64', 'Array', 'Vector', 'Matrix', 'String', 'UTF8String', 'ASCIIString', 'error', 'warn', 'info', '@printf'];
-
-  //var stringPrefixes = new RegExp("^[br]?('|\")")
-  var stringPrefixes = /^(`|'|"{3}|([br]?"))/;
-  var keywords = wordRegexp(keywordList);
-  var builtins = wordRegexp(builtinList);
-  var openers = wordRegexp(blockOpeners);
-  var closers = wordRegexp(blockClosers);
-  var macro = /^@[_A-Za-z][_A-Za-z0-9]*/;
-  var symbol = /^:[_A-Za-z][_A-Za-z0-9]*/;
-  var indentInfo = null;
-
-  function in_array(state) {
-    var ch = cur_scope(state);
-    if(ch=="[" || ch=="{") {
-      return true;
-    }
-    else {
-      return false;
-    }
-  }
-
-  function cur_scope(state) {
-    if(state.scopes.length==0) {
-      return null;
-    }
-    return state.scopes[state.scopes.length - 1];
-  }
-
-  // tokenizers
-  function tokenBase(stream, state) {
-    // Handle scope changes
-    var leaving_expr = state.leaving_expr;
-    if(stream.sol()) {
-      leaving_expr = false;
-    }
-    state.leaving_expr = false;
-    if(leaving_expr) {
-      if(stream.match(/^'+/)) {
-        return 'operator';
-      }
-
-    }
-
-    if(stream.match(/^\.{2,3}/)) {
-      return 'operator';
-    }
-
-    if (stream.eatSpace()) {
-      return null;
-    }
-
-    var ch = stream.peek();
-    // Handle Comments
-    if (ch === '#') {
-        stream.skipToEnd();
-        return 'comment';
-    }
-    if(ch==='[') {
-      state.scopes.push("[");
-    }
-
-    if(ch==='{') {
-      state.scopes.push("{");
-    }
-
-    var scope=cur_scope(state);
-
-    if(scope==='[' && ch===']') {
-      state.scopes.pop();
-      state.leaving_expr=true;
-    }
-
-    if(scope==='{' && ch==='}') {
-      state.scopes.pop();
-      state.leaving_expr=true;
-    }
-
-    if(ch===')') {
-      state.leaving_expr = true;
-    }
-
-    var match;
-    if(!in_array(state) && (match=stream.match(openers, false))) {
-      state.scopes.push(match);
-    }
-
-    if(!in_array(state) && stream.match(closers, false)) {
-      state.scopes.pop();
-    }
-
-    if(in_array(state)) {
-      if(stream.match(/^end/)) {
-        return 'number';
-      }
-
-    }
-
-    if(stream.match(/^=>/)) {
-      return 'operator';
-    }
-
-
-    // Handle Number Literals
-    if (stream.match(/^[0-9\.]/, false)) {
-      var imMatcher = RegExp(/^im\b/);
-      var floatLiteral = false;
-      // Floats
-      if (stream.match(/^\d*\.(?!\.)\d+([ef][\+\-]?\d+)?/i)) { floatLiteral = true; }
-      if (stream.match(/^\d+\.(?!\.)\d*/)) { floatLiteral = true; }
-      if (stream.match(/^\.\d+/)) { floatLiteral = true; }
-      if (floatLiteral) {
-          // Float literals may be "imaginary"
-          stream.match(imMatcher);
-          state.leaving_expr = true;
-          return 'number';
-      }
-      // Integers
-      var intLiteral = false;
-      // Hex
-      if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
-      // Binary
-      if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
-      // Octal
-      if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
-      // Decimal
-      if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
-          intLiteral = true;
-      }
-      // Zero by itself with no other piece of number.
-      if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
-      if (intLiteral) {
-          // Integer literals may be "long"
-          stream.match(imMatcher);
-          state.leaving_expr = true;
-          return 'number';
-      }
-    }
-
-    if(stream.match(/^(::)|(<:)/)) {
-      return 'operator';
-    }
-
-    // Handle symbols
-    if(!leaving_expr && stream.match(symbol)) {
-      return 'string';
-    }
-
-    // Handle operators and Delimiters
-    if (stream.match(operators)) {
-      return 'operator';
-    }
-
-
-    // Handle Strings
-    if (stream.match(stringPrefixes)) {
-      state.tokenize = tokenStringFactory(stream.current());
-      return state.tokenize(stream, state);
-    }
-
-    if (stream.match(macro)) {
-      return 'meta';
-    }
-
-
-    if (stream.match(delimiters)) {
-      return null;
-    }
-
-    if (stream.match(keywords)) {
-      return 'keyword';
-    }
-
-    if (stream.match(builtins)) {
-      return 'builtin';
-    }
-
-
-    if (stream.match(identifiers)) {
-      state.leaving_expr=true;
-      return 'variable';
-    }
-    // Handle non-detected items
-    stream.next();
-    return ERRORCLASS;
-  }
-
-  function tokenStringFactory(delimiter) {
-    while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
-      delimiter = delimiter.substr(1);
-    }
-    var singleline = delimiter.length == 1;
-    var OUTCLASS = 'string';
-
-    function tokenString(stream, state) {
-      while (!stream.eol()) {
-        stream.eatWhile(/[^'"\\]/);
-        if (stream.eat('\\')) {
-            stream.next();
-            if (singleline && stream.eol()) {
-              return OUTCLASS;
-            }
-        } else if (stream.match(delimiter)) {
-            state.tokenize = tokenBase;
-            return OUTCLASS;
-        } else {
-            stream.eat(/['"]/);
-        }
-      }
-      if (singleline) {
-        if (parserConf.singleLineStringErrors) {
-            return ERRORCLASS;
-        } else {
-            state.tokenize = tokenBase;
-        }
-      }
-      return OUTCLASS;
-    }
-    tokenString.isString = true;
-    return tokenString;
-  }
-
-  function tokenLexer(stream, state) {
-    indentInfo = null;
-    var style = state.tokenize(stream, state);
-    var current = stream.current();
-
-    // Handle '.' connected identifiers
-    if (current === '.') {
-      style = stream.match(identifiers, false) ? null : ERRORCLASS;
-      if (style === null && state.lastStyle === 'meta') {
-          // Apply 'meta' style to '.' connected identifiers when
-          // appropriate.
-        style = 'meta';
-      }
-      return style;
-    }
-
-    return style;
-  }
-
-  var external = {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        scopes: [],
-        leaving_expr: false
-      };
-    },
-
-    token: function(stream, state) {
-      var style = tokenLexer(stream, state);
-      state.lastStyle = style;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var delta = 0;
-      if(textAfter=="end" || textAfter=="]" || textAfter=="}" || textAfter=="else" || textAfter=="elseif" || textAfter=="catch" || textAfter=="finally") {
-        delta = -1;
-      }
-      return (state.scopes.length + delta) * 4;
-    },
-
-    lineComment: "#",
-    fold: "indent",
-    electricChars: "edlsifyh]}"
-  };
-  return external;
-});
-
-
-CodeMirror.defineMIME("text/x-julia", "julia");
-
-});
--- a/kallithea/public/codemirror/mode/kotlin/kotlin.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("kotlin", function (config, parserConfig) {
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  var multiLineStrings = parserConfig.multiLineStrings;
-
-  var keywords = words(
-          "package continue return object while break class data trait throw super" +
-          " when type this else This try val var fun for is in if do as true false null get set");
-  var softKeywords = words("import" +
-      " where by get set abstract enum open annotation override private public internal" +
-      " protected catch out vararg inline finally final ref");
-  var blockKeywords = words("catch class do else finally for if where try while enum");
-  var atoms = words("null true false this");
-
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"' || ch == "'") {
-      return startString(ch, stream, state);
-    }
-    // Wildcard import w/o trailing semicolon (import smth.*)
-    if (ch == "." && stream.eat("*")) {
-      return "word";
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      if (stream.eat(/eE/)) {
-        stream.eat(/\+\-/);
-        stream.eatWhile(/\d/);
-      }
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("*")) {
-        state.tokenize.push(tokenComment);
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      if (expectExpression(state.lastToken)) {
-        return startString(ch, stream, state);
-      }
-    }
-    // Commented
-    if (ch == "-" && stream.eat(">")) {
-      curPunc = "->";
-      return null;
-    }
-    if (/[\-+*&%=<>!?|\/~]/.test(ch)) {
-      stream.eatWhile(/[\-+*&%=<>|~]/);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_]/);
-
-    var cur = stream.current();
-    if (atoms.propertyIsEnumerable(cur)) {
-      return "atom";
-    }
-    if (softKeywords.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "softKeyword";
-    }
-
-    if (keywords.propertyIsEnumerable(cur)) {
-      if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
-      return "keyword";
-    }
-    return "word";
-  }
-
-  tokenBase.isBase = true;
-
-  function startString(quote, stream, state) {
-    var tripleQuoted = false;
-    if (quote != "/" && stream.eat(quote)) {
-      if (stream.eat(quote)) tripleQuoted = true;
-      else return "string";
-    }
-    function t(stream, state) {
-      var escaped = false, next, end = !tripleQuoted;
-
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {
-          if (!tripleQuoted) {
-            break;
-          }
-          if (stream.match(quote + quote)) {
-            end = true;
-            break;
-          }
-        }
-
-        if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
-          state.tokenize.push(tokenBaseUntilBrace());
-          return "string";
-        }
-
-        if (next == "$" && !escaped && !stream.eat(" ")) {
-          state.tokenize.push(tokenBaseUntilSpace());
-          return "string";
-        }
-        escaped = !escaped && next == "\\";
-      }
-      if (multiLineStrings)
-        state.tokenize.push(t);
-      if (end) state.tokenize.pop();
-      return "string";
-    }
-
-    state.tokenize.push(t);
-    return t(stream, state);
-  }
-
-  function tokenBaseUntilBrace() {
-    var depth = 1;
-
-    function t(stream, state) {
-      if (stream.peek() == "}") {
-        depth--;
-        if (depth == 0) {
-          state.tokenize.pop();
-          return state.tokenize[state.tokenize.length - 1](stream, state);
-        }
-      } else if (stream.peek() == "{") {
-        depth++;
-      }
-      return tokenBase(stream, state);
-    }
-
-    t.isBase = true;
-    return t;
-  }
-
-  function tokenBaseUntilSpace() {
-    function t(stream, state) {
-      if (stream.eat(/[\w]/)) {
-        var isWord = stream.eatWhile(/[\w]/);
-        if (isWord) {
-          state.tokenize.pop();
-          return "word";
-        }
-      }
-      state.tokenize.pop();
-      return "string";
-    }
-
-    t.isBase = true;
-    return t;
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize.pop();
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function expectExpression(last) {
-    return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
-        last == "newstatement" || last == "keyword" || last == "proplabel";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-
-  function pushContext(state, col, type) {
-    return state.context = new Context(state.indented, col, type, null, state.context);
-  }
-
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}")
-      state.indented = state.context.indented;
-    return state.context = state.context.prev;
-  }
-
-  // Interface
-
-  return {
-    startState: function (basecolumn) {
-      return {
-        tokenize: [tokenBase],
-        context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true,
-        lastToken: null
-      };
-    },
-
-    token: function (stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-        // Automatic semicolon insertion
-        if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
-          popContext(state);
-          ctx = state.context;
-        }
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      var style = state.tokenize[state.tokenize.length - 1](stream, state);
-      if (style == "comment") return style;
-      if (ctx.align == null) ctx.align = true;
-      if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
-      // Handle indentation for {x -> \n ... }
-      else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
-        popContext(state);
-        state.context.align = false;
-      }
-      else if (curPunc == "{") pushContext(state, stream.column(), "}");
-      else if (curPunc == "[") pushContext(state, stream.column(), "]");
-      else if (curPunc == "(") pushContext(state, stream.column(), ")");
-      else if (curPunc == "}") {
-        while (ctx.type == "statement") ctx = popContext(state);
-        if (ctx.type == "}") ctx = popContext(state);
-        while (ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == ctx.type) popContext(state);
-      else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
-        pushContext(state, stream.column(), "statement");
-      state.startOfLine = false;
-      state.lastToken = curPunc || style;
-      return style;
-    },
-
-    indent: function (state, textAfter) {
-      if (!state.tokenize[state.tokenize.length - 1].isBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
-      if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
-      var closing = firstChar == ctx.type;
-      if (ctx.type == "statement") {
-        return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
-      }
-      else if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indented + (closing ? 0 : config.indentUnit);
-    },
-
-    electricChars: "{}"
-  };
-});
-
-CodeMirror.defineMIME("text/x-kotlin", "kotlin");
-
-});
--- a/kallithea/public/codemirror/mode/livescript/livescript.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Link to the project's GitHub page:
- * https://github.com/duralog/CodeMirror
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.defineMode('livescript', function(){
-    var tokenBase = function(stream, state) {
-      var next_rule = state.next || "start";
-      if (next_rule) {
-        state.next = state.next;
-        var nr = Rules[next_rule];
-        if (nr.splice) {
-          for (var i$ = 0; i$ < nr.length; ++i$) {
-            var r = nr[i$], m;
-            if (r.regex && (m = stream.match(r.regex))) {
-              state.next = r.next || state.next;
-              return r.token;
-            }
-          }
-          stream.next();
-          return 'error';
-        }
-        if (stream.match(r = Rules[next_rule])) {
-          if (r.regex && stream.match(r.regex)) {
-            state.next = r.next;
-            return r.token;
-          } else {
-            stream.next();
-            return 'error';
-          }
-        }
-      }
-      stream.next();
-      return 'error';
-    };
-    var external = {
-      startState: function(){
-        return {
-          next: 'start',
-          lastToken: null
-        };
-      },
-      token: function(stream, state){
-        while (stream.pos == stream.start)
-          var style = tokenBase(stream, state);
-        state.lastToken = {
-          style: style,
-          indent: stream.indentation(),
-          content: stream.current()
-        };
-        return style.replace(/\./g, ' ');
-      },
-      indent: function(state){
-        var indentation = state.lastToken.indent;
-        if (state.lastToken.content.match(indenter)) {
-          indentation += 2;
-        }
-        return indentation;
-      }
-    };
-    return external;
-  });
-
-  var identifier = '(?![\\d\\s])[$\\w\\xAA-\\uFFDC](?:(?!\\s)[$\\w\\xAA-\\uFFDC]|-[A-Za-z])*';
-  var indenter = RegExp('(?:[({[=:]|[-~]>|\\b(?:e(?:lse|xport)|d(?:o|efault)|t(?:ry|hen)|finally|import(?:\\s*all)?|const|var|let|new|catch(?:\\s*' + identifier + ')?))\\s*$');
-  var keywordend = '(?![$\\w]|-[A-Za-z]|\\s*:(?![:=]))';
-  var stringfill = {
-    token: 'string',
-    regex: '.+'
-  };
-  var Rules = {
-    start: [
-      {
-        token: 'comment.doc',
-        regex: '/\\*',
-        next: 'comment'
-      }, {
-        token: 'comment',
-        regex: '#.*'
-      }, {
-        token: 'keyword',
-        regex: '(?:t(?:h(?:is|row|en)|ry|ypeof!?)|c(?:on(?:tinue|st)|a(?:se|tch)|lass)|i(?:n(?:stanceof)?|mp(?:ort(?:\\s+all)?|lements)|[fs])|d(?:e(?:fault|lete|bugger)|o)|f(?:or(?:\\s+own)?|inally|unction)|s(?:uper|witch)|e(?:lse|x(?:tends|port)|val)|a(?:nd|rguments)|n(?:ew|ot)|un(?:less|til)|w(?:hile|ith)|o[fr]|return|break|let|var|loop)' + keywordend
-      }, {
-        token: 'constant.language',
-        regex: '(?:true|false|yes|no|on|off|null|void|undefined)' + keywordend
-      }, {
-        token: 'invalid.illegal',
-        regex: '(?:p(?:ackage|r(?:ivate|otected)|ublic)|i(?:mplements|nterface)|enum|static|yield)' + keywordend
-      }, {
-        token: 'language.support.class',
-        regex: '(?:R(?:e(?:gExp|ferenceError)|angeError)|S(?:tring|yntaxError)|E(?:rror|valError)|Array|Boolean|Date|Function|Number|Object|TypeError|URIError)' + keywordend
-      }, {
-        token: 'language.support.function',
-        regex: '(?:is(?:NaN|Finite)|parse(?:Int|Float)|Math|JSON|(?:en|de)codeURI(?:Component)?)' + keywordend
-      }, {
-        token: 'variable.language',
-        regex: '(?:t(?:hat|il|o)|f(?:rom|allthrough)|it|by|e)' + keywordend
-      }, {
-        token: 'identifier',
-        regex: identifier + '\\s*:(?![:=])'
-      }, {
-        token: 'variable',
-        regex: identifier
-      }, {
-        token: 'keyword.operator',
-        regex: '(?:\\.{3}|\\s+\\?)'
-      }, {
-        token: 'keyword.variable',
-        regex: '(?:@+|::|\\.\\.)',
-        next: 'key'
-      }, {
-        token: 'keyword.operator',
-        regex: '\\.\\s*',
-        next: 'key'
-      }, {
-        token: 'string',
-        regex: '\\\\\\S[^\\s,;)}\\]]*'
-      }, {
-        token: 'string.doc',
-        regex: '\'\'\'',
-        next: 'qdoc'
-      }, {
-        token: 'string.doc',
-        regex: '"""',
-        next: 'qqdoc'
-      }, {
-        token: 'string',
-        regex: '\'',
-        next: 'qstring'
-      }, {
-        token: 'string',
-        regex: '"',
-        next: 'qqstring'
-      }, {
-        token: 'string',
-        regex: '`',
-        next: 'js'
-      }, {
-        token: 'string',
-        regex: '<\\[',
-        next: 'words'
-      }, {
-        token: 'string.regex',
-        regex: '//',
-        next: 'heregex'
-      }, {
-        token: 'string.regex',
-        regex: '\\/(?:[^[\\/\\n\\\\]*(?:(?:\\\\.|\\[[^\\]\\n\\\\]*(?:\\\\.[^\\]\\n\\\\]*)*\\])[^[\\/\\n\\\\]*)*)\\/[gimy$]{0,4}',
-        next: 'key'
-      }, {
-        token: 'constant.numeric',
-        regex: '(?:0x[\\da-fA-F][\\da-fA-F_]*|(?:[2-9]|[12]\\d|3[0-6])r[\\da-zA-Z][\\da-zA-Z_]*|(?:\\d[\\d_]*(?:\\.\\d[\\d_]*)?|\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[\\w$]*)'
-      }, {
-        token: 'lparen',
-        regex: '[({[]'
-      }, {
-        token: 'rparen',
-        regex: '[)}\\]]',
-        next: 'key'
-      }, {
-        token: 'keyword.operator',
-        regex: '\\S+'
-      }, {
-        token: 'text',
-        regex: '\\s+'
-      }
-    ],
-    heregex: [
-      {
-        token: 'string.regex',
-        regex: '.*?//[gimy$?]{0,4}',
-        next: 'start'
-      }, {
-        token: 'string.regex',
-        regex: '\\s*#{'
-      }, {
-        token: 'comment.regex',
-        regex: '\\s+(?:#.*)?'
-      }, {
-        token: 'string.regex',
-        regex: '\\S+'
-      }
-    ],
-    key: [
-      {
-        token: 'keyword.operator',
-        regex: '[.?@!]+'
-      }, {
-        token: 'identifier',
-        regex: identifier,
-        next: 'start'
-      }, {
-        token: 'text',
-        regex: '',
-        next: 'start'
-      }
-    ],
-    comment: [
-      {
-        token: 'comment.doc',
-        regex: '.*?\\*/',
-        next: 'start'
-      }, {
-        token: 'comment.doc',
-        regex: '.+'
-      }
-    ],
-    qdoc: [
-      {
-        token: 'string',
-        regex: ".*?'''",
-        next: 'key'
-      }, stringfill
-    ],
-    qqdoc: [
-      {
-        token: 'string',
-        regex: '.*?"""',
-        next: 'key'
-      }, stringfill
-    ],
-    qstring: [
-      {
-        token: 'string',
-        regex: '[^\\\\\']*(?:\\\\.[^\\\\\']*)*\'',
-        next: 'key'
-      }, stringfill
-    ],
-    qqstring: [
-      {
-        token: 'string',
-        regex: '[^\\\\"]*(?:\\\\.[^\\\\"]*)*"',
-        next: 'key'
-      }, stringfill
-    ],
-    js: [
-      {
-        token: 'string',
-        regex: '[^\\\\`]*(?:\\\\.[^\\\\`]*)*`',
-        next: 'key'
-      }, stringfill
-    ],
-    words: [
-      {
-        token: 'string',
-        regex: '.*?\\]>',
-        next: 'key'
-      }, stringfill
-    ]
-  };
-  for (var idx in Rules) {
-    var r = Rules[idx];
-    if (r.splice) {
-      for (var i = 0, len = r.length; i < len; ++i) {
-        var rr = r[i];
-        if (typeof rr.regex === 'string') {
-          Rules[idx][i].regex = new RegExp('^' + rr.regex);
-        }
-      }
-    } else if (typeof rr.regex === 'string') {
-      Rules[idx].regex = new RegExp('^' + r.regex);
-    }
-  }
-
-  CodeMirror.defineMIME('text/x-livescript', 'livescript');
-
-});
--- a/kallithea/public/codemirror/mode/lua/lua.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,159 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's
-// CodeMirror 1 mode.
-// highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("lua", function(config, parserConfig) {
-  var indentUnit = config.indentUnit;
-
-  function prefixRE(words) {
-    return new RegExp("^(?:" + words.join("|") + ")", "i");
-  }
-  function wordRE(words) {
-    return new RegExp("^(?:" + words.join("|") + ")$", "i");
-  }
-  var specials = wordRE(parserConfig.specials || []);
-
-  // long list of standard functions from lua manual
-  var builtins = wordRE([
-    "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load",
-    "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require",
-    "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall",
-
-    "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield",
-
-    "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable",
-    "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable",
-    "debug.setupvalue","debug.traceback",
-
-    "close","flush","lines","read","seek","setvbuf","write",
-
-    "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin",
-    "io.stdout","io.tmpfile","io.type","io.write",
-
-    "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg",
-    "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max",
-    "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh",
-    "math.sqrt","math.tan","math.tanh",
-
-    "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale",
-    "os.time","os.tmpname",
-
-    "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload",
-    "package.seeall",
-
-    "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub",
-    "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper",
-
-    "table.concat","table.insert","table.maxn","table.remove","table.sort"
-  ]);
-  var keywords = wordRE(["and","break","elseif","false","nil","not","or","return",
-                         "true","function", "end", "if", "then", "else", "do",
-                         "while", "repeat", "until", "for", "in", "local" ]);
-
-  var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]);
-  var dedentTokens = wordRE(["end", "until", "\\)", "}"]);
-  var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]);
-
-  function readBracket(stream) {
-    var level = 0;
-    while (stream.eat("=")) ++level;
-    stream.eat("[");
-    return level;
-  }
-
-  function normal(stream, state) {
-    var ch = stream.next();
-    if (ch == "-" && stream.eat("-")) {
-      if (stream.eat("[") && stream.eat("["))
-        return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state);
-      stream.skipToEnd();
-      return "comment";
-    }
-    if (ch == "\"" || ch == "'")
-      return (state.cur = string(ch))(stream, state);
-    if (ch == "[" && /[\[=]/.test(stream.peek()))
-      return (state.cur = bracketed(readBracket(stream), "string"))(stream, state);
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w.%]/);
-      return "number";
-    }
-    if (/[\w_]/.test(ch)) {
-      stream.eatWhile(/[\w\\\-_.]/);
-      return "variable";
-    }
-    return null;
-  }
-
-  function bracketed(level, style) {
-    return function(stream, state) {
-      var curlev = null, ch;
-      while ((ch = stream.next()) != null) {
-        if (curlev == null) {if (ch == "]") curlev = 0;}
-        else if (ch == "=") ++curlev;
-        else if (ch == "]" && curlev == level) { state.cur = normal; break; }
-        else curlev = null;
-      }
-      return style;
-    };
-  }
-
-  function string(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) break;
-        escaped = !escaped && ch == "\\";
-      }
-      if (!escaped) state.cur = normal;
-      return "string";
-    };
-  }
-
-  return {
-    startState: function(basecol) {
-      return {basecol: basecol || 0, indentDepth: 0, cur: normal};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = state.cur(stream, state);
-      var word = stream.current();
-      if (style == "variable") {
-        if (keywords.test(word)) style = "keyword";
-        else if (builtins.test(word)) style = "builtin";
-        else if (specials.test(word)) style = "variable-2";
-      }
-      if ((style != "comment") && (style != "string")){
-        if (indentTokens.test(word)) ++state.indentDepth;
-        else if (dedentTokens.test(word)) --state.indentDepth;
-      }
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var closing = dedentPartial.test(textAfter);
-      return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0));
-    },
-
-    lineComment: "--",
-    blockCommentStart: "--[[",
-    blockCommentEnd: "]]"
-  };
-});
-
-CodeMirror.defineMIME("text/x-lua", "lua");
-
-});
--- a/kallithea/public/codemirror/mode/markdown/markdown.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,758 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror", require("../xml/xml")));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../xml/xml"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
-
-  var htmlFound = CodeMirror.modes.hasOwnProperty("xml");
-  var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain");
-  var aliases = {
-    html: "htmlmixed",
-    js: "javascript",
-    json: "application/json",
-    c: "text/x-csrc",
-    "c++": "text/x-c++src",
-    java: "text/x-java",
-    csharp: "text/x-csharp",
-    "c#": "text/x-csharp",
-    scala: "text/x-scala"
-  };
-
-  var getMode = (function () {
-    var i, modes = {}, mimes = {}, mime;
-
-    var list = [];
-    for (var m in CodeMirror.modes)
-      if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
-    for (i = 0; i < list.length; i++) {
-      modes[list[i]] = list[i];
-    }
-    var mimesList = [];
-    for (var m in CodeMirror.mimeModes)
-      if (CodeMirror.mimeModes.propertyIsEnumerable(m))
-        mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
-    for (i = 0; i < mimesList.length; i++) {
-      mime = mimesList[i].mime;
-      mimes[mime] = mimesList[i].mime;
-    }
-
-    for (var a in aliases) {
-      if (aliases[a] in modes || aliases[a] in mimes)
-        modes[a] = aliases[a];
-    }
-
-    return function (lang) {
-      return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;
-    };
-  }());
-
-  // Should characters that affect highlighting be highlighted separate?
-  // Does not include characters that will be output (such as `1.` and `-` for lists)
-  if (modeCfg.highlightFormatting === undefined)
-    modeCfg.highlightFormatting = false;
-
-  // Maximum number of nested blockquotes. Set to 0 for infinite nesting.
-  // Excess `>` will emit `error` token.
-  if (modeCfg.maxBlockquoteDepth === undefined)
-    modeCfg.maxBlockquoteDepth = 0;
-
-  // Should underscores in words open/close em/strong?
-  if (modeCfg.underscoresBreakWords === undefined)
-    modeCfg.underscoresBreakWords = true;
-
-  // Turn on fenced code blocks? ("```" to start/end)
-  if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
-
-  // Turn on task lists? ("- [ ] " and "- [x] ")
-  if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
-
-  var codeDepth = 0;
-
-  var header   = 'header'
-  ,   code     = 'comment'
-  ,   quote    = 'quote'
-  ,   list1    = 'variable-2'
-  ,   list2    = 'variable-3'
-  ,   list3    = 'keyword'
-  ,   hr       = 'hr'
-  ,   image    = 'tag'
-  ,   formatting = 'formatting'
-  ,   linkinline = 'link'
-  ,   linkemail = 'link'
-  ,   linktext = 'link'
-  ,   linkhref = 'string'
-  ,   em       = 'em'
-  ,   strong   = 'strong';
-
-  var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
-  ,   ulRE = /^[*\-+]\s+/
-  ,   olRE = /^[0-9]+\.\s+/
-  ,   taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
-  ,   atxHeaderRE = /^#+/
-  ,   setextHeaderRE = /^(?:\={1,}|-{1,})$/
-  ,   textRE = /^[^#!\[\]*_\\<>` "'(]+/;
-
-  function switchInline(stream, state, f) {
-    state.f = state.inline = f;
-    return f(stream, state);
-  }
-
-  function switchBlock(stream, state, f) {
-    state.f = state.block = f;
-    return f(stream, state);
-  }
-
-
-  // Blocks
-
-  function blankLine(state) {
-    // Reset linkTitle state
-    state.linkTitle = false;
-    // Reset EM state
-    state.em = false;
-    // Reset STRONG state
-    state.strong = false;
-    // Reset state.quote
-    state.quote = 0;
-    if (!htmlFound && state.f == htmlBlock) {
-      state.f = inlineNormal;
-      state.block = blockNormal;
-    }
-    // Reset state.trailingSpace
-    state.trailingSpace = 0;
-    state.trailingSpaceNewLine = false;
-    // Mark this line as blank
-    state.thisLineHasContent = false;
-    return null;
-  }
-
-  function blockNormal(stream, state) {
-
-    var sol = stream.sol();
-
-    var prevLineIsList = (state.list !== false);
-    if (state.list !== false && state.indentationDiff >= 0) { // Continued list
-      if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
-        state.indentation -= state.indentationDiff;
-      }
-      state.list = null;
-    } else if (state.list !== false && state.indentation > 0) {
-      state.list = null;
-      state.listDepth = Math.floor(state.indentation / 4);
-    } else if (state.list !== false) { // No longer a list
-      state.list = false;
-      state.listDepth = 0;
-    }
-
-    var match = null;
-    if (state.indentationDiff >= 4) {
-      state.indentation -= 4;
-      stream.skipToEnd();
-      return code;
-    } else if (stream.eatSpace()) {
-      return null;
-    } else if (match = stream.match(atxHeaderRE)) {
-      state.header = match[0].length <= 6 ? match[0].length : 6;
-      if (modeCfg.highlightFormatting) state.formatting = "header";
-      state.f = state.inline;
-      return getType(state);
-    } else if (state.prevLineHasContent && (match = stream.match(setextHeaderRE))) {
-      state.header = match[0].charAt(0) == '=' ? 1 : 2;
-      if (modeCfg.highlightFormatting) state.formatting = "header";
-      state.f = state.inline;
-      return getType(state);
-    } else if (stream.eat('>')) {
-      state.indentation++;
-      state.quote = sol ? 1 : state.quote + 1;
-      if (modeCfg.highlightFormatting) state.formatting = "quote";
-      stream.eatSpace();
-      return getType(state);
-    } else if (stream.peek() === '[') {
-      return switchInline(stream, state, footnoteLink);
-    } else if (stream.match(hrRE, true)) {
-      return hr;
-    } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
-      var listType = null;
-      if (stream.match(ulRE, true)) {
-        listType = 'ul';
-      } else {
-        stream.match(olRE, true);
-        listType = 'ol';
-      }
-      state.indentation += 4;
-      state.list = true;
-      state.listDepth++;
-      if (modeCfg.taskLists && stream.match(taskListRE, false)) {
-        state.taskList = true;
-      }
-      state.f = state.inline;
-      if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
-      return getType(state);
-    } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) {
-      // try switching mode
-      state.localMode = getMode(RegExp.$1);
-      if (state.localMode) state.localState = state.localMode.startState();
-      switchBlock(stream, state, local);
-      if (modeCfg.highlightFormatting) state.formatting = "code-block";
-      state.code = true;
-      return getType(state);
-    }
-
-    return switchInline(stream, state, state.inline);
-  }
-
-  function htmlBlock(stream, state) {
-    var style = htmlMode.token(stream, state.htmlState);
-    if ((htmlFound && state.htmlState.tagStart === null && !state.htmlState.context) ||
-        (state.md_inside && stream.current().indexOf(">") > -1)) {
-      state.f = inlineNormal;
-      state.block = blockNormal;
-      state.htmlState = null;
-    }
-    return style;
-  }
-
-  function local(stream, state) {
-    if (stream.sol() && stream.match(/^```/, true)) {
-      state.localMode = state.localState = null;
-      state.f = inlineNormal;
-      state.block = blockNormal;
-      if (modeCfg.highlightFormatting) state.formatting = "code-block";
-      state.code = true;
-      var returnType = getType(state);
-      state.code = false;
-      return returnType;
-    } else if (state.localMode) {
-      return state.localMode.token(stream, state.localState);
-    } else {
-      stream.skipToEnd();
-      return code;
-    }
-  }
-
-  // Inline
-  function getType(state) {
-    var styles = [];
-
-    if (state.formatting) {
-      styles.push(formatting);
-
-      if (typeof state.formatting === "string") state.formatting = [state.formatting];
-
-      for (var i = 0; i < state.formatting.length; i++) {
-        styles.push(formatting + "-" + state.formatting[i]);
-
-        if (state.formatting[i] === "header") {
-          styles.push(formatting + "-" + state.formatting[i] + "-" + state.header);
-        }
-
-        // Add `formatting-quote` and `formatting-quote-#` for blockquotes
-        // Add `error` instead if the maximum blockquote nesting depth is passed
-        if (state.formatting[i] === "quote") {
-          if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
-            styles.push(formatting + "-" + state.formatting[i] + "-" + state.quote);
-          } else {
-            styles.push("error");
-          }
-        }
-      }
-    }
-
-    if (state.taskOpen) {
-      styles.push("meta");
-      return styles.length ? styles.join(' ') : null;
-    }
-    if (state.taskClosed) {
-      styles.push("property");
-      return styles.length ? styles.join(' ') : null;
-    }
-
-    if (state.linkHref) {
-      styles.push(linkhref);
-      return styles.length ? styles.join(' ') : null;
-    }
-
-    if (state.strong) { styles.push(strong); }
-    if (state.em) { styles.push(em); }
-
-    if (state.linkText) { styles.push(linktext); }
-
-    if (state.code) { styles.push(code); }
-
-    if (state.header) { styles.push(header); styles.push(header + "-" + state.header); }
-
-    if (state.quote) {
-      styles.push(quote);
-
-      // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
-      if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
-        styles.push(quote + "-" + state.quote);
-      } else {
-        styles.push(quote + "-" + modeCfg.maxBlockquoteDepth);
-      }
-    }
-
-    if (state.list !== false) {
-      var listMod = (state.listDepth - 1) % 3;
-      if (!listMod) {
-        styles.push(list1);
-      } else if (listMod === 1) {
-        styles.push(list2);
-      } else {
-        styles.push(list3);
-      }
-    }
-
-    if (state.trailingSpaceNewLine) {
-      styles.push("trailing-space-new-line");
-    } else if (state.trailingSpace) {
-      styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
-    }
-
-    return styles.length ? styles.join(' ') : null;
-  }
-
-  function handleText(stream, state) {
-    if (stream.match(textRE, true)) {
-      return getType(state);
-    }
-    return undefined;
-  }
-
-  function inlineNormal(stream, state) {
-    var style = state.text(stream, state);
-    if (typeof style !== 'undefined')
-      return style;
-
-    if (state.list) { // List marker (*, +, -, 1., etc)
-      state.list = null;
-      return getType(state);
-    }
-
-    if (state.taskList) {
-      var taskOpen = stream.match(taskListRE, true)[1] !== "x";
-      if (taskOpen) state.taskOpen = true;
-      else state.taskClosed = true;
-      if (modeCfg.highlightFormatting) state.formatting = "task";
-      state.taskList = false;
-      return getType(state);
-    }
-
-    state.taskOpen = false;
-    state.taskClosed = false;
-
-    if (state.header && stream.match(/^#+$/, true)) {
-      if (modeCfg.highlightFormatting) state.formatting = "header";
-      return getType(state);
-    }
-
-    // Get sol() value now, before character is consumed
-    var sol = stream.sol();
-
-    var ch = stream.next();
-
-    if (ch === '\\') {
-      stream.next();
-      if (modeCfg.highlightFormatting) {
-        var type = getType(state);
-        return type ? type + " formatting-escape" : "formatting-escape";
-      }
-    }
-
-    // Matches link titles present on next line
-    if (state.linkTitle) {
-      state.linkTitle = false;
-      var matchCh = ch;
-      if (ch === '(') {
-        matchCh = ')';
-      }
-      matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
-      var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
-      if (stream.match(new RegExp(regex), true)) {
-        return linkhref;
-      }
-    }
-
-    // If this block is changed, it may need to be updated in GFM mode
-    if (ch === '`') {
-      var previousFormatting = state.formatting;
-      if (modeCfg.highlightFormatting) state.formatting = "code";
-      var t = getType(state);
-      var before = stream.pos;
-      stream.eatWhile('`');
-      var difference = 1 + stream.pos - before;
-      if (!state.code) {
-        codeDepth = difference;
-        state.code = true;
-        return getType(state);
-      } else {
-        if (difference === codeDepth) { // Must be exact
-          state.code = false;
-          return t;
-        }
-        state.formatting = previousFormatting;
-        return getType(state);
-      }
-    } else if (state.code) {
-      return getType(state);
-    }
-
-    if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
-      stream.match(/\[[^\]]*\]/);
-      state.inline = state.f = linkHref;
-      return image;
-    }
-
-    if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) {
-      state.linkText = true;
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      return getType(state);
-    }
-
-    if (ch === ']' && state.linkText) {
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      var type = getType(state);
-      state.linkText = false;
-      state.inline = state.f = linkHref;
-      return type;
-    }
-
-    if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
-      state.f = state.inline = linkInline;
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      var type = getType(state);
-      if (type){
-        type += " ";
-      } else {
-        type = "";
-      }
-      return type + linkinline;
-    }
-
-    if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
-      state.f = state.inline = linkInline;
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      var type = getType(state);
-      if (type){
-        type += " ";
-      } else {
-        type = "";
-      }
-      return type + linkemail;
-    }
-
-    if (ch === '<' && stream.match(/^\w/, false)) {
-      if (stream.string.indexOf(">") != -1) {
-        var atts = stream.string.substring(1,stream.string.indexOf(">"));
-        if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
-          state.md_inside = true;
-        }
-      }
-      stream.backUp(1);
-      state.htmlState = CodeMirror.startState(htmlMode);
-      return switchBlock(stream, state, htmlBlock);
-    }
-
-    if (ch === '<' && stream.match(/^\/\w*?>/)) {
-      state.md_inside = false;
-      return "tag";
-    }
-
-    var ignoreUnderscore = false;
-    if (!modeCfg.underscoresBreakWords) {
-      if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
-        var prevPos = stream.pos - 2;
-        if (prevPos >= 0) {
-          var prevCh = stream.string.charAt(prevPos);
-          if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
-            ignoreUnderscore = true;
-          }
-        }
-      }
-    }
-    if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
-      if (sol && stream.peek() === ' ') {
-        // Do nothing, surrounded by newline and space
-      } else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
-        if (modeCfg.highlightFormatting) state.formatting = "strong";
-        var t = getType(state);
-        state.strong = false;
-        return t;
-      } else if (!state.strong && stream.eat(ch)) { // Add STRONG
-        state.strong = ch;
-        if (modeCfg.highlightFormatting) state.formatting = "strong";
-        return getType(state);
-      } else if (state.em === ch) { // Remove EM
-        if (modeCfg.highlightFormatting) state.formatting = "em";
-        var t = getType(state);
-        state.em = false;
-        return t;
-      } else if (!state.em) { // Add EM
-        state.em = ch;
-        if (modeCfg.highlightFormatting) state.formatting = "em";
-        return getType(state);
-      }
-    } else if (ch === ' ') {
-      if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
-        if (stream.peek() === ' ') { // Surrounded by spaces, ignore
-          return getType(state);
-        } else { // Not surrounded by spaces, back up pointer
-          stream.backUp(1);
-        }
-      }
-    }
-
-    if (ch === ' ') {
-      if (stream.match(/ +$/, false)) {
-        state.trailingSpace++;
-      } else if (state.trailingSpace) {
-        state.trailingSpaceNewLine = true;
-      }
-    }
-
-    return getType(state);
-  }
-
-  function linkInline(stream, state) {
-    var ch = stream.next();
-
-    if (ch === ">") {
-      state.f = state.inline = inlineNormal;
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      var type = getType(state);
-      if (type){
-        type += " ";
-      } else {
-        type = "";
-      }
-      return type + linkinline;
-    }
-
-    stream.match(/^[^>]+/, true);
-
-    return linkinline;
-  }
-
-  function linkHref(stream, state) {
-    // Check if space, and return NULL if so (to avoid marking the space)
-    if(stream.eatSpace()){
-      return null;
-    }
-    var ch = stream.next();
-    if (ch === '(' || ch === '[') {
-      state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
-      if (modeCfg.highlightFormatting) state.formatting = "link-string";
-      state.linkHref = true;
-      return getType(state);
-    }
-    return 'error';
-  }
-
-  function getLinkHrefInside(endChar) {
-    return function(stream, state) {
-      var ch = stream.next();
-
-      if (ch === endChar) {
-        state.f = state.inline = inlineNormal;
-        if (modeCfg.highlightFormatting) state.formatting = "link-string";
-        var returnState = getType(state);
-        state.linkHref = false;
-        return returnState;
-      }
-
-      if (stream.match(inlineRE(endChar), true)) {
-        stream.backUp(1);
-      }
-
-      state.linkHref = true;
-      return getType(state);
-    };
-  }
-
-  function footnoteLink(stream, state) {
-    if (stream.match(/^[^\]]*\]:/, false)) {
-      state.f = footnoteLinkInside;
-      stream.next(); // Consume [
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      state.linkText = true;
-      return getType(state);
-    }
-    return switchInline(stream, state, inlineNormal);
-  }
-
-  function footnoteLinkInside(stream, state) {
-    if (stream.match(/^\]:/, true)) {
-      state.f = state.inline = footnoteUrl;
-      if (modeCfg.highlightFormatting) state.formatting = "link";
-      var returnType = getType(state);
-      state.linkText = false;
-      return returnType;
-    }
-
-    stream.match(/^[^\]]+/, true);
-
-    return linktext;
-  }
-
-  function footnoteUrl(stream, state) {
-    // Check if space, and return NULL if so (to avoid marking the space)
-    if(stream.eatSpace()){
-      return null;
-    }
-    // Match URL
-    stream.match(/^[^\s]+/, true);
-    // Check for link title
-    if (stream.peek() === undefined) { // End of line, set flag to check next line
-      state.linkTitle = true;
-    } else { // More content on line, check if link title
-      stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
-    }
-    state.f = state.inline = inlineNormal;
-    return linkhref;
-  }
-
-  var savedInlineRE = [];
-  function inlineRE(endChar) {
-    if (!savedInlineRE[endChar]) {
-      // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
-      endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
-      // Match any non-endChar, escaped character, as well as the closing
-      // endChar.
-      savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')');
-    }
-    return savedInlineRE[endChar];
-  }
-
-  var mode = {
-    startState: function() {
-      return {
-        f: blockNormal,
-
-        prevLineHasContent: false,
-        thisLineHasContent: false,
-
-        block: blockNormal,
-        htmlState: null,
-        indentation: 0,
-
-        inline: inlineNormal,
-        text: handleText,
-
-        formatting: false,
-        linkText: false,
-        linkHref: false,
-        linkTitle: false,
-        em: false,
-        strong: false,
-        header: 0,
-        taskList: false,
-        list: false,
-        listDepth: 0,
-        quote: 0,
-        trailingSpace: 0,
-        trailingSpaceNewLine: false
-      };
-    },
-
-    copyState: function(s) {
-      return {
-        f: s.f,
-
-        prevLineHasContent: s.prevLineHasContent,
-        thisLineHasContent: s.thisLineHasContent,
-
-        block: s.block,
-        htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
-        indentation: s.indentation,
-
-        localMode: s.localMode,
-        localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
-
-        inline: s.inline,
-        text: s.text,
-        formatting: false,
-        linkTitle: s.linkTitle,
-        em: s.em,
-        strong: s.strong,
-        header: s.header,
-        taskList: s.taskList,
-        list: s.list,
-        listDepth: s.listDepth,
-        quote: s.quote,
-        trailingSpace: s.trailingSpace,
-        trailingSpaceNewLine: s.trailingSpaceNewLine,
-        md_inside: s.md_inside
-      };
-    },
-
-    token: function(stream, state) {
-
-      // Reset state.formatting
-      state.formatting = false;
-
-      if (stream.sol()) {
-        var forceBlankLine = !!state.header;
-
-        // Reset state.header
-        state.header = 0;
-
-        if (stream.match(/^\s*$/, true) || forceBlankLine) {
-          state.prevLineHasContent = false;
-          blankLine(state);
-          return forceBlankLine ? this.token(stream, state) : null;
-        } else {
-          state.prevLineHasContent = state.thisLineHasContent;
-          state.thisLineHasContent = true;
-        }
-
-        // Reset state.taskList
-        state.taskList = false;
-
-        // Reset state.code
-        state.code = false;
-
-        // Reset state.trailingSpace
-        state.trailingSpace = 0;
-        state.trailingSpaceNewLine = false;
-
-        state.f = state.block;
-        var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, '    ').length;
-        var difference = Math.floor((indentation - state.indentation) / 4) * 4;
-        if (difference > 4) difference = 4;
-        var adjustedIndentation = state.indentation + difference;
-        state.indentationDiff = adjustedIndentation - state.indentation;
-        state.indentation = adjustedIndentation;
-        if (indentation > 0) return null;
-      }
-      var result = state.f(stream, state);
-      if (stream.start == stream.pos) return this.token(stream, state);
-      else return result;
-    },
-
-    innerMode: function(state) {
-      if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
-      if (state.localState) return {state: state.localState, mode: state.localMode};
-      return {state: state, mode: mode};
-    },
-
-    blankLine: blankLine,
-
-    getType: getType,
-
-    fold: "markdown"
-  };
-  return mode;
-}, "xml");
-
-CodeMirror.defineMIME("text/x-markdown", "markdown");
-
-});
--- a/kallithea/public/codemirror/mode/meta.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.modeInfo = [
-    {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]},
-    {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"},
-    {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]},
-    {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "hpp", "h++"]},
-    {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]},
-    {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"]},
-    {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]},
-    {name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"]},
-    {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"]},
-    {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"},
-    {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]},
-    {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]},
-    {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]},
-    {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]},
-    {name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]},
-    {name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]},
-    {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]},
-    {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]},
-    {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]},
-    {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]},
-    {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]},
-    {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]},
-    {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"]},
-    {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]},
-    {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]},
-    {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"},
-    {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]},
-    {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy"]},
-    {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]},
-    {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]},
-    {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]},
-    {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]},
-    {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"]},
-    {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm"]},
-    {name: "HTTP", mime: "message/http", mode: "http"},
-    {name: "Jade", mime: "text/x-jade", mode: "jade", ext: ["jade"]},
-    {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]},
-    {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"]},
-    {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"],
-     mode: "javascript", ext: ["js"]},
-    {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"]},
-    {name: "JSON-LD", mime: "application/ld+json", mode: "javascript"},
-    {name: "Jinja2", mime: "null", mode: "jinja2"},
-    {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]},
-    {name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin", ext: ["kt"]},
-    {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]},
-    {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"]},
-    {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]},
-    {name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]},
-    {name: "mIRC", mime: "text/mirc", mode: "mirc"},
-    {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"},
-    {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]},
-    {name: "MS SQL", mime: "text/x-mssql", mode: "sql"},
-    {name: "MySQL", mime: "text/x-mysql", mode: "sql"},
-    {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"},
-    {name: "NTriples", mime: "text/n-triples", mode: "ntriples", ext: ["nt"]},
-    {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]},
-    {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]},
-    {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]},
-    {name: "PEG.js", mime: "null", mode: "pegjs"},
-    {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]},
-    {name: "PHP", mime: "application/x-httpd-php", mode: "php", ext: ["php", "php3", "php4", "php5", "phtml"]},
-    {name: "Pig", mime: "text/x-pig", mode: "pig"},
-    {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]},
-    {name: "PLSQL", mime: "text/x-plsql", mode: "sql"},
-    {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"]},
-    {name: "Python", mime: "text/x-python", mode: "python", ext: ["py", "pyw"]},
-    {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]},
-    {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]},
-    {name: "R", mime: "text/x-rsrc", mode: "r", ext: ["r"]},
-    {name: "reStructuredText", mime: "text/x-rst", mode: "rst", ext: ["rst"]},
-    {name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: ["rb"]},
-    {name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: ["rs"]},
-    {name: "Sass", mime: "text/x-sass", mode: "sass", ext: ["sass"]},
-    {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]},
-    {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]},
-    {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]},
-    {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"]},
-    {name: "Sieve", mime: "application/sieve", mode: "sieve"},
-    {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim"},
-    {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]},
-    {name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]},
-    {name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
-    {name: "Solr", mime: "text/x-solr", mode: "solr"},
-    {name: "SPARQL", mime: "application/x-sparql-query", mode: "sparql", ext: ["sparql"]},
-    {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]},
-    {name: "MariaDB", mime: "text/x-mariadb", mode: "sql"},
-    {name: "sTeX", mime: "text/x-stex", mode: "stex"},
-    {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx"]},
-    {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v"]},
-    {name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]},
-    {name: "Textile", mime: "text/x-textile", mode: "textile"},
-    {name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"},
-    {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"},
-    {name: "TOML", mime: "text/x-toml", mode: "toml"},
-    {name: "Tornado", mime: "text/x-tornado", mode: "tornado"},
-    {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]},
-    {name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"]},
-    {name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]},
-    {name: "VBScript", mime: "text/vbscript", mode: "vbscript"},
-    {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]},
-    {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]},
-    {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"]},
-    {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]},
-    {name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml"]},
-    {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}
-  ];
-  // Ensure all modes have a mime property for backwards compatibility
-  for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
-    var info = CodeMirror.modeInfo[i];
-    if (info.mimes) info.mime = info.mimes[0];
-  }
-
-  CodeMirror.findModeByMIME = function(mime) {
-    for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
-      var info = CodeMirror.modeInfo[i];
-      if (info.mime == mime) return info;
-      if (info.mimes) for (var j = 0; j < info.mimes.length; j++)
-        if (info.mimes[j] == mime) return info;
-    }
-  };
-
-  CodeMirror.findModeByExtension = function(ext) {
-    for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
-      var info = CodeMirror.modeInfo[i];
-      if (info.ext) for (var j = 0; j < info.ext.length; j++)
-        if (info.ext[j] == ext) return info;
-    }
-  };
-});
--- a/kallithea/public/codemirror/mode/mirc/mirc.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-//mIRC mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMIME("text/mirc", "mirc");
-CodeMirror.defineMode("mirc", function() {
-  function parseWords(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var specials = parseWords("$! $$ $& $? $+ $abook $abs $active $activecid " +
-                            "$activewid $address $addtok $agent $agentname $agentstat $agentver " +
-                            "$alias $and $anick $ansi2mirc $aop $appactive $appstate $asc $asctime " +
-                            "$asin $atan $avoice $away $awaymsg $awaytime $banmask $base $bfind " +
-                            "$binoff $biton $bnick $bvar $bytes $calc $cb $cd $ceil $chan $chanmodes " +
-                            "$chantypes $chat $chr $cid $clevel $click $cmdbox $cmdline $cnick $color " +
-                            "$com $comcall $comchan $comerr $compact $compress $comval $cos $count " +
-                            "$cr $crc $creq $crlf $ctime $ctimer $ctrlenter $date $day $daylight " +
-                            "$dbuh $dbuw $dccignore $dccport $dde $ddename $debug $decode $decompress " +
-                            "$deltok $devent $dialog $did $didreg $didtok $didwm $disk $dlevel $dll " +
-                            "$dllcall $dname $dns $duration $ebeeps $editbox $emailaddr $encode $error " +
-                            "$eval $event $exist $feof $ferr $fgetc $file $filename $filtered $finddir " +
-                            "$finddirn $findfile $findfilen $findtok $fline $floor $fopen $fread $fserve " +
-                            "$fulladdress $fulldate $fullname $fullscreen $get $getdir $getdot $gettok $gmt " +
-                            "$group $halted $hash $height $hfind $hget $highlight $hnick $hotline " +
-                            "$hotlinepos $ial $ialchan $ibl $idle $iel $ifmatch $ignore $iif $iil " +
-                            "$inelipse $ini $inmidi $inpaste $inpoly $input $inrect $inroundrect " +
-                            "$insong $instok $int $inwave $ip $isalias $isbit $isdde $isdir $isfile " +
-                            "$isid $islower $istok $isupper $keychar $keyrpt $keyval $knick $lactive " +
-                            "$lactivecid $lactivewid $left $len $level $lf $line $lines $link $lock " +
-                            "$lock $locked $log $logstamp $logstampfmt $longfn $longip $lower $ltimer " +
-                            "$maddress $mask $matchkey $matchtok $md5 $me $menu $menubar $menucontext " +
-                            "$menutype $mid $middir $mircdir $mircexe $mircini $mklogfn $mnick $mode " +
-                            "$modefirst $modelast $modespl $mouse $msfile $network $newnick $nick $nofile " +
-                            "$nopath $noqt $not $notags $notify $null $numeric $numok $oline $onpoly " +
-                            "$opnick $or $ord $os $passivedcc $pic $play $pnick $port $portable $portfree " +
-                            "$pos $prefix $prop $protect $puttok $qt $query $rand $r $rawmsg $read $readomo " +
-                            "$readn $regex $regml $regsub $regsubex $remove $remtok $replace $replacex " +
-                            "$reptok $result $rgb $right $round $scid $scon $script $scriptdir $scriptline " +
-                            "$sdir $send $server $serverip $sfile $sha1 $shortfn $show $signal $sin " +
-                            "$site $sline $snick $snicks $snotify $sock $sockbr $sockerr $sockname " +
-                            "$sorttok $sound $sqrt $ssl $sreq $sslready $status $strip $str $stripped " +
-                            "$syle $submenu $switchbar $tan $target $ticks $time $timer $timestamp " +
-                            "$timestampfmt $timezone $tip $titlebar $toolbar $treebar $trust $ulevel " +
-                            "$ulist $upper $uptime $url $usermode $v1 $v2 $var $vcmd $vcmdstat $vcmdver " +
-                            "$version $vnick $vol $wid $width $wildsite $wildtok $window $wrap $xor");
-  var keywords = parseWords("abook ajinvite alias aline ame amsg anick aop auser autojoin avoice " +
-                            "away background ban bcopy beep bread break breplace bset btrunc bunset bwrite " +
-                            "channel clear clearall cline clipboard close cnick color comclose comopen " +
-                            "comreg continue copy creq ctcpreply ctcps dcc dccserver dde ddeserver " +
-                            "debug dec describe dialog did didtok disable disconnect dlevel dline dll " +
-                            "dns dqwindow drawcopy drawdot drawfill drawline drawpic drawrect drawreplace " +
-                            "drawrot drawsave drawscroll drawtext ebeeps echo editbox emailaddr enable " +
-                            "events exit fclose filter findtext finger firewall flash flist flood flush " +
-                            "flushini font fopen fseek fsend fserve fullname fwrite ghide gload gmove " +
-                            "gopts goto gplay gpoint gqreq groups gshow gsize gstop gtalk gunload hadd " +
-                            "halt haltdef hdec hdel help hfree hinc hload hmake hop hsave ial ialclear " +
-                            "ialmark identd if ignore iline inc invite iuser join kick linesep links list " +
-                            "load loadbuf localinfo log mdi me menubar mkdir mnick mode msg nick noop notice " +
-                            "notify omsg onotice part partall pdcc perform play playctrl pop protect pvoice " +
-                            "qme qmsg query queryn quit raw reload remini remote remove rename renwin " +
-                            "reseterror resetidle return rlevel rline rmdir run ruser save savebuf saveini " +
-                            "say scid scon server set showmirc signam sline sockaccept sockclose socklist " +
-                            "socklisten sockmark sockopen sockpause sockread sockrename sockudp sockwrite " +
-                            "sound speak splay sreq strip switchbar timer timestamp titlebar tnick tokenize " +
-                            "toolbar topic tray treebar ulist unload unset unsetall updatenl url uwho " +
-                            "var vcadd vcmd vcrem vol while whois window winhelp write writeint if isalnum " +
-                            "isalpha isaop isavoice isban ischan ishop isignore isin isincs isletter islower " +
-                            "isnotify isnum ison isop isprotect isreg isupper isvoice iswm iswmcs " +
-                            "elseif else goto menu nicklist status title icon size option text edit " +
-                            "button check radio box scroll list combo link tab item");
-  var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch");
-  var isOperatorChar = /[+\-*&%=<>!?^\/\|]/;
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-  function tokenBase(stream, state) {
-    var beforeParams = state.beforeParams;
-    state.beforeParams = false;
-    var ch = stream.next();
-    if (/[\[\]{}\(\),\.]/.test(ch)) {
-      if (ch == "(" && beforeParams) state.inParams = true;
-      else if (ch == ")") state.inParams = false;
-      return null;
-    }
-    else if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    else if (ch == "\\") {
-      stream.eat("\\");
-      stream.eat(/./);
-      return "number";
-    }
-    else if (ch == "/" && stream.eat("*")) {
-      return chain(stream, state, tokenComment);
-    }
-    else if (ch == ";" && stream.match(/ *\( *\(/)) {
-      return chain(stream, state, tokenUnparsed);
-    }
-    else if (ch == ";" && !state.inParams) {
-      stream.skipToEnd();
-      return "comment";
-    }
-    else if (ch == '"') {
-      stream.eat(/"/);
-      return "keyword";
-    }
-    else if (ch == "$") {
-      stream.eatWhile(/[$_a-z0-9A-Z\.:]/);
-      if (specials && specials.propertyIsEnumerable(stream.current().toLowerCase())) {
-        return "keyword";
-      }
-      else {
-        state.beforeParams = true;
-        return "builtin";
-      }
-    }
-    else if (ch == "%") {
-      stream.eatWhile(/[^,^\s^\(^\)]/);
-      state.beforeParams = true;
-      return "string";
-    }
-    else if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    else {
-      stream.eatWhile(/[\w\$_{}]/);
-      var word = stream.current().toLowerCase();
-      if (keywords && keywords.propertyIsEnumerable(word))
-        return "keyword";
-      if (functions && functions.propertyIsEnumerable(word)) {
-        state.beforeParams = true;
-        return "keyword";
-      }
-      return null;
-    }
-  }
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-  function tokenUnparsed(stream, state) {
-    var maybeEnd = 0, ch;
-    while (ch = stream.next()) {
-      if (ch == ";" && maybeEnd == 2) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      if (ch == ")")
-        maybeEnd++;
-      else if (ch != " ")
-        maybeEnd = 0;
-    }
-    return "meta";
-  }
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        beforeParams: false,
-        inParams: false
-      };
-    },
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      return state.tokenize(stream, state);
-    }
-  };
-});
-
-});
--- a/kallithea/public/codemirror/mode/mllike/mllike.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('mllike', function(_config, parserConfig) {
-  var words = {
-    'let': 'keyword',
-    'rec': 'keyword',
-    'in': 'keyword',
-    'of': 'keyword',
-    'and': 'keyword',
-    'if': 'keyword',
-    'then': 'keyword',
-    'else': 'keyword',
-    'for': 'keyword',
-    'to': 'keyword',
-    'while': 'keyword',
-    'do': 'keyword',
-    'done': 'keyword',
-    'fun': 'keyword',
-    'function': 'keyword',
-    'val': 'keyword',
-    'type': 'keyword',
-    'mutable': 'keyword',
-    'match': 'keyword',
-    'with': 'keyword',
-    'try': 'keyword',
-    'open': 'builtin',
-    'ignore': 'builtin',
-    'begin': 'keyword',
-    'end': 'keyword'
-  };
-
-  var extraWords = parserConfig.extraWords || {};
-  for (var prop in extraWords) {
-    if (extraWords.hasOwnProperty(prop)) {
-      words[prop] = parserConfig.extraWords[prop];
-    }
-  }
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-
-    if (ch === '"') {
-      state.tokenize = tokenString;
-      return state.tokenize(stream, state);
-    }
-    if (ch === '(') {
-      if (stream.eat('*')) {
-        state.commentLevel++;
-        state.tokenize = tokenComment;
-        return state.tokenize(stream, state);
-      }
-    }
-    if (ch === '~') {
-      stream.eatWhile(/\w/);
-      return 'variable-2';
-    }
-    if (ch === '`') {
-      stream.eatWhile(/\w/);
-      return 'quote';
-    }
-    if (ch === '/' && parserConfig.slashComments && stream.eat('/')) {
-      stream.skipToEnd();
-      return 'comment';
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\d]/);
-      if (stream.eat('.')) {
-        stream.eatWhile(/[\d]/);
-      }
-      return 'number';
-    }
-    if ( /[+\-*&%=<>!?|]/.test(ch)) {
-      return 'operator';
-    }
-    stream.eatWhile(/\w/);
-    var cur = stream.current();
-    return words[cur] || 'variable';
-  }
-
-  function tokenString(stream, state) {
-    var next, end = false, escaped = false;
-    while ((next = stream.next()) != null) {
-      if (next === '"' && !escaped) {
-        end = true;
-        break;
-      }
-      escaped = !escaped && next === '\\';
-    }
-    if (end && !escaped) {
-      state.tokenize = tokenBase;
-    }
-    return 'string';
-  };
-
-  function tokenComment(stream, state) {
-    var prev, next;
-    while(state.commentLevel > 0 && (next = stream.next()) != null) {
-      if (prev === '(' && next === '*') state.commentLevel++;
-      if (prev === '*' && next === ')') state.commentLevel--;
-      prev = next;
-    }
-    if (state.commentLevel <= 0) {
-      state.tokenize = tokenBase;
-    }
-    return 'comment';
-  }
-
-  return {
-    startState: function() {return {tokenize: tokenBase, commentLevel: 0};},
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      return state.tokenize(stream, state);
-    },
-
-    blockCommentStart: "(*",
-    blockCommentEnd: "*)",
-    lineComment: parserConfig.slashComments ? "//" : null
-  };
-});
-
-CodeMirror.defineMIME('text/x-ocaml', {
-  name: 'mllike',
-  extraWords: {
-    'succ': 'keyword',
-    'trace': 'builtin',
-    'exit': 'builtin',
-    'print_string': 'builtin',
-    'print_endline': 'builtin',
-    'true': 'atom',
-    'false': 'atom',
-    'raise': 'keyword'
-  }
-});
-
-CodeMirror.defineMIME('text/x-fsharp', {
-  name: 'mllike',
-  extraWords: {
-    'abstract': 'keyword',
-    'as': 'keyword',
-    'assert': 'keyword',
-    'base': 'keyword',
-    'class': 'keyword',
-    'default': 'keyword',
-    'delegate': 'keyword',
-    'downcast': 'keyword',
-    'downto': 'keyword',
-    'elif': 'keyword',
-    'exception': 'keyword',
-    'extern': 'keyword',
-    'finally': 'keyword',
-    'global': 'keyword',
-    'inherit': 'keyword',
-    'inline': 'keyword',
-    'interface': 'keyword',
-    'internal': 'keyword',
-    'lazy': 'keyword',
-    'let!': 'keyword',
-    'member' : 'keyword',
-    'module': 'keyword',
-    'namespace': 'keyword',
-    'new': 'keyword',
-    'null': 'keyword',
-    'override': 'keyword',
-    'private': 'keyword',
-    'public': 'keyword',
-    'return': 'keyword',
-    'return!': 'keyword',
-    'select': 'keyword',
-    'static': 'keyword',
-    'struct': 'keyword',
-    'upcast': 'keyword',
-    'use': 'keyword',
-    'use!': 'keyword',
-    'val': 'keyword',
-    'when': 'keyword',
-    'yield': 'keyword',
-    'yield!': 'keyword',
-
-    'List': 'builtin',
-    'Seq': 'builtin',
-    'Map': 'builtin',
-    'Set': 'builtin',
-    'int': 'builtin',
-    'string': 'builtin',
-    'raise': 'builtin',
-    'failwith': 'builtin',
-    'not': 'builtin',
-    'true': 'builtin',
-    'false': 'builtin'
-  },
-  slashComments: true
-});
-
-});
--- a/kallithea/public/codemirror/mode/modelica/modelica.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Modelica support for CodeMirror, copyright (c) by Lennart Ochel
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})
-
-(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.defineMode("modelica", function(config, parserConfig) {
-
-    var indentUnit = config.indentUnit;
-    var keywords = parserConfig.keywords || {};
-    var builtin = parserConfig.builtin || {};
-    var atoms = parserConfig.atoms || {};
-
-    var isSingleOperatorChar = /[;=\(:\),{}.*<>+\-\/^\[\]]/;
-    var isDoubleOperatorChar = /(:=|<=|>=|==|<>|\.\+|\.\-|\.\*|\.\/|\.\^)/;
-    var isDigit = /[0-9]/;
-    var isNonDigit = /[_a-zA-Z]/;
-
-    function tokenLineComment(stream, state) {
-      stream.skipToEnd();
-      state.tokenize = null;
-      return "comment";
-    }
-
-    function tokenBlockComment(stream, state) {
-      var maybeEnd = false, ch;
-      while (ch = stream.next()) {
-        if (maybeEnd && ch == "/") {
-          state.tokenize = null;
-          break;
-        }
-        maybeEnd = (ch == "*");
-      }
-      return "comment";
-    }
-
-    function tokenString(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == '"' && !escaped) {
-          state.tokenize = null;
-          state.sol = false;
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-
-      return "string";
-    }
-
-    function tokenIdent(stream, state) {
-      stream.eatWhile(isDigit);
-      while (stream.eat(isDigit) || stream.eat(isNonDigit)) { }
-
-
-      var cur = stream.current();
-
-      if(state.sol && (cur == "package" || cur == "model" || cur == "when" || cur == "connector")) state.level++;
-      else if(state.sol && cur == "end" && state.level > 0) state.level--;
-
-      state.tokenize = null;
-      state.sol = false;
-
-      if (keywords.propertyIsEnumerable(cur)) return "keyword";
-      else if (builtin.propertyIsEnumerable(cur)) return "builtin";
-      else if (atoms.propertyIsEnumerable(cur)) return "atom";
-      else return "variable";
-    }
-
-    function tokenQIdent(stream, state) {
-      while (stream.eat(/[^']/)) { }
-
-      state.tokenize = null;
-      state.sol = false;
-
-      if(stream.eat("'"))
-        return "variable";
-      else
-        return "error";
-    }
-
-    function tokenUnsignedNuber(stream, state) {
-      stream.eatWhile(isDigit);
-      if (stream.eat('.')) {
-        stream.eatWhile(isDigit);
-      }
-      if (stream.eat('e') || stream.eat('E')) {
-        if (!stream.eat('-'))
-          stream.eat('+');
-        stream.eatWhile(isDigit);
-      }
-
-      state.tokenize = null;
-      state.sol = false;
-      return "number";
-    }
-
-    // Interface
-    return {
-      startState: function() {
-        return {
-          tokenize: null,
-          level: 0,
-          sol: true
-        };
-      },
-
-      token: function(stream, state) {
-        if(state.tokenize != null) {
-          return state.tokenize(stream, state);
-        }
-
-        if(stream.sol()) {
-          state.sol = true;
-        }
-
-        // WHITESPACE
-        if(stream.eatSpace()) {
-          state.tokenize = null;
-          return null;
-        }
-
-        var ch = stream.next();
-
-        // LINECOMMENT
-        if(ch == '/' && stream.eat('/')) {
-          state.tokenize = tokenLineComment;
-        }
-        // BLOCKCOMMENT
-        else if(ch == '/' && stream.eat('*')) {
-          state.tokenize = tokenBlockComment;
-        }
-        // TWO SYMBOL TOKENS
-        else if(isDoubleOperatorChar.test(ch+stream.peek())) {
-          stream.next();
-          state.tokenize = null;
-          return "operator";
-        }
-        // SINGLE SYMBOL TOKENS
-        else if(isSingleOperatorChar.test(ch)) {
-          state.tokenize = null;
-          return "operator";
-        }
-        // IDENT
-        else if(isNonDigit.test(ch)) {
-          state.tokenize = tokenIdent;
-        }
-        // Q-IDENT
-        else if(ch == "'" && stream.peek() && stream.peek() != "'") {
-          state.tokenize = tokenQIdent;
-        }
-        // STRING
-        else if(ch == '"') {
-          state.tokenize = tokenString;
-        }
-        // UNSIGNED_NUBER
-        else if(isDigit.test(ch)) {
-          state.tokenize = tokenUnsignedNuber;
-        }
-        // ERROR
-        else {
-          state.tokenize = null;
-          return "error";
-        }
-
-        return state.tokenize(stream, state);
-      },
-
-      indent: function(state, textAfter) {
-        if (state.tokenize != null) return CodeMirror.Pass;
-
-        var level = state.level;
-        if(/(algorithm)/.test(textAfter)) level--;
-        if(/(equation)/.test(textAfter)) level--;
-        if(/(initial algorithm)/.test(textAfter)) level--;
-        if(/(initial equation)/.test(textAfter)) level--;
-        if(/(end)/.test(textAfter)) level--;
-
-        if(level > 0)
-          return indentUnit*level;
-        else
-          return 0;
-      },
-
-      blockCommentStart: "/*",
-      blockCommentEnd: "*/",
-      lineComment: "//"
-    };
-  });
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i=0; i<words.length; ++i)
-      obj[words[i]] = true;
-    return obj;
-  }
-
-  var modelicaKeywords = "algorithm and annotation assert block break class connect connector constant constrainedby der discrete each else elseif elsewhen encapsulated end enumeration equation expandable extends external false final flow for function if import impure in initial inner input loop model not operator or outer output package parameter partial protected public pure record redeclare replaceable return stream then true type when while within";
-  var modelicaBuiltin = "abs acos actualStream asin atan atan2 cardinality ceil cos cosh delay div edge exp floor getInstanceName homotopy inStream integer log log10 mod pre reinit rem semiLinear sign sin sinh spatialDistribution sqrt tan tanh";
-  var modelicaAtoms = "Real Boolean Integer String";
-
-  function def(mimes, mode) {
-    if (typeof mimes == "string")
-      mimes = [mimes];
-
-    var words = [];
-
-    function add(obj) {
-      if (obj)
-        for (var prop in obj)
-          if (obj.hasOwnProperty(prop))
-            words.push(prop);
-    }
-
-    add(mode.keywords);
-    add(mode.builtin);
-    add(mode.atoms);
-
-    if (words.length) {
-      mode.helperType = mimes[0];
-      CodeMirror.registerHelper("hintWords", mimes[0], words);
-    }
-
-    for (var i=0; i<mimes.length; ++i)
-      CodeMirror.defineMIME(mimes[i], mode);
-  }
-
-  def(["text/x-modelica"], {
-    name: "modelica",
-    keywords: words(modelicaKeywords),
-    builtin: words(modelicaBuiltin),
-    atoms: words(modelicaAtoms)
-  });
-});
--- a/kallithea/public/codemirror/mode/nginx/nginx.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("nginx", function(config) {
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  var keywords = words(
-    /* ngxDirectiveControl */ "break return rewrite set" +
-    /* ngxDirective */ " accept_mutex accept_mutex_delay access_log add_after_body add_before_body add_header addition_types aio alias allow ancient_browser ancient_browser_value auth_basic auth_basic_user_file auth_http auth_http_header auth_http_timeout autoindex autoindex_exact_size autoindex_localtime charset charset_types client_body_buffer_size client_body_in_file_only client_body_in_single_buffer client_body_temp_path client_body_timeout client_header_buffer_size client_header_timeout client_max_body_size connection_pool_size create_full_put_path daemon dav_access dav_methods debug_connection debug_points default_type degradation degrade deny devpoll_changes devpoll_events directio directio_alignment empty_gif env epoll_events error_log eventport_events expires fastcgi_bind fastcgi_buffer_size fastcgi_buffers fastcgi_busy_buffers_size fastcgi_cache fastcgi_cache_key fastcgi_cache_methods fastcgi_cache_min_uses fastcgi_cache_path fastcgi_cache_use_stale fastcgi_cache_valid fastcgi_catch_stderr fastcgi_connect_timeout fastcgi_hide_header fastcgi_ignore_client_abort fastcgi_ignore_headers fastcgi_index fastcgi_intercept_errors fastcgi_max_temp_file_size fastcgi_next_upstream fastcgi_param fastcgi_pass_header fastcgi_pass_request_body fastcgi_pass_request_headers fastcgi_read_timeout fastcgi_send_lowat fastcgi_send_timeout fastcgi_split_path_info fastcgi_store fastcgi_store_access fastcgi_temp_file_write_size fastcgi_temp_path fastcgi_upstream_fail_timeout fastcgi_upstream_max_fails flv geoip_city geoip_country google_perftools_profiles gzip gzip_buffers gzip_comp_level gzip_disable gzip_hash gzip_http_version gzip_min_length gzip_no_buffer gzip_proxied gzip_static gzip_types gzip_vary gzip_window if_modified_since ignore_invalid_headers image_filter image_filter_buffer image_filter_jpeg_quality image_filter_transparency imap_auth imap_capabilities imap_client_buffer index ip_hash keepalive_requests keepalive_timeout kqueue_changes kqueue_events large_client_header_buffers limit_conn limit_conn_log_level limit_rate limit_rate_after limit_req limit_req_log_level limit_req_zone limit_zone lingering_time lingering_timeout lock_file log_format log_not_found log_subrequest map_hash_bucket_size map_hash_max_size master_process memcached_bind memcached_buffer_size memcached_connect_timeout memcached_next_upstream memcached_read_timeout memcached_send_timeout memcached_upstream_fail_timeout memcached_upstream_max_fails merge_slashes min_delete_depth modern_browser modern_browser_value msie_padding msie_refresh multi_accept open_file_cache open_file_cache_errors open_file_cache_events open_file_cache_min_uses open_file_cache_valid open_log_file_cache output_buffers override_charset perl perl_modules perl_require perl_set pid pop3_auth pop3_capabilities port_in_redirect postpone_gzipping postpone_output protocol proxy proxy_bind proxy_buffer proxy_buffer_size proxy_buffering proxy_buffers proxy_busy_buffers_size proxy_cache proxy_cache_key proxy_cache_methods proxy_cache_min_uses proxy_cache_path proxy_cache_use_stale proxy_cache_valid proxy_connect_timeout proxy_headers_hash_bucket_size proxy_headers_hash_max_size proxy_hide_header proxy_ignore_client_abort proxy_ignore_headers proxy_intercept_errors proxy_max_temp_file_size proxy_method proxy_next_upstream proxy_pass_error_message proxy_pass_header proxy_pass_request_body proxy_pass_request_headers proxy_read_timeout proxy_redirect proxy_send_lowat proxy_send_timeout proxy_set_body proxy_set_header proxy_ssl_session_reuse proxy_store proxy_store_access proxy_temp_file_write_size proxy_temp_path proxy_timeout proxy_upstream_fail_timeout proxy_upstream_max_fails random_index read_ahead real_ip_header recursive_error_pages request_pool_size reset_timedout_connection resolver resolver_timeout rewrite_log rtsig_overflow_events rtsig_overflow_test rtsig_overflow_threshold rtsig_signo satisfy secure_link_secret send_lowat send_timeout sendfile sendfile_max_chunk server_name_in_redirect server_names_hash_bucket_size server_names_hash_max_size server_tokens set_real_ip_from smtp_auth smtp_capabilities smtp_client_buffer smtp_greeting_delay so_keepalive source_charset ssi ssi_ignore_recycled_buffers ssi_min_file_chunk ssi_silent_errors ssi_types ssi_value_length ssl ssl_certificate ssl_certificate_key ssl_ciphers ssl_client_certificate ssl_crl ssl_dhparam ssl_engine ssl_prefer_server_ciphers ssl_protocols ssl_session_cache ssl_session_timeout ssl_verify_client ssl_verify_depth starttls stub_status sub_filter sub_filter_once sub_filter_types tcp_nodelay tcp_nopush thread_stack_size timeout timer_resolution types_hash_bucket_size types_hash_max_size underscores_in_headers uninitialized_variable_warn use user userid userid_domain userid_expires userid_mark userid_name userid_p3p userid_path userid_service valid_referers variables_hash_bucket_size variables_hash_max_size worker_connections worker_cpu_affinity worker_priority worker_processes worker_rlimit_core worker_rlimit_nofile worker_rlimit_sigpending worker_threads working_directory xclient xml_entities xslt_stylesheet xslt_typesdrew@li229-23"
-    );
-
-  var keywords_block = words(
-    /* ngxDirectiveBlock */ "http mail events server types location upstream charset_map limit_except if geo map"
-    );
-
-  var keywords_important = words(
-    /* ngxDirectiveImportant */ "include root server server_name listen internal proxy_pass memcached_pass fastcgi_pass try_files"
-    );
-
-  var indentUnit = config.indentUnit, type;
-  function ret(style, tp) {type = tp; return style;}
-
-  function tokenBase(stream, state) {
-
-
-    stream.eatWhile(/[\w\$_]/);
-
-    var cur = stream.current();
-
-
-    if (keywords.propertyIsEnumerable(cur)) {
-      return "keyword";
-    }
-    else if (keywords_block.propertyIsEnumerable(cur)) {
-      return "variable-2";
-    }
-    else if (keywords_important.propertyIsEnumerable(cur)) {
-      return "string-2";
-    }
-    /**/
-
-    var ch = stream.next();
-    if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("meta", stream.current());}
-    else if (ch == "/" && stream.eat("*")) {
-      state.tokenize = tokenCComment;
-      return tokenCComment(stream, state);
-    }
-    else if (ch == "<" && stream.eat("!")) {
-      state.tokenize = tokenSGMLComment;
-      return tokenSGMLComment(stream, state);
-    }
-    else if (ch == "=") ret(null, "compare");
-    else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare");
-    else if (ch == "\"" || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    else if (ch == "#") {
-      stream.skipToEnd();
-      return ret("comment", "comment");
-    }
-    else if (ch == "!") {
-      stream.match(/^\s*\w*/);
-      return ret("keyword", "important");
-    }
-    else if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w.%]/);
-      return ret("number", "unit");
-    }
-    else if (/[,.+>*\/]/.test(ch)) {
-      return ret(null, "select-op");
-    }
-    else if (/[;{}:\[\]]/.test(ch)) {
-      return ret(null, ch);
-    }
-    else {
-      stream.eatWhile(/[\w\\\-]/);
-      return ret("variable", "variable");
-    }
-  }
-
-  function tokenCComment(stream, state) {
-    var maybeEnd = false, ch;
-    while ((ch = stream.next()) != null) {
-      if (maybeEnd && ch == "/") {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenSGMLComment(stream, state) {
-    var dashes = 0, ch;
-    while ((ch = stream.next()) != null) {
-      if (dashes >= 2 && ch == ">") {
-        state.tokenize = tokenBase;
-        break;
-      }
-      dashes = (ch == "-") ? dashes + 1 : 0;
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped)
-          break;
-        escaped = !escaped && ch == "\\";
-      }
-      if (!escaped) state.tokenize = tokenBase;
-      return ret("string", "string");
-    };
-  }
-
-  return {
-    startState: function(base) {
-      return {tokenize: tokenBase,
-              baseIndent: base || 0,
-              stack: []};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      type = null;
-      var style = state.tokenize(stream, state);
-
-      var context = state.stack[state.stack.length-1];
-      if (type == "hash" && context == "rule") style = "atom";
-      else if (style == "variable") {
-        if (context == "rule") style = "number";
-        else if (!context || context == "@media{") style = "tag";
-      }
-
-      if (context == "rule" && /^[\{\};]$/.test(type))
-        state.stack.pop();
-      if (type == "{") {
-        if (context == "@media") state.stack[state.stack.length-1] = "@media{";
-        else state.stack.push("{");
-      }
-      else if (type == "}") state.stack.pop();
-      else if (type == "@media") state.stack.push("@media");
-      else if (context == "{" && type != "comment") state.stack.push("rule");
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var n = state.stack.length;
-      if (/^\}/.test(textAfter))
-        n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
-      return state.baseIndent + n * indentUnit;
-    },
-
-    electricChars: "}"
-  };
-});
-
-CodeMirror.defineMIME("text/nginx", "text/x-nginx-conf");
-
-});
--- a/kallithea/public/codemirror/mode/ntriples/ntriples.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**********************************************************
-* This script provides syntax highlighting support for
-* the Ntriples format.
-* Ntriples format specification:
-*     http://www.w3.org/TR/rdf-testcases/#ntriples
-***********************************************************/
-
-/*
-    The following expression defines the defined ASF grammar transitions.
-
-    pre_subject ->
-        {
-        ( writing_subject_uri | writing_bnode_uri )
-            -> pre_predicate
-                -> writing_predicate_uri
-                    -> pre_object
-                        -> writing_object_uri | writing_object_bnode |
-                          (
-                            writing_object_literal
-                                -> writing_literal_lang | writing_literal_type
-                          )
-                            -> post_object
-                                -> BEGIN
-         } otherwise {
-             -> ERROR
-         }
-*/
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("ntriples", function() {
-
-  var Location = {
-    PRE_SUBJECT         : 0,
-    WRITING_SUB_URI     : 1,
-    WRITING_BNODE_URI   : 2,
-    PRE_PRED            : 3,
-    WRITING_PRED_URI    : 4,
-    PRE_OBJ             : 5,
-    WRITING_OBJ_URI     : 6,
-    WRITING_OBJ_BNODE   : 7,
-    WRITING_OBJ_LITERAL : 8,
-    WRITING_LIT_LANG    : 9,
-    WRITING_LIT_TYPE    : 10,
-    POST_OBJ            : 11,
-    ERROR               : 12
-  };
-  function transitState(currState, c) {
-    var currLocation = currState.location;
-    var ret;
-
-    // Opening.
-    if     (currLocation == Location.PRE_SUBJECT && c == '<') ret = Location.WRITING_SUB_URI;
-    else if(currLocation == Location.PRE_SUBJECT && c == '_') ret = Location.WRITING_BNODE_URI;
-    else if(currLocation == Location.PRE_PRED    && c == '<') ret = Location.WRITING_PRED_URI;
-    else if(currLocation == Location.PRE_OBJ     && c == '<') ret = Location.WRITING_OBJ_URI;
-    else if(currLocation == Location.PRE_OBJ     && c == '_') ret = Location.WRITING_OBJ_BNODE;
-    else if(currLocation == Location.PRE_OBJ     && c == '"') ret = Location.WRITING_OBJ_LITERAL;
-
-    // Closing.
-    else if(currLocation == Location.WRITING_SUB_URI     && c == '>') ret = Location.PRE_PRED;
-    else if(currLocation == Location.WRITING_BNODE_URI   && c == ' ') ret = Location.PRE_PRED;
-    else if(currLocation == Location.WRITING_PRED_URI    && c == '>') ret = Location.PRE_OBJ;
-    else if(currLocation == Location.WRITING_OBJ_URI     && c == '>') ret = Location.POST_OBJ;
-    else if(currLocation == Location.WRITING_OBJ_BNODE   && c == ' ') ret = Location.POST_OBJ;
-    else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '"') ret = Location.POST_OBJ;
-    else if(currLocation == Location.WRITING_LIT_LANG && c == ' ') ret = Location.POST_OBJ;
-    else if(currLocation == Location.WRITING_LIT_TYPE && c == '>') ret = Location.POST_OBJ;
-
-    // Closing typed and language literal.
-    else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '@') ret = Location.WRITING_LIT_LANG;
-    else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '^') ret = Location.WRITING_LIT_TYPE;
-
-    // Spaces.
-    else if( c == ' ' &&
-             (
-               currLocation == Location.PRE_SUBJECT ||
-               currLocation == Location.PRE_PRED    ||
-               currLocation == Location.PRE_OBJ     ||
-               currLocation == Location.POST_OBJ
-             )
-           ) ret = currLocation;
-
-    // Reset.
-    else if(currLocation == Location.POST_OBJ && c == '.') ret = Location.PRE_SUBJECT;
-
-    // Error
-    else ret = Location.ERROR;
-
-    currState.location=ret;
-  }
-
-  return {
-    startState: function() {
-       return {
-           location : Location.PRE_SUBJECT,
-           uris     : [],
-           anchors  : [],
-           bnodes   : [],
-           langs    : [],
-           types    : []
-       };
-    },
-    token: function(stream, state) {
-      var ch = stream.next();
-      if(ch == '<') {
-         transitState(state, ch);
-         var parsedURI = '';
-         stream.eatWhile( function(c) { if( c != '#' && c != '>' ) { parsedURI += c; return true; } return false;} );
-         state.uris.push(parsedURI);
-         if( stream.match('#', false) ) return 'variable';
-         stream.next();
-         transitState(state, '>');
-         return 'variable';
-      }
-      if(ch == '#') {
-        var parsedAnchor = '';
-        stream.eatWhile(function(c) { if(c != '>' && c != ' ') { parsedAnchor+= c; return true; } return false;});
-        state.anchors.push(parsedAnchor);
-        return 'variable-2';
-      }
-      if(ch == '>') {
-          transitState(state, '>');
-          return 'variable';
-      }
-      if(ch == '_') {
-          transitState(state, ch);
-          var parsedBNode = '';
-          stream.eatWhile(function(c) { if( c != ' ' ) { parsedBNode += c; return true; } return false;});
-          state.bnodes.push(parsedBNode);
-          stream.next();
-          transitState(state, ' ');
-          return 'builtin';
-      }
-      if(ch == '"') {
-          transitState(state, ch);
-          stream.eatWhile( function(c) { return c != '"'; } );
-          stream.next();
-          if( stream.peek() != '@' && stream.peek() != '^' ) {
-              transitState(state, '"');
-          }
-          return 'string';
-      }
-      if( ch == '@' ) {
-          transitState(state, '@');
-          var parsedLang = '';
-          stream.eatWhile(function(c) { if( c != ' ' ) { parsedLang += c; return true; } return false;});
-          state.langs.push(parsedLang);
-          stream.next();
-          transitState(state, ' ');
-          return 'string-2';
-      }
-      if( ch == '^' ) {
-          stream.next();
-          transitState(state, '^');
-          var parsedType = '';
-          stream.eatWhile(function(c) { if( c != '>' ) { parsedType += c; return true; } return false;} );
-          state.types.push(parsedType);
-          stream.next();
-          transitState(state, '>');
-          return 'variable';
-      }
-      if( ch == ' ' ) {
-          transitState(state, ch);
-      }
-      if( ch == '.' ) {
-          transitState(state, ch);
-      }
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/n-triples", "ntriples");
-
-});
--- a/kallithea/public/codemirror/mode/octave/octave.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("octave", function() {
-  function wordRegexp(words) {
-    return new RegExp("^((" + words.join(")|(") + "))\\b");
-  }
-
-  var singleOperators = new RegExp("^[\\+\\-\\*/&|\\^~<>!@'\\\\]");
-  var singleDelimiters = new RegExp('^[\\(\\[\\{\\},:=;]');
-  var doubleOperators = new RegExp("^((==)|(~=)|(<=)|(>=)|(<<)|(>>)|(\\.[\\+\\-\\*/\\^\\\\]))");
-  var doubleDelimiters = new RegExp("^((!=)|(\\+=)|(\\-=)|(\\*=)|(/=)|(&=)|(\\|=)|(\\^=))");
-  var tripleDelimiters = new RegExp("^((>>=)|(<<=))");
-  var expressionEnd = new RegExp("^[\\]\\)]");
-  var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*");
-
-  var builtins = wordRegexp([
-    'error', 'eval', 'function', 'abs', 'acos', 'atan', 'asin', 'cos',
-    'cosh', 'exp', 'log', 'prod', 'sum', 'log10', 'max', 'min', 'sign', 'sin', 'sinh',
-    'sqrt', 'tan', 'reshape', 'break', 'zeros', 'default', 'margin', 'round', 'ones',
-    'rand', 'syn', 'ceil', 'floor', 'size', 'clear', 'zeros', 'eye', 'mean', 'std', 'cov',
-    'det', 'eig', 'inv', 'norm', 'rank', 'trace', 'expm', 'logm', 'sqrtm', 'linspace', 'plot',
-    'title', 'xlabel', 'ylabel', 'legend', 'text', 'grid', 'meshgrid', 'mesh', 'num2str',
-    'fft', 'ifft', 'arrayfun', 'cellfun', 'input', 'fliplr', 'flipud', 'ismember'
-  ]);
-
-  var keywords = wordRegexp([
-    'return', 'case', 'switch', 'else', 'elseif', 'end', 'endif', 'endfunction',
-    'if', 'otherwise', 'do', 'for', 'while', 'try', 'catch', 'classdef', 'properties', 'events',
-    'methods', 'global', 'persistent', 'endfor', 'endwhile', 'printf', 'sprintf', 'disp', 'until',
-    'continue', 'pkg'
-  ]);
-
-
-  // tokenizers
-  function tokenTranspose(stream, state) {
-    if (!stream.sol() && stream.peek() === '\'') {
-      stream.next();
-      state.tokenize = tokenBase;
-      return 'operator';
-    }
-    state.tokenize = tokenBase;
-    return tokenBase(stream, state);
-  }
-
-
-  function tokenComment(stream, state) {
-    if (stream.match(/^.*%}/)) {
-      state.tokenize = tokenBase;
-      return 'comment';
-    };
-    stream.skipToEnd();
-    return 'comment';
-  }
-
-  function tokenBase(stream, state) {
-    // whitespaces
-    if (stream.eatSpace()) return null;
-
-    // Handle one line Comments
-    if (stream.match('%{')){
-      state.tokenize = tokenComment;
-      stream.skipToEnd();
-      return 'comment';
-    }
-
-    if (stream.match(/^[%#]/)){
-      stream.skipToEnd();
-      return 'comment';
-    }
-
-    // Handle Number Literals
-    if (stream.match(/^[0-9\.+-]/, false)) {
-      if (stream.match(/^[+-]?0x[0-9a-fA-F]+[ij]?/)) {
-        stream.tokenize = tokenBase;
-        return 'number'; };
-      if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; };
-      if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; };
-    }
-    if (stream.match(wordRegexp(['nan','NaN','inf','Inf']))) { return 'number'; };
-
-    // Handle Strings
-    if (stream.match(/^"([^"]|(""))*"/)) { return 'string'; } ;
-    if (stream.match(/^'([^']|(''))*'/)) { return 'string'; } ;
-
-    // Handle words
-    if (stream.match(keywords)) { return 'keyword'; } ;
-    if (stream.match(builtins)) { return 'builtin'; } ;
-    if (stream.match(identifiers)) { return 'variable'; } ;
-
-    if (stream.match(singleOperators) || stream.match(doubleOperators)) { return 'operator'; };
-    if (stream.match(singleDelimiters) || stream.match(doubleDelimiters) || stream.match(tripleDelimiters)) { return null; };
-
-    if (stream.match(expressionEnd)) {
-      state.tokenize = tokenTranspose;
-      return null;
-    };
-
-
-    // Handle non-detected items
-    stream.next();
-    return 'error';
-  };
-
-
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase
-      };
-    },
-
-    token: function(stream, state) {
-      var style = state.tokenize(stream, state);
-      if (style === 'number' || style === 'variable'){
-        state.tokenize = tokenTranspose;
-      }
-      return style;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-octave", "octave");
-
-});
--- a/kallithea/public/codemirror/mode/pascal/pascal.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("pascal", function() {
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var keywords = words("and array begin case const div do downto else end file for forward integer " +
-                       "boolean char function goto if in label mod nil not of or packed procedure " +
-                       "program record repeat set string then to type until var while with");
-  var atoms = {"null": true};
-
-  var isOperatorChar = /[+\-*&%=<>!?|\/]/;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == "#" && state.startOfLine) {
-      stream.skipToEnd();
-      return "meta";
-    }
-    if (ch == '"' || ch == "'") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    if (ch == "(" && stream.eat("*")) {
-      state.tokenize = tokenComment;
-      return tokenComment(stream, state);
-    }
-    if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
-      return null;
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return "number";
-    }
-    if (ch == "/") {
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-    }
-    if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return "operator";
-    }
-    stream.eatWhile(/[\w\$_]/);
-    var cur = stream.current();
-    if (keywords.propertyIsEnumerable(cur)) return "keyword";
-    if (atoms.propertyIsEnumerable(cur)) return "atom";
-    return "variable";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !escaped) state.tokenize = null;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == ")" && maybeEnd) {
-        state.tokenize = null;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  // Interface
-
-  return {
-    startState: function() {
-      return {tokenize: null};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta") return style;
-      return style;
-    },
-
-    electricChars: "{}"
-  };
-});
-
-CodeMirror.defineMIME("text/x-pascal", "pascal");
-
-});
--- a/kallithea/public/codemirror/mode/pegjs/pegjs.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../javascript/javascript"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../javascript/javascript"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("pegjs", function (config) {
-  var jsMode = CodeMirror.getMode(config, "javascript");
-
-  function identifier(stream) {
-    return stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/);
-  }
-
-  return {
-    startState: function () {
-      return {
-        inString: false,
-        stringType: null,
-        inComment: false,
-        inChracterClass: false,
-        braced: 0,
-        lhs: true,
-        localState: null
-      };
-    },
-    token: function (stream, state) {
-      if (stream)
-
-      //check for state changes
-      if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) {
-        state.stringType = stream.peek();
-        stream.next(); // Skip quote
-        state.inString = true; // Update state
-      }
-      if (!state.inString && !state.inComment && stream.match(/^\/\*/)) {
-        state.inComment = true;
-      }
-
-      //return state
-      if (state.inString) {
-        while (state.inString && !stream.eol()) {
-          if (stream.peek() === state.stringType) {
-            stream.next(); // Skip quote
-            state.inString = false; // Clear flag
-          } else if (stream.peek() === '\\') {
-            stream.next();
-            stream.next();
-          } else {
-            stream.match(/^.[^\\\"\']*/);
-          }
-        }
-        return state.lhs ? "property string" : "string"; // Token style
-      } else if (state.inComment) {
-        while (state.inComment && !stream.eol()) {
-          if (stream.match(/\*\//)) {
-            state.inComment = false; // Clear flag
-          } else {
-            stream.match(/^.[^\*]*/);
-          }
-        }
-        return "comment";
-      } else if (state.inChracterClass) {
-          while (state.inChracterClass && !stream.eol()) {
-            if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) {
-              state.inChracterClass = false;
-            }
-          }
-      } else if (stream.peek() === '[') {
-        stream.next();
-        state.inChracterClass = true;
-        return 'bracket';
-      } else if (stream.match(/^\/\//)) {
-        stream.skipToEnd();
-        return "comment";
-      } else if (state.braced || stream.peek() === '{') {
-        if (state.localState === null) {
-          state.localState = jsMode.startState();
-        }
-        var token = jsMode.token(stream, state.localState);
-        var text = stream.current();
-        if (!token) {
-          for (var i = 0; i < text.length; i++) {
-            if (text[i] === '{') {
-              state.braced++;
-            } else if (text[i] === '}') {
-              state.braced--;
-            }
-          };
-        }
-        return token;
-      } else if (identifier(stream)) {
-        if (stream.peek() === ':') {
-          return 'variable';
-        }
-        return 'variable-2';
-      } else if (['[', ']', '(', ')'].indexOf(stream.peek()) != -1) {
-        stream.next();
-        return 'bracket';
-      } else if (!stream.eatSpace()) {
-        stream.next();
-      }
-      return null;
-    }
-  };
-}, "javascript");
-
-});
--- a/kallithea/public/codemirror/mode/perl/perl.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,832 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// CodeMirror2 mode/perl/perl.js (text/x-perl) beta 0.10 (2011-11-08)
-// This is a part of CodeMirror from https://github.com/sabaca/CodeMirror_mode_perl (mail@sabaca.com)
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("perl",function(){
-        // http://perldoc.perl.org
-        var PERL={                                      //   null - magic touch
-                                                        //   1 - keyword
-                                                        //   2 - def
-                                                        //   3 - atom
-                                                        //   4 - operator
-                                                        //   5 - variable-2 (predefined)
-                                                        //   [x,y] - x=1,2,3; y=must be defined if x{...}
-                                                //      PERL operators
-                '->'                            :   4,
-                '++'                            :   4,
-                '--'                            :   4,
-                '**'                            :   4,
-                                                        //   ! ~ \ and unary + and -
-                '=~'                            :   4,
-                '!~'                            :   4,
-                '*'                             :   4,
-                '/'                             :   4,
-                '%'                             :   4,
-                'x'                             :   4,
-                '+'                             :   4,
-                '-'                             :   4,
-                '.'                             :   4,
-                '<<'                            :   4,
-                '>>'                            :   4,
-                                                        //   named unary operators
-                '<'                             :   4,
-                '>'                             :   4,
-                '<='                            :   4,
-                '>='                            :   4,
-                'lt'                            :   4,
-                'gt'                            :   4,
-                'le'                            :   4,
-                'ge'                            :   4,
-                '=='                            :   4,
-                '!='                            :   4,
-                '<=>'                           :   4,
-                'eq'                            :   4,
-                'ne'                            :   4,
-                'cmp'                           :   4,
-                '~~'                            :   4,
-                '&'                             :   4,
-                '|'                             :   4,
-                '^'                             :   4,
-                '&&'                            :   4,
-                '||'                            :   4,
-                '//'                            :   4,
-                '..'                            :   4,
-                '...'                           :   4,
-                '?'                             :   4,
-                ':'                             :   4,
-                '='                             :   4,
-                '+='                            :   4,
-                '-='                            :   4,
-                '*='                            :   4,  //   etc. ???
-                ','                             :   4,
-                '=>'                            :   4,
-                '::'                            :   4,
-                                                        //   list operators (rightward)
-                'not'                           :   4,
-                'and'                           :   4,
-                'or'                            :   4,
-                'xor'                           :   4,
-                                                //      PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;)
-                'BEGIN'                         :   [5,1],
-                'END'                           :   [5,1],
-                'PRINT'                         :   [5,1],
-                'PRINTF'                        :   [5,1],
-                'GETC'                          :   [5,1],
-                'READ'                          :   [5,1],
-                'READLINE'                      :   [5,1],
-                'DESTROY'                       :   [5,1],
-                'TIE'                           :   [5,1],
-                'TIEHANDLE'                     :   [5,1],
-                'UNTIE'                         :   [5,1],
-                'STDIN'                         :    5,
-                'STDIN_TOP'                     :    5,
-                'STDOUT'                        :    5,
-                'STDOUT_TOP'                    :    5,
-                'STDERR'                        :    5,
-                'STDERR_TOP'                    :    5,
-                '$ARG'                          :    5,
-                '$_'                            :    5,
-                '@ARG'                          :    5,
-                '@_'                            :    5,
-                '$LIST_SEPARATOR'               :    5,
-                '$"'                            :    5,
-                '$PROCESS_ID'                   :    5,
-                '$PID'                          :    5,
-                '$$'                            :    5,
-                '$REAL_GROUP_ID'                :    5,
-                '$GID'                          :    5,
-                '$('                            :    5,
-                '$EFFECTIVE_GROUP_ID'           :    5,
-                '$EGID'                         :    5,
-                '$)'                            :    5,
-                '$PROGRAM_NAME'                 :    5,
-                '$0'                            :    5,
-                '$SUBSCRIPT_SEPARATOR'          :    5,
-                '$SUBSEP'                       :    5,
-                '$;'                            :    5,
-                '$REAL_USER_ID'                 :    5,
-                '$UID'                          :    5,
-                '$<'                            :    5,
-                '$EFFECTIVE_USER_ID'            :    5,
-                '$EUID'                         :    5,
-                '$>'                            :    5,
-                '$a'                            :    5,
-                '$b'                            :    5,
-                '$COMPILING'                    :    5,
-                '$^C'                           :    5,
-                '$DEBUGGING'                    :    5,
-                '$^D'                           :    5,
-                '${^ENCODING}'                  :    5,
-                '$ENV'                          :    5,
-                '%ENV'                          :    5,
-                '$SYSTEM_FD_MAX'                :    5,
-                '$^F'                           :    5,
-                '@F'                            :    5,
-                '${^GLOBAL_PHASE}'              :    5,
-                '$^H'                           :    5,
-                '%^H'                           :    5,
-                '@INC'                          :    5,
-                '%INC'                          :    5,
-                '$INPLACE_EDIT'                 :    5,
-                '$^I'                           :    5,
-                '$^M'                           :    5,
-                '$OSNAME'                       :    5,
-                '$^O'                           :    5,
-                '${^OPEN}'                      :    5,
-                '$PERLDB'                       :    5,
-                '$^P'                           :    5,
-                '$SIG'                          :    5,
-                '%SIG'                          :    5,
-                '$BASETIME'                     :    5,
-                '$^T'                           :    5,
-                '${^TAINT}'                     :    5,
-                '${^UNICODE}'                   :    5,
-                '${^UTF8CACHE}'                 :    5,
-                '${^UTF8LOCALE}'                :    5,
-                '$PERL_VERSION'                 :    5,
-                '$^V'                           :    5,
-                '${^WIN32_SLOPPY_STAT}'         :    5,
-                '$EXECUTABLE_NAME'              :    5,
-                '$^X'                           :    5,
-                '$1'                            :    5, // - regexp $1, $2...
-                '$MATCH'                        :    5,
-                '$&'                            :    5,
-                '${^MATCH}'                     :    5,
-                '$PREMATCH'                     :    5,
-                '$`'                            :    5,
-                '${^PREMATCH}'                  :    5,
-                '$POSTMATCH'                    :    5,
-                "$'"                            :    5,
-                '${^POSTMATCH}'                 :    5,
-                '$LAST_PAREN_MATCH'             :    5,
-                '$+'                            :    5,
-                '$LAST_SUBMATCH_RESULT'         :    5,
-                '$^N'                           :    5,
-                '@LAST_MATCH_END'               :    5,
-                '@+'                            :    5,
-                '%LAST_PAREN_MATCH'             :    5,
-                '%+'                            :    5,
-                '@LAST_MATCH_START'             :    5,
-                '@-'                            :    5,
-                '%LAST_MATCH_START'             :    5,
-                '%-'                            :    5,
-                '$LAST_REGEXP_CODE_RESULT'      :    5,
-                '$^R'                           :    5,
-                '${^RE_DEBUG_FLAGS}'            :    5,
-                '${^RE_TRIE_MAXBUF}'            :    5,
-                '$ARGV'                         :    5,
-                '@ARGV'                         :    5,
-                'ARGV'                          :    5,
-                'ARGVOUT'                       :    5,
-                '$OUTPUT_FIELD_SEPARATOR'       :    5,
-                '$OFS'                          :    5,
-                '$,'                            :    5,
-                '$INPUT_LINE_NUMBER'            :    5,
-                '$NR'                           :    5,
-                '$.'                            :    5,
-                '$INPUT_RECORD_SEPARATOR'       :    5,
-                '$RS'                           :    5,
-                '$/'                            :    5,
-                '$OUTPUT_RECORD_SEPARATOR'      :    5,
-                '$ORS'                          :    5,
-                '$\\'                           :    5,
-                '$OUTPUT_AUTOFLUSH'             :    5,
-                '$|'                            :    5,
-                '$ACCUMULATOR'                  :    5,
-                '$^A'                           :    5,
-                '$FORMAT_FORMFEED'              :    5,
-                '$^L'                           :    5,
-                '$FORMAT_PAGE_NUMBER'           :    5,
-                '$%'                            :    5,
-                '$FORMAT_LINES_LEFT'            :    5,
-                '$-'                            :    5,
-                '$FORMAT_LINE_BREAK_CHARACTERS' :    5,
-                '$:'                            :    5,
-                '$FORMAT_LINES_PER_PAGE'        :    5,
-                '$='                            :    5,
-                '$FORMAT_TOP_NAME'              :    5,
-                '$^'                            :    5,
-                '$FORMAT_NAME'                  :    5,
-                '$~'                            :    5,
-                '${^CHILD_ERROR_NATIVE}'        :    5,
-                '$EXTENDED_OS_ERROR'            :    5,
-                '$^E'                           :    5,
-                '$EXCEPTIONS_BEING_CAUGHT'      :    5,
-                '$^S'                           :    5,
-                '$WARNING'                      :    5,
-                '$^W'                           :    5,
-                '${^WARNING_BITS}'              :    5,
-                '$OS_ERROR'                     :    5,
-                '$ERRNO'                        :    5,
-                '$!'                            :    5,
-                '%OS_ERROR'                     :    5,
-                '%ERRNO'                        :    5,
-                '%!'                            :    5,
-                '$CHILD_ERROR'                  :    5,
-                '$?'                            :    5,
-                '$EVAL_ERROR'                   :    5,
-                '$@'                            :    5,
-                '$OFMT'                         :    5,
-                '$#'                            :    5,
-                '$*'                            :    5,
-                '$ARRAY_BASE'                   :    5,
-                '$['                            :    5,
-                '$OLD_PERL_VERSION'             :    5,
-                '$]'                            :    5,
-                                                //      PERL blocks
-                'if'                            :[1,1],
-                elsif                           :[1,1],
-                'else'                          :[1,1],
-                'while'                         :[1,1],
-                unless                          :[1,1],
-                'for'                           :[1,1],
-                foreach                         :[1,1],
-                                                //      PERL functions
-                'abs'                           :1,     // - absolute value function
-                accept                          :1,     // - accept an incoming socket connect
-                alarm                           :1,     // - schedule a SIGALRM
-                'atan2'                         :1,     // - arctangent of Y/X in the range -PI to PI
-                bind                            :1,     // - binds an address to a socket
-                binmode                         :1,     // - prepare binary files for I/O
-                bless                           :1,     // - create an object
-                bootstrap                       :1,     //
-                'break'                         :1,     // - break out of a "given" block
-                caller                          :1,     // - get context of the current subroutine call
-                chdir                           :1,     // - change your current working directory
-                chmod                           :1,     // - changes the permissions on a list of files
-                chomp                           :1,     // - remove a trailing record separator from a string
-                chop                            :1,     // - remove the last character from a string
-                chown                           :1,     // - change the owership on a list of files
-                chr                             :1,     // - get character this number represents
-                chroot                          :1,     // - make directory new root for path lookups
-                close                           :1,     // - close file (or pipe or socket) handle
-                closedir                        :1,     // - close directory handle
-                connect                         :1,     // - connect to a remote socket
-                'continue'                      :[1,1], // - optional trailing block in a while or foreach
-                'cos'                           :1,     // - cosine function
-                crypt                           :1,     // - one-way passwd-style encryption
-                dbmclose                        :1,     // - breaks binding on a tied dbm file
-                dbmopen                         :1,     // - create binding on a tied dbm file
-                'default'                       :1,     //
-                defined                         :1,     // - test whether a value, variable, or function is defined
-                'delete'                        :1,     // - deletes a value from a hash
-                die                             :1,     // - raise an exception or bail out
-                'do'                            :1,     // - turn a BLOCK into a TERM
-                dump                            :1,     // - create an immediate core dump
-                each                            :1,     // - retrieve the next key/value pair from a hash
-                endgrent                        :1,     // - be done using group file
-                endhostent                      :1,     // - be done using hosts file
-                endnetent                       :1,     // - be done using networks file
-                endprotoent                     :1,     // - be done using protocols file
-                endpwent                        :1,     // - be done using passwd file
-                endservent                      :1,     // - be done using services file
-                eof                             :1,     // - test a filehandle for its end
-                'eval'                          :1,     // - catch exceptions or compile and run code
-                'exec'                          :1,     // - abandon this program to run another
-                exists                          :1,     // - test whether a hash key is present
-                exit                            :1,     // - terminate this program
-                'exp'                           :1,     // - raise I to a power
-                fcntl                           :1,     // - file control system call
-                fileno                          :1,     // - return file descriptor from filehandle
-                flock                           :1,     // - lock an entire file with an advisory lock
-                fork                            :1,     // - create a new process just like this one
-                format                          :1,     // - declare a picture format with use by the write() function
-                formline                        :1,     // - internal function used for formats
-                getc                            :1,     // - get the next character from the filehandle
-                getgrent                        :1,     // - get next group record
-                getgrgid                        :1,     // - get group record given group user ID
-                getgrnam                        :1,     // - get group record given group name
-                gethostbyaddr                   :1,     // - get host record given its address
-                gethostbyname                   :1,     // - get host record given name
-                gethostent                      :1,     // - get next hosts record
-                getlogin                        :1,     // - return who logged in at this tty
-                getnetbyaddr                    :1,     // - get network record given its address
-                getnetbyname                    :1,     // - get networks record given name
-                getnetent                       :1,     // - get next networks record
-                getpeername                     :1,     // - find the other end of a socket connection
-                getpgrp                         :1,     // - get process group
-                getppid                         :1,     // - get parent process ID
-                getpriority                     :1,     // - get current nice value
-                getprotobyname                  :1,     // - get protocol record given name
-                getprotobynumber                :1,     // - get protocol record numeric protocol
-                getprotoent                     :1,     // - get next protocols record
-                getpwent                        :1,     // - get next passwd record
-                getpwnam                        :1,     // - get passwd record given user login name
-                getpwuid                        :1,     // - get passwd record given user ID
-                getservbyname                   :1,     // - get services record given its name
-                getservbyport                   :1,     // - get services record given numeric port
-                getservent                      :1,     // - get next services record
-                getsockname                     :1,     // - retrieve the sockaddr for a given socket
-                getsockopt                      :1,     // - get socket options on a given socket
-                given                           :1,     //
-                glob                            :1,     // - expand filenames using wildcards
-                gmtime                          :1,     // - convert UNIX time into record or string using Greenwich time
-                'goto'                          :1,     // - create spaghetti code
-                grep                            :1,     // - locate elements in a list test true against a given criterion
-                hex                             :1,     // - convert a string to a hexadecimal number
-                'import'                        :1,     // - patch a module's namespace into your own
-                index                           :1,     // - find a substring within a string
-                'int'                           :1,     // - get the integer portion of a number
-                ioctl                           :1,     // - system-dependent device control system call
-                'join'                          :1,     // - join a list into a string using a separator
-                keys                            :1,     // - retrieve list of indices from a hash
-                kill                            :1,     // - send a signal to a process or process group
-                last                            :1,     // - exit a block prematurely
-                lc                              :1,     // - return lower-case version of a string
-                lcfirst                         :1,     // - return a string with just the next letter in lower case
-                length                          :1,     // - return the number of bytes in a string
-                'link'                          :1,     // - create a hard link in the filesytem
-                listen                          :1,     // - register your socket as a server
-                local                           : 2,    // - create a temporary value for a global variable (dynamic scoping)
-                localtime                       :1,     // - convert UNIX time into record or string using local time
-                lock                            :1,     // - get a thread lock on a variable, subroutine, or method
-                'log'                           :1,     // - retrieve the natural logarithm for a number
-                lstat                           :1,     // - stat a symbolic link
-                m                               :null,  // - match a string with a regular expression pattern
-                map                             :1,     // - apply a change to a list to get back a new list with the changes
-                mkdir                           :1,     // - create a directory
-                msgctl                          :1,     // - SysV IPC message control operations
-                msgget                          :1,     // - get SysV IPC message queue
-                msgrcv                          :1,     // - receive a SysV IPC message from a message queue
-                msgsnd                          :1,     // - send a SysV IPC message to a message queue
-                my                              : 2,    // - declare and assign a local variable (lexical scoping)
-                'new'                           :1,     //
-                next                            :1,     // - iterate a block prematurely
-                no                              :1,     // - unimport some module symbols or semantics at compile time
-                oct                             :1,     // - convert a string to an octal number
-                open                            :1,     // - open a file, pipe, or descriptor
-                opendir                         :1,     // - open a directory
-                ord                             :1,     // - find a character's numeric representation
-                our                             : 2,    // - declare and assign a package variable (lexical scoping)
-                pack                            :1,     // - convert a list into a binary representation
-                'package'                       :1,     // - declare a separate global namespace
-                pipe                            :1,     // - open a pair of connected filehandles
-                pop                             :1,     // - remove the last element from an array and return it
-                pos                             :1,     // - find or set the offset for the last/next m//g search
-                print                           :1,     // - output a list to a filehandle
-                printf                          :1,     // - output a formatted list to a filehandle
-                prototype                       :1,     // - get the prototype (if any) of a subroutine
-                push                            :1,     // - append one or more elements to an array
-                q                               :null,  // - singly quote a string
-                qq                              :null,  // - doubly quote a string
-                qr                              :null,  // - Compile pattern
-                quotemeta                       :null,  // - quote regular expression magic characters
-                qw                              :null,  // - quote a list of words
-                qx                              :null,  // - backquote quote a string
-                rand                            :1,     // - retrieve the next pseudorandom number
-                read                            :1,     // - fixed-length buffered input from a filehandle
-                readdir                         :1,     // - get a directory from a directory handle
-                readline                        :1,     // - fetch a record from a file
-                readlink                        :1,     // - determine where a symbolic link is pointing
-                readpipe                        :1,     // - execute a system command and collect standard output
-                recv                            :1,     // - receive a message over a Socket
-                redo                            :1,     // - start this loop iteration over again
-                ref                             :1,     // - find out the type of thing being referenced
-                rename                          :1,     // - change a filename
-                require                         :1,     // - load in external functions from a library at runtime
-                reset                           :1,     // - clear all variables of a given name
-                'return'                        :1,     // - get out of a function early
-                reverse                         :1,     // - flip a string or a list
-                rewinddir                       :1,     // - reset directory handle
-                rindex                          :1,     // - right-to-left substring search
-                rmdir                           :1,     // - remove a directory
-                s                               :null,  // - replace a pattern with a string
-                say                             :1,     // - print with newline
-                scalar                          :1,     // - force a scalar context
-                seek                            :1,     // - reposition file pointer for random-access I/O
-                seekdir                         :1,     // - reposition directory pointer
-                select                          :1,     // - reset default output or do I/O multiplexing
-                semctl                          :1,     // - SysV semaphore control operations
-                semget                          :1,     // - get set of SysV semaphores
-                semop                           :1,     // - SysV semaphore operations
-                send                            :1,     // - send a message over a socket
-                setgrent                        :1,     // - prepare group file for use
-                sethostent                      :1,     // - prepare hosts file for use
-                setnetent                       :1,     // - prepare networks file for use
-                setpgrp                         :1,     // - set the process group of a process
-                setpriority                     :1,     // - set a process's nice value
-                setprotoent                     :1,     // - prepare protocols file for use
-                setpwent                        :1,     // - prepare passwd file for use
-                setservent                      :1,     // - prepare services file for use
-                setsockopt                      :1,     // - set some socket options
-                shift                           :1,     // - remove the first element of an array, and return it
-                shmctl                          :1,     // - SysV shared memory operations
-                shmget                          :1,     // - get SysV shared memory segment identifier
-                shmread                         :1,     // - read SysV shared memory
-                shmwrite                        :1,     // - write SysV shared memory
-                shutdown                        :1,     // - close down just half of a socket connection
-                'sin'                           :1,     // - return the sine of a number
-                sleep                           :1,     // - block for some number of seconds
-                socket                          :1,     // - create a socket
-                socketpair                      :1,     // - create a pair of sockets
-                'sort'                          :1,     // - sort a list of values
-                splice                          :1,     // - add or remove elements anywhere in an array
-                'split'                         :1,     // - split up a string using a regexp delimiter
-                sprintf                         :1,     // - formatted print into a string
-                'sqrt'                          :1,     // - square root function
-                srand                           :1,     // - seed the random number generator
-                stat                            :1,     // - get a file's status information
-                state                           :1,     // - declare and assign a state variable (persistent lexical scoping)
-                study                           :1,     // - optimize input data for repeated searches
-                'sub'                           :1,     // - declare a subroutine, possibly anonymously
-                'substr'                        :1,     // - get or alter a portion of a stirng
-                symlink                         :1,     // - create a symbolic link to a file
-                syscall                         :1,     // - execute an arbitrary system call
-                sysopen                         :1,     // - open a file, pipe, or descriptor
-                sysread                         :1,     // - fixed-length unbuffered input from a filehandle
-                sysseek                         :1,     // - position I/O pointer on handle used with sysread and syswrite
-                system                          :1,     // - run a separate program
-                syswrite                        :1,     // - fixed-length unbuffered output to a filehandle
-                tell                            :1,     // - get current seekpointer on a filehandle
-                telldir                         :1,     // - get current seekpointer on a directory handle
-                tie                             :1,     // - bind a variable to an object class
-                tied                            :1,     // - get a reference to the object underlying a tied variable
-                time                            :1,     // - return number of seconds since 1970
-                times                           :1,     // - return elapsed time for self and child processes
-                tr                              :null,  // - transliterate a string
-                truncate                        :1,     // - shorten a file
-                uc                              :1,     // - return upper-case version of a string
-                ucfirst                         :1,     // - return a string with just the next letter in upper case
-                umask                           :1,     // - set file creation mode mask
-                undef                           :1,     // - remove a variable or function definition
-                unlink                          :1,     // - remove one link to a file
-                unpack                          :1,     // - convert binary structure into normal perl variables
-                unshift                         :1,     // - prepend more elements to the beginning of a list
-                untie                           :1,     // - break a tie binding to a variable
-                use                             :1,     // - load in a module at compile time
-                utime                           :1,     // - set a file's last access and modify times
-                values                          :1,     // - return a list of the values in a hash
-                vec                             :1,     // - test or set particular bits in a string
-                wait                            :1,     // - wait for any child process to die
-                waitpid                         :1,     // - wait for a particular child process to die
-                wantarray                       :1,     // - get void vs scalar vs list context of current subroutine call
-                warn                            :1,     // - print debugging info
-                when                            :1,     //
-                write                           :1,     // - print a picture record
-                y                               :null}; // - transliterate a string
-
-        var RXstyle="string-2";
-        var RXmodifiers=/[goseximacplud]/;              // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type
-
-        function tokenChain(stream,state,chain,style,tail){     // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;)
-                state.chain=null;                               //                                                          12   3tail
-                state.style=null;
-                state.tail=null;
-                state.tokenize=function(stream,state){
-                        var e=false,c,i=0;
-                        while(c=stream.next()){
-                                if(c===chain[i]&&!e){
-                                        if(chain[++i]!==undefined){
-                                                state.chain=chain[i];
-                                                state.style=style;
-                                                state.tail=tail;}
-                                        else if(tail)
-                                                stream.eatWhile(tail);
-                                        state.tokenize=tokenPerl;
-                                        return style;}
-                                e=!e&&c=="\\";}
-                        return style;};
-                return state.tokenize(stream,state);}
-
-        function tokenSOMETHING(stream,state,string){
-                state.tokenize=function(stream,state){
-                        if(stream.string==string)
-                                state.tokenize=tokenPerl;
-                        stream.skipToEnd();
-                        return "string";};
-                return state.tokenize(stream,state);}
-
-        function tokenPerl(stream,state){
-                if(stream.eatSpace())
-                        return null;
-                if(state.chain)
-                        return tokenChain(stream,state,state.chain,state.style,state.tail);
-                if(stream.match(/^\-?[\d\.]/,false))
-                        if(stream.match(/^(\-?(\d*\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F]+|0b[01]+|\d+(e[+-]?\d+)?)/))
-                                return 'number';
-                if(stream.match(/^<<(?=\w)/)){                  // NOTE: <<SOMETHING\n...\nSOMETHING\n
-                        stream.eatWhile(/\w/);
-                        return tokenSOMETHING(stream,state,stream.current().substr(2));}
-                if(stream.sol()&&stream.match(/^\=item(?!\w)/)){// NOTE: \n=item...\n=cut\n
-                        return tokenSOMETHING(stream,state,'=cut');}
-                var ch=stream.next();
-                if(ch=='"'||ch=="'"){                           // NOTE: ' or " or <<'SOMETHING'\n...\nSOMETHING\n or <<"SOMETHING"\n...\nSOMETHING\n
-                        if(prefix(stream, 3)=="<<"+ch){
-                                var p=stream.pos;
-                                stream.eatWhile(/\w/);
-                                var n=stream.current().substr(1);
-                                if(n&&stream.eat(ch))
-                                        return tokenSOMETHING(stream,state,n);
-                                stream.pos=p;}
-                        return tokenChain(stream,state,[ch],"string");}
-                if(ch=="q"){
-                        var c=look(stream, -2);
-                        if(!(c&&/\w/.test(c))){
-                                c=look(stream, 0);
-                                if(c=="x"){
-                                        c=look(stream, 1);
-                                        if(c=="("){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);}
-                                        if(c=="["){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);}
-                                        if(c=="{"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);}
-                                        if(c=="<"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}}
-                                else if(c=="q"){
-                                        c=look(stream, 1);
-                                        if(c=="("){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[")"],"string");}
-                                        if(c=="["){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["]"],"string");}
-                                        if(c=="{"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["}"],"string");}
-                                        if(c=="<"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[">"],"string");}
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[stream.eat(c)],"string");}}
-                                else if(c=="w"){
-                                        c=look(stream, 1);
-                                        if(c=="("){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[")"],"bracket");}
-                                        if(c=="["){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["]"],"bracket");}
-                                        if(c=="{"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["}"],"bracket");}
-                                        if(c=="<"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[">"],"bracket");}
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[stream.eat(c)],"bracket");}}
-                                else if(c=="r"){
-                                        c=look(stream, 1);
-                                        if(c=="("){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);}
-                                        if(c=="["){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);}
-                                        if(c=="{"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);}
-                                        if(c=="<"){
-                                                eatSuffix(stream, 2);
-                                                return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}}
-                                else if(/[\^'"!~\/(\[{<]/.test(c)){
-                                        if(c=="("){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[")"],"string");}
-                                        if(c=="["){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,["]"],"string");}
-                                        if(c=="{"){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,["}"],"string");}
-                                        if(c=="<"){
-                                                eatSuffix(stream, 1);
-                                                return tokenChain(stream,state,[">"],"string");}
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                return tokenChain(stream,state,[stream.eat(c)],"string");}}}}
-                if(ch=="m"){
-                        var c=look(stream, -2);
-                        if(!(c&&/\w/.test(c))){
-                                c=stream.eat(/[(\[{<\^'"!~\/]/);
-                                if(c){
-                                        if(/[\^'"!~\/]/.test(c)){
-                                                return tokenChain(stream,state,[c],RXstyle,RXmodifiers);}
-                                        if(c=="("){
-                                                return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);}
-                                        if(c=="["){
-                                                return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);}
-                                        if(c=="{"){
-                                                return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);}
-                                        if(c=="<"){
-                                                return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}}}}
-                if(ch=="s"){
-                        var c=/[\/>\]})\w]/.test(look(stream, -2));
-                        if(!c){
-                                c=stream.eat(/[(\[{<\^'"!~\/]/);
-                                if(c){
-                                        if(c=="[")
-                                                return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers);
-                                        if(c=="{")
-                                                return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers);
-                                        if(c=="<")
-                                                return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers);
-                                        if(c=="(")
-                                                return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers);
-                                        return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}
-                if(ch=="y"){
-                        var c=/[\/>\]})\w]/.test(look(stream, -2));
-                        if(!c){
-                                c=stream.eat(/[(\[{<\^'"!~\/]/);
-                                if(c){
-                                        if(c=="[")
-                                                return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers);
-                                        if(c=="{")
-                                                return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers);
-                                        if(c=="<")
-                                                return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers);
-                                        if(c=="(")
-                                                return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers);
-                                        return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}
-                if(ch=="t"){
-                        var c=/[\/>\]})\w]/.test(look(stream, -2));
-                        if(!c){
-                                c=stream.eat("r");if(c){
-                                c=stream.eat(/[(\[{<\^'"!~\/]/);
-                                if(c){
-                                        if(c=="[")
-                                                return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers);
-                                        if(c=="{")
-                                                return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers);
-                                        if(c=="<")
-                                                return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers);
-                                        if(c=="(")
-                                                return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers);
-                                        return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}}
-                if(ch=="`"){
-                        return tokenChain(stream,state,[ch],"variable-2");}
-                if(ch=="/"){
-                        if(!/~\s*$/.test(prefix(stream)))
-                                return "operator";
-                        else
-                                return tokenChain(stream,state,[ch],RXstyle,RXmodifiers);}
-                if(ch=="$"){
-                        var p=stream.pos;
-                        if(stream.eatWhile(/\d/)||stream.eat("{")&&stream.eatWhile(/\d/)&&stream.eat("}"))
-                                return "variable-2";
-                        else
-                                stream.pos=p;}
-                if(/[$@%]/.test(ch)){
-                        var p=stream.pos;
-                        if(stream.eat("^")&&stream.eat(/[A-Z]/)||!/[@$%&]/.test(look(stream, -2))&&stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){
-                                var c=stream.current();
-                                if(PERL[c])
-                                        return "variable-2";}
-                        stream.pos=p;}
-                if(/[$@%&]/.test(ch)){
-                        if(stream.eatWhile(/[\w$\[\]]/)||stream.eat("{")&&stream.eatWhile(/[\w$\[\]]/)&&stream.eat("}")){
-                                var c=stream.current();
-                                if(PERL[c])
-                                        return "variable-2";
-                                else
-                                        return "variable";}}
-                if(ch=="#"){
-                        if(look(stream, -2)!="$"){
-                                stream.skipToEnd();
-                                return "comment";}}
-                if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)){
-                        var p=stream.pos;
-                        stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/);
-                        if(PERL[stream.current()])
-                                return "operator";
-                        else
-                                stream.pos=p;}
-                if(ch=="_"){
-                        if(stream.pos==1){
-                                if(suffix(stream, 6)=="_END__"){
-                                        return tokenChain(stream,state,['\0'],"comment");}
-                                else if(suffix(stream, 7)=="_DATA__"){
-                                        return tokenChain(stream,state,['\0'],"variable-2");}
-                                else if(suffix(stream, 7)=="_C__"){
-                                        return tokenChain(stream,state,['\0'],"string");}}}
-                if(/\w/.test(ch)){
-                        var p=stream.pos;
-                        if(look(stream, -2)=="{"&&(look(stream, 0)=="}"||stream.eatWhile(/\w/)&&look(stream, 0)=="}"))
-                                return "string";
-                        else
-                                stream.pos=p;}
-                if(/[A-Z]/.test(ch)){
-                        var l=look(stream, -2);
-                        var p=stream.pos;
-                        stream.eatWhile(/[A-Z_]/);
-                        if(/[\da-z]/.test(look(stream, 0))){
-                                stream.pos=p;}
-                        else{
-                                var c=PERL[stream.current()];
-                                if(!c)
-                                        return "meta";
-                                if(c[1])
-                                        c=c[0];
-                                if(l!=":"){
-                                        if(c==1)
-                                                return "keyword";
-                                        else if(c==2)
-                                                return "def";
-                                        else if(c==3)
-                                                return "atom";
-                                        else if(c==4)
-                                                return "operator";
-                                        else if(c==5)
-                                                return "variable-2";
-                                        else
-                                                return "meta";}
-                                else
-                                        return "meta";}}
-                if(/[a-zA-Z_]/.test(ch)){
-                        var l=look(stream, -2);
-                        stream.eatWhile(/\w/);
-                        var c=PERL[stream.current()];
-                        if(!c)
-                                return "meta";
-                        if(c[1])
-                                c=c[0];
-                        if(l!=":"){
-                                if(c==1)
-                                        return "keyword";
-                                else if(c==2)
-                                        return "def";
-                                else if(c==3)
-                                        return "atom";
-                                else if(c==4)
-                                        return "operator";
-                                else if(c==5)
-                                        return "variable-2";
-                                else
-                                        return "meta";}
-                        else
-                                return "meta";}
-                return null;}
-
-        return{
-                startState:function(){
-                        return{
-                                tokenize:tokenPerl,
-                                chain:null,
-                                style:null,
-                                tail:null};},
-                token:function(stream,state){
-                        return (state.tokenize||tokenPerl)(stream,state);}
-                };});
-
-CodeMirror.registerHelper("wordChars", "perl", /[\w$]/);
-
-CodeMirror.defineMIME("text/x-perl", "perl");
-
-// it's like "peek", but need for look-ahead or look-behind if index < 0
-function look(stream, c){
-  return stream.string.charAt(stream.pos+(c||0));
-}
-
-// return a part of prefix of current stream from current position
-function prefix(stream, c){
-  if(c){
-    var x=stream.pos-c;
-    return stream.string.substr((x>=0?x:0),c);}
-  else{
-    return stream.string.substr(0,stream.pos-1);
-  }
-}
-
-// return a part of suffix of current stream from current position
-function suffix(stream, c){
-  var y=stream.string.length;
-  var x=y-stream.pos+1;
-  return stream.string.substr(stream.pos,(c&&c<y?c:x));
-}
-
-// eating and vomiting a part of stream from current position
-function eatSuffix(stream, c){
-  var x=stream.pos+c;
-  var y;
-  if(x<=0)
-    stream.pos=0;
-  else if(x>=(y=stream.string.length-1))
-    stream.pos=y;
-  else
-    stream.pos=x;
-}
-
-});
--- a/kallithea/public/codemirror/mode/php/php.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,226 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../clike/clike"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../clike/clike"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  function keywords(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  // Helper for stringWithEscapes
-  function matchSequence(list, end) {
-    if (list.length == 0) return stringWithEscapes(end);
-    return function (stream, state) {
-      var patterns = list[0];
-      for (var i = 0; i < patterns.length; i++) if (stream.match(patterns[i][0])) {
-        state.tokenize = matchSequence(list.slice(1), end);
-        return patterns[i][1];
-      }
-      state.tokenize = stringWithEscapes(end);
-      return "string";
-    };
-  }
-  function stringWithEscapes(closing) {
-    return function(stream, state) { return stringWithEscapes_(stream, state, closing); };
-  }
-  function stringWithEscapes_(stream, state, closing) {
-    // "Complex" syntax
-    if (stream.match("${", false) || stream.match("{$", false)) {
-      state.tokenize = null;
-      return "string";
-    }
-
-    // Simple syntax
-    if (stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) {
-      // After the variable name there may appear array or object operator.
-      if (stream.match("[", false)) {
-        // Match array operator
-        state.tokenize = matchSequence([
-          [["[", null]],
-          [[/\d[\w\.]*/, "number"],
-           [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"],
-           [/[\w\$]+/, "variable"]],
-          [["]", null]]
-        ], closing);
-      }
-      if (stream.match(/\-\>\w/, false)) {
-        // Match object operator
-        state.tokenize = matchSequence([
-          [["->", null]],
-          [[/[\w]+/, "variable"]]
-        ], closing);
-      }
-      return "variable-2";
-    }
-
-    var escaped = false;
-    // Normal string
-    while (!stream.eol() &&
-           (escaped || (!stream.match("{$", false) &&
-                        !stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false)))) {
-      if (!escaped && stream.match(closing)) {
-        state.tokenize = null;
-        state.tokStack.pop(); state.tokStack.pop();
-        break;
-      }
-      escaped = stream.next() == "\\" && !escaped;
-    }
-    return "string";
-  }
-
-  var phpKeywords = "abstract and array as break case catch class clone const continue declare default " +
-    "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " +
-    "for foreach function global goto if implements interface instanceof namespace " +
-    "new or private protected public static switch throw trait try use var while xor " +
-    "die echo empty exit eval include include_once isset list require require_once return " +
-    "print unset __halt_compiler self static parent yield insteadof finally";
-  var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__";
-  var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";
-  CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" "));
-  CodeMirror.registerHelper("wordChars", "php", /[\w$]/);
-
-  var phpConfig = {
-    name: "clike",
-    helperType: "php",
-    keywords: keywords(phpKeywords),
-    blockKeywords: keywords("catch do else elseif for foreach if switch try while finally"),
-    atoms: keywords(phpAtoms),
-    builtin: keywords(phpBuiltin),
-    multiLineStrings: true,
-    hooks: {
-      "$": function(stream) {
-        stream.eatWhile(/[\w\$_]/);
-        return "variable-2";
-      },
-      "<": function(stream, state) {
-        if (stream.match(/<</)) {
-          stream.eatWhile(/[\w\.]/);
-          var delim = stream.current().slice(3);
-          if (delim) {
-            (state.tokStack || (state.tokStack = [])).push(delim, 0);
-            state.tokenize = stringWithEscapes(delim);
-            return "string";
-          }
-        }
-        return false;
-      },
-      "#": function(stream) {
-        while (!stream.eol() && !stream.match("?>", false)) stream.next();
-        return "comment";
-      },
-      "/": function(stream) {
-        if (stream.eat("/")) {
-          while (!stream.eol() && !stream.match("?>", false)) stream.next();
-          return "comment";
-        }
-        return false;
-      },
-      '"': function(_stream, state) {
-        (state.tokStack || (state.tokStack = [])).push('"', 0);
-        state.tokenize = stringWithEscapes('"');
-        return "string";
-      },
-      "{": function(_stream, state) {
-        if (state.tokStack && state.tokStack.length)
-          state.tokStack[state.tokStack.length - 1]++;
-        return false;
-      },
-      "}": function(_stream, state) {
-        if (state.tokStack && state.tokStack.length > 0 &&
-            !--state.tokStack[state.tokStack.length - 1]) {
-          state.tokenize = stringWithEscapes(state.tokStack[state.tokStack.length - 2]);
-        }
-        return false;
-      }
-    }
-  };
-
-  CodeMirror.defineMode("php", function(config, parserConfig) {
-    var htmlMode = CodeMirror.getMode(config, "text/html");
-    var phpMode = CodeMirror.getMode(config, phpConfig);
-
-    function dispatch(stream, state) {
-      var isPHP = state.curMode == phpMode;
-      if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null;
-      if (!isPHP) {
-        if (stream.match(/^<\?\w*/)) {
-          state.curMode = phpMode;
-          state.curState = state.php;
-          return "meta";
-        }
-        if (state.pending == '"' || state.pending == "'") {
-          while (!stream.eol() && stream.next() != state.pending) {}
-          var style = "string";
-        } else if (state.pending && stream.pos < state.pending.end) {
-          stream.pos = state.pending.end;
-          var style = state.pending.style;
-        } else {
-          var style = htmlMode.token(stream, state.curState);
-        }
-        if (state.pending) state.pending = null;
-        var cur = stream.current(), openPHP = cur.search(/<\?/), m;
-        if (openPHP != -1) {
-          if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0];
-          else state.pending = {end: stream.pos, style: style};
-          stream.backUp(cur.length - openPHP);
-        }
-        return style;
-      } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
-        state.curMode = htmlMode;
-        state.curState = state.html;
-        return "meta";
-      } else {
-        return phpMode.token(stream, state.curState);
-      }
-    }
-
-    return {
-      startState: function() {
-        var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
-        return {html: html,
-                php: php,
-                curMode: parserConfig.startOpen ? phpMode : htmlMode,
-                curState: parserConfig.startOpen ? php : html,
-                pending: null};
-      },
-
-      copyState: function(state) {
-        var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
-            php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
-        if (state.curMode == htmlMode) cur = htmlNew;
-        else cur = phpNew;
-        return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
-                pending: state.pending};
-      },
-
-      token: dispatch,
-
-      indent: function(state, textAfter) {
-        if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) ||
-            (state.curMode == phpMode && /^\?>/.test(textAfter)))
-          return htmlMode.indent(state.html, textAfter);
-        return state.curMode.indent(state.curState, textAfter);
-      },
-
-      blockCommentStart: "/*",
-      blockCommentEnd: "*/",
-      lineComment: "//",
-
-      innerMode: function(state) { return {state: state.curState, mode: state.curMode}; }
-    };
-  }, "htmlmixed", "clike");
-
-  CodeMirror.defineMIME("application/x-httpd-php", "php");
-  CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true});
-  CodeMirror.defineMIME("text/x-php", phpConfig);
-});
--- a/kallithea/public/codemirror/mode/pig/pig.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
- *      Pig Latin Mode for CodeMirror 2
- *      @author Prasanth Jayachandran
- *      @link   https://github.com/prasanthj/pig-codemirror-2
- *  This implementation is adapted from PL/SQL mode in CodeMirror 2.
- */
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("pig", function(_config, parserConfig) {
-  var keywords = parserConfig.keywords,
-  builtins = parserConfig.builtins,
-  types = parserConfig.types,
-  multiLineStrings = parserConfig.multiLineStrings;
-
-  var isOperatorChar = /[*+\-%<>=&?:\/!|]/;
-
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-
-  var type;
-  function ret(tp, style) {
-    type = tp;
-    return style;
-  }
-
-  function tokenComment(stream, state) {
-    var isEnd = false;
-    var ch;
-    while(ch = stream.next()) {
-      if(ch == "/" && isEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      isEnd = (ch == "*");
-    }
-    return ret("comment", "comment");
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while((next = stream.next()) != null) {
-        if (next == quote && !escaped) {
-          end = true; break;
-        }
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || multiLineStrings))
-        state.tokenize = tokenBase;
-      return ret("string", "error");
-    };
-  }
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-
-    // is a start of string?
-    if (ch == '"' || ch == "'")
-      return chain(stream, state, tokenString(ch));
-    // is it one of the special chars
-    else if(/[\[\]{}\(\),;\.]/.test(ch))
-      return ret(ch);
-    // is it a number?
-    else if(/\d/.test(ch)) {
-      stream.eatWhile(/[\w\.]/);
-      return ret("number", "number");
-    }
-    // multi line comment or operator
-    else if (ch == "/") {
-      if (stream.eat("*")) {
-        return chain(stream, state, tokenComment);
-      }
-      else {
-        stream.eatWhile(isOperatorChar);
-        return ret("operator", "operator");
-      }
-    }
-    // single line comment or operator
-    else if (ch=="-") {
-      if(stream.eat("-")){
-        stream.skipToEnd();
-        return ret("comment", "comment");
-      }
-      else {
-        stream.eatWhile(isOperatorChar);
-        return ret("operator", "operator");
-      }
-    }
-    // is it an operator
-    else if (isOperatorChar.test(ch)) {
-      stream.eatWhile(isOperatorChar);
-      return ret("operator", "operator");
-    }
-    else {
-      // get the while word
-      stream.eatWhile(/[\w\$_]/);
-      // is it one of the listed keywords?
-      if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) {
-        if (stream.eat(")") || stream.eat(".")) {
-          //keywords can be used as variables like flatten(group), group.$0 etc..
-        }
-        else {
-          return ("keyword", "keyword");
-        }
-      }
-      // is it one of the builtin functions?
-      if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase()))
-      {
-        return ("keyword", "variable-2");
-      }
-      // is it one of the listed types?
-      if (types && types.propertyIsEnumerable(stream.current().toUpperCase()))
-        return ("keyword", "variable-3");
-      // default is a 'variable'
-      return ret("variable", "pig-word");
-    }
-  }
-
-  // Interface
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      if(stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      return style;
-    }
-  };
-});
-
-(function() {
-  function keywords(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  // builtin funcs taken from trunk revision 1303237
-  var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL "
-    + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS "
-    + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG "
-    + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN "
-    + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER "
-    + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS "
-    + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA  "
-    + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE "
-    + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG "
-    + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER ";
-
-  // taken from QueryLexer.g
-  var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP "
-    + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL "
-    + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE "
-    + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE "
-    + "NEQ MATCHES TRUE FALSE DUMP";
-
-  // data types
-  var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP ";
-
-  CodeMirror.defineMIME("text/x-pig", {
-    name: "pig",
-    builtins: keywords(pBuiltins),
-    keywords: keywords(pKeywords),
-    types: keywords(pTypes)
-  });
-
-  CodeMirror.registerHelper("hintWords", "pig", (pBuiltins + pTypes + pKeywords).split(" "));
-}());
-
-});
--- a/kallithea/public/codemirror/mode/properties/properties.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("properties", function() {
-  return {
-    token: function(stream, state) {
-      var sol = stream.sol() || state.afterSection;
-      var eol = stream.eol();
-
-      state.afterSection = false;
-
-      if (sol) {
-        if (state.nextMultiline) {
-          state.inMultiline = true;
-          state.nextMultiline = false;
-        } else {
-          state.position = "def";
-        }
-      }
-
-      if (eol && ! state.nextMultiline) {
-        state.inMultiline = false;
-        state.position = "def";
-      }
-
-      if (sol) {
-        while(stream.eatSpace());
-      }
-
-      var ch = stream.next();
-
-      if (sol && (ch === "#" || ch === "!" || ch === ";")) {
-        state.position = "comment";
-        stream.skipToEnd();
-        return "comment";
-      } else if (sol && ch === "[") {
-        state.afterSection = true;
-        stream.skipTo("]"); stream.eat("]");
-        return "header";
-      } else if (ch === "=" || ch === ":") {
-        state.position = "quote";
-        return null;
-      } else if (ch === "\\" && state.position === "quote") {
-        if (stream.next() !== "u") {    // u = Unicode sequence \u1234
-          // Multiline value
-          state.nextMultiline = true;
-        }
-      }
-
-      return state.position;
-    },
-
-    startState: function() {
-      return {
-        position : "def",       // Current position, "def", "quote" or "comment"
-        nextMultiline : false,  // Is the next line multiline value
-        inMultiline : false,    // Is the current line a multiline value
-        afterSection : false    // Did we just open a section
-      };
-    }
-
-  };
-});
-
-CodeMirror.defineMIME("text/x-properties", "properties");
-CodeMirror.defineMIME("text/x-ini", "properties");
-
-});
--- a/kallithea/public/codemirror/mode/puppet/puppet.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("puppet", function () {
-  // Stores the words from the define method
-  var words = {};
-  // Taken, mostly, from the Puppet official variable standards regex
-  var variable_regex = /({)?([a-z][a-z0-9_]*)?((::[a-z][a-z0-9_]*)*::)?[a-zA-Z0-9_]+(})?/;
-
-  // Takes a string of words separated by spaces and adds them as
-  // keys with the value of the first argument 'style'
-  function define(style, string) {
-    var split = string.split(' ');
-    for (var i = 0; i < split.length; i++) {
-      words[split[i]] = style;
-    }
-  }
-
-  // Takes commonly known puppet types/words and classifies them to a style
-  define('keyword', 'class define site node include import inherits');
-  define('keyword', 'case if else in and elsif default or');
-  define('atom', 'false true running present absent file directory undef');
-  define('builtin', 'action augeas burst chain computer cron destination dport exec ' +
-    'file filebucket group host icmp iniface interface jump k5login limit log_level ' +
-    'log_prefix macauthorization mailalias maillist mcx mount nagios_command ' +
-    'nagios_contact nagios_contactgroup nagios_host nagios_hostdependency ' +
-    'nagios_hostescalation nagios_hostextinfo nagios_hostgroup nagios_service ' +
-    'nagios_servicedependency nagios_serviceescalation nagios_serviceextinfo ' +
-    'nagios_servicegroup nagios_timeperiod name notify outiface package proto reject ' +
-    'resources router schedule scheduled_task selboolean selmodule service source ' +
-    'sport ssh_authorized_key sshkey stage state table tidy todest toports tosource ' +
-    'user vlan yumrepo zfs zone zpool');
-
-  // After finding a start of a string ('|") this function attempts to find the end;
-  // If a variable is encountered along the way, we display it differently when it
-  // is encapsulated in a double-quoted string.
-  function tokenString(stream, state) {
-    var current, prev, found_var = false;
-    while (!stream.eol() && (current = stream.next()) != state.pending) {
-      if (current === '$' && prev != '\\' && state.pending == '"') {
-        found_var = true;
-        break;
-      }
-      prev = current;
-    }
-    if (found_var) {
-      stream.backUp(1);
-    }
-    if (current == state.pending) {
-      state.continueString = false;
-    } else {
-      state.continueString = true;
-    }
-    return "string";
-  }
-
-  // Main function
-  function tokenize(stream, state) {
-    // Matches one whole word
-    var word = stream.match(/[\w]+/, false);
-    // Matches attributes (i.e. ensure => present ; 'ensure' would be matched)
-    var attribute = stream.match(/(\s+)?\w+\s+=>.*/, false);
-    // Matches non-builtin resource declarations
-    // (i.e. "apache::vhost {" or "mycustomclasss {" would be matched)
-    var resource = stream.match(/(\s+)?[\w:_]+(\s+)?{/, false);
-    // Matches virtual and exported resources (i.e. @@user { ; and the like)
-    var special_resource = stream.match(/(\s+)?[@]{1,2}[\w:_]+(\s+)?{/, false);
-
-    // Finally advance the stream
-    var ch = stream.next();
-
-    // Have we found a variable?
-    if (ch === '$') {
-      if (stream.match(variable_regex)) {
-        // If so, and its in a string, assign it a different color
-        return state.continueString ? 'variable-2' : 'variable';
-      }
-      // Otherwise return an invalid variable
-      return "error";
-    }
-    // Should we still be looking for the end of a string?
-    if (state.continueString) {
-      // If so, go through the loop again
-      stream.backUp(1);
-      return tokenString(stream, state);
-    }
-    // Are we in a definition (class, node, define)?
-    if (state.inDefinition) {
-      // If so, return def (i.e. for 'class myclass {' ; 'myclass' would be matched)
-      if (stream.match(/(\s+)?[\w:_]+(\s+)?/)) {
-        return 'def';
-      }
-      // Match the rest it the next time around
-      stream.match(/\s+{/);
-      state.inDefinition = false;
-    }
-    // Are we in an 'include' statement?
-    if (state.inInclude) {
-      // Match and return the included class
-      stream.match(/(\s+)?\S+(\s+)?/);
-      state.inInclude = false;
-      return 'def';
-    }
-    // Do we just have a function on our hands?
-    // In 'ensure_resource("myclass")', 'ensure_resource' is matched
-    if (stream.match(/(\s+)?\w+\(/)) {
-      stream.backUp(1);
-      return 'def';
-    }
-    // Have we matched the prior attribute regex?
-    if (attribute) {
-      stream.match(/(\s+)?\w+/);
-      return 'tag';
-    }
-    // Do we have Puppet specific words?
-    if (word && words.hasOwnProperty(word)) {
-      // Negates the initial next()
-      stream.backUp(1);
-      // Acutally move the stream
-      stream.match(/[\w]+/);
-      // We want to process these words differently
-      // do to the importance they have in Puppet
-      if (stream.match(/\s+\S+\s+{/, false)) {
-        state.inDefinition = true;
-      }
-      if (word == 'include') {
-        state.inInclude = true;
-      }
-      // Returns their value as state in the prior define methods
-      return words[word];
-    }
-    // Is there a match on a reference?
-    if (/(\s+)?[A-Z]/.test(word)) {
-      // Negate the next()
-      stream.backUp(1);
-      // Match the full reference
-      stream.match(/(\s+)?[A-Z][\w:_]+/);
-      return 'def';
-    }
-    // Have we matched the prior resource regex?
-    if (resource) {
-      stream.match(/(\s+)?[\w:_]+/);
-      return 'def';
-    }
-    // Have we matched the prior special_resource regex?
-    if (special_resource) {
-      stream.match(/(\s+)?[@]{1,2}/);
-      return 'special';
-    }
-    // Match all the comments. All of them.
-    if (ch == "#") {
-      stream.skipToEnd();
-      return "comment";
-    }
-    // Have we found a string?
-    if (ch == "'" || ch == '"') {
-      // Store the type (single or double)
-      state.pending = ch;
-      // Perform the looping function to find the end
-      return tokenString(stream, state);
-    }
-    // Match all the brackets
-    if (ch == '{' || ch == '}') {
-      return 'bracket';
-    }
-    // Match characters that we are going to assume
-    // are trying to be regex
-    if (ch == '/') {
-      stream.match(/.*?\//);
-      return 'variable-3';
-    }
-    // Match all the numbers
-    if (ch.match(/[0-9]/)) {
-      stream.eatWhile(/[0-9]+/);
-      return 'number';
-    }
-    // Match the '=' and '=>' operators
-    if (ch == '=') {
-      if (stream.peek() == '>') {
-          stream.next();
-      }
-      return "operator";
-    }
-    // Keep advancing through all the rest
-    stream.eatWhile(/[\w-]/);
-    // Return a blank line for everything else
-    return null;
-  }
-  // Start it all
-  return {
-    startState: function () {
-      var state = {};
-      state.inDefinition = false;
-      state.inInclude = false;
-      state.continueString = false;
-      state.pending = false;
-      return state;
-    },
-    token: function (stream, state) {
-      // Strip the spaces, but regex will account for them eitherway
-      if (stream.eatSpace()) return null;
-      // Go through the main process
-      return tokenize(stream, state);
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-puppet", "puppet");
-
-});
--- a/kallithea/public/codemirror/mode/python/python.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,359 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  function wordRegexp(words) {
-    return new RegExp("^((" + words.join(")|(") + "))\\b");
-  }
-
-  var wordOperators = wordRegexp(["and", "or", "not", "is"]);
-  var commonKeywords = ["as", "assert", "break", "class", "continue",
-                        "def", "del", "elif", "else", "except", "finally",
-                        "for", "from", "global", "if", "import",
-                        "lambda", "pass", "raise", "return",
-                        "try", "while", "with", "yield", "in"];
-  var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
-                        "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
-                        "enumerate", "eval", "filter", "float", "format", "frozenset",
-                        "getattr", "globals", "hasattr", "hash", "help", "hex", "id",
-                        "input", "int", "isinstance", "issubclass", "iter", "len",
-                        "list", "locals", "map", "max", "memoryview", "min", "next",
-                        "object", "oct", "open", "ord", "pow", "property", "range",
-                        "repr", "reversed", "round", "set", "setattr", "slice",
-                        "sorted", "staticmethod", "str", "sum", "super", "tuple",
-                        "type", "vars", "zip", "__import__", "NotImplemented",
-                        "Ellipsis", "__debug__"];
-  var py2 = {builtins: ["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
-                        "file", "intern", "long", "raw_input", "reduce", "reload",
-                        "unichr", "unicode", "xrange", "False", "True", "None"],
-             keywords: ["exec", "print"]};
-  var py3 = {builtins: ["ascii", "bytes", "exec", "print"],
-             keywords: ["nonlocal", "False", "True", "None"]};
-
-  CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins));
-
-  function top(state) {
-    return state.scopes[state.scopes.length - 1];
-  }
-
-  CodeMirror.defineMode("python", function(conf, parserConf) {
-    var ERRORCLASS = "error";
-
-    var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]");
-    var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
-    var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
-    var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
-
-    if (parserConf.version && parseInt(parserConf.version, 10) == 3){
-        // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
-        var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]");
-        var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*");
-    } else {
-        var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
-        var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
-    }
-
-    var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
-
-    var myKeywords = commonKeywords, myBuiltins = commonBuiltins;
-    if(parserConf.extra_keywords != undefined){
-      myKeywords = myKeywords.concat(parserConf.extra_keywords);
-    }
-    if(parserConf.extra_builtins != undefined){
-      myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
-    }
-    if (parserConf.version && parseInt(parserConf.version, 10) == 3) {
-      myKeywords = myKeywords.concat(py3.keywords);
-      myBuiltins = myBuiltins.concat(py3.builtins);
-      var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
-    } else {
-      myKeywords = myKeywords.concat(py2.keywords);
-      myBuiltins = myBuiltins.concat(py2.builtins);
-      var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
-    }
-    var keywords = wordRegexp(myKeywords);
-    var builtins = wordRegexp(myBuiltins);
-
-    // tokenizers
-    function tokenBase(stream, state) {
-      // Handle scope changes
-      if (stream.sol() && top(state).type == "py") {
-        var scopeOffset = top(state).offset;
-        if (stream.eatSpace()) {
-          var lineOffset = stream.indentation();
-          if (lineOffset > scopeOffset)
-            pushScope(stream, state, "py");
-          else if (lineOffset < scopeOffset && dedent(stream, state))
-            state.errorToken = true;
-          return null;
-        } else {
-          var style = tokenBaseInner(stream, state);
-          if (scopeOffset > 0 && dedent(stream, state))
-            style += " " + ERRORCLASS;
-          return style;
-        }
-      }
-      return tokenBaseInner(stream, state);
-    }
-
-    function tokenBaseInner(stream, state) {
-      if (stream.eatSpace()) return null;
-
-      var ch = stream.peek();
-
-      // Handle Comments
-      if (ch == "#") {
-        stream.skipToEnd();
-        return "comment";
-      }
-
-      // Handle Number Literals
-      if (stream.match(/^[0-9\.]/, false)) {
-        var floatLiteral = false;
-        // Floats
-        if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
-        if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
-        if (stream.match(/^\.\d+/)) { floatLiteral = true; }
-        if (floatLiteral) {
-          // Float literals may be "imaginary"
-          stream.eat(/J/i);
-          return "number";
-        }
-        // Integers
-        var intLiteral = false;
-        // Hex
-        if (stream.match(/^0x[0-9a-f]+/i)) intLiteral = true;
-        // Binary
-        if (stream.match(/^0b[01]+/i)) intLiteral = true;
-        // Octal
-        if (stream.match(/^0o[0-7]+/i)) intLiteral = true;
-        // Decimal
-        if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
-          // Decimal literals may be "imaginary"
-          stream.eat(/J/i);
-          // TODO - Can you have imaginary longs?
-          intLiteral = true;
-        }
-        // Zero by itself with no other piece of number.
-        if (stream.match(/^0(?![\dx])/i)) intLiteral = true;
-        if (intLiteral) {
-          // Integer literals may be "long"
-          stream.eat(/L/i);
-          return "number";
-        }
-      }
-
-      // Handle Strings
-      if (stream.match(stringPrefixes)) {
-        state.tokenize = tokenStringFactory(stream.current());
-        return state.tokenize(stream, state);
-      }
-
-      // Handle operators and Delimiters
-      if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
-        return null;
-
-      if (stream.match(doubleOperators)
-          || stream.match(singleOperators)
-          || stream.match(wordOperators))
-        return "operator";
-
-      if (stream.match(singleDelimiters))
-        return null;
-
-      if (stream.match(keywords))
-        return "keyword";
-
-      if (stream.match(builtins))
-        return "builtin";
-
-      if (stream.match(/^(self|cls)\b/))
-        return "variable-2";
-
-      if (stream.match(identifiers)) {
-        if (state.lastToken == "def" || state.lastToken == "class")
-          return "def";
-        return "variable";
-      }
-
-      // Handle non-detected items
-      stream.next();
-      return ERRORCLASS;
-    }
-
-    function tokenStringFactory(delimiter) {
-      while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
-        delimiter = delimiter.substr(1);
-
-      var singleline = delimiter.length == 1;
-      var OUTCLASS = "string";
-
-      function tokenString(stream, state) {
-        while (!stream.eol()) {
-          stream.eatWhile(/[^'"\\]/);
-          if (stream.eat("\\")) {
-            stream.next();
-            if (singleline && stream.eol())
-              return OUTCLASS;
-          } else if (stream.match(delimiter)) {
-            state.tokenize = tokenBase;
-            return OUTCLASS;
-          } else {
-            stream.eat(/['"]/);
-          }
-        }
-        if (singleline) {
-          if (parserConf.singleLineStringErrors)
-            return ERRORCLASS;
-          else
-            state.tokenize = tokenBase;
-        }
-        return OUTCLASS;
-      }
-      tokenString.isString = true;
-      return tokenString;
-    }
-
-    function pushScope(stream, state, type) {
-      var offset = 0, align = null;
-      if (type == "py") {
-        while (top(state).type != "py")
-          state.scopes.pop();
-      }
-      offset = top(state).offset + (type == "py" ? conf.indentUnit : hangingIndent);
-      if (type != "py" && !stream.match(/^(\s|#.*)*$/, false))
-        align = stream.column() + 1;
-      state.scopes.push({offset: offset, type: type, align: align});
-    }
-
-    function dedent(stream, state) {
-      var indented = stream.indentation();
-      while (top(state).offset > indented) {
-        if (top(state).type != "py") return true;
-        state.scopes.pop();
-      }
-      return top(state).offset != indented;
-    }
-
-    function tokenLexer(stream, state) {
-      var style = state.tokenize(stream, state);
-      var current = stream.current();
-
-      // Handle '.' connected identifiers
-      if (current == ".") {
-        style = stream.match(identifiers, false) ? null : ERRORCLASS;
-        if (style == null && state.lastStyle == "meta") {
-          // Apply 'meta' style to '.' connected identifiers when
-          // appropriate.
-          style = "meta";
-        }
-        return style;
-      }
-
-      // Handle decorators
-      if (current == "@"){
-        if(parserConf.version && parseInt(parserConf.version, 10) == 3){
-            return stream.match(identifiers, false) ? "meta" : "operator";
-        } else {
-            return stream.match(identifiers, false) ? "meta" : ERRORCLASS;
-        }
-      }
-
-      if ((style == "variable" || style == "builtin")
-          && state.lastStyle == "meta")
-        style = "meta";
-
-      // Handle scope changes.
-      if (current == "pass" || current == "return")
-        state.dedent += 1;
-
-      if (current == "lambda") state.lambda = true;
-      if (current == ":" && !state.lambda && top(state).type == "py")
-        pushScope(stream, state, "py");
-
-      var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1;
-      if (delimiter_index != -1)
-        pushScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
-
-      delimiter_index = "])}".indexOf(current);
-      if (delimiter_index != -1) {
-        if (top(state).type == current) state.scopes.pop();
-        else return ERRORCLASS;
-      }
-      if (state.dedent > 0 && stream.eol() && top(state).type == "py") {
-        if (state.scopes.length > 1) state.scopes.pop();
-        state.dedent -= 1;
-      }
-
-      return style;
-    }
-
-    var external = {
-      startState: function(basecolumn) {
-        return {
-          tokenize: tokenBase,
-          scopes: [{offset: basecolumn || 0, type: "py", align: null}],
-          lastStyle: null,
-          lastToken: null,
-          lambda: false,
-          dedent: 0
-        };
-      },
-
-      token: function(stream, state) {
-        var addErr = state.errorToken;
-        if (addErr) state.errorToken = false;
-        var style = tokenLexer(stream, state);
-
-        state.lastStyle = style;
-
-        var current = stream.current();
-        if (current && style)
-          state.lastToken = current;
-
-        if (stream.eol() && state.lambda)
-          state.lambda = false;
-        return addErr ? style + " " + ERRORCLASS : style;
-      },
-
-      indent: function(state, textAfter) {
-        if (state.tokenize != tokenBase)
-          return state.tokenize.isString ? CodeMirror.Pass : 0;
-
-        var scope = top(state);
-        var closing = textAfter && textAfter.charAt(0) == scope.type;
-        if (scope.align != null)
-          return scope.align - (closing ? 1 : 0);
-        else if (closing && state.scopes.length > 1)
-          return state.scopes[state.scopes.length - 2].offset;
-        else
-          return scope.offset;
-      },
-
-      lineComment: "#",
-      fold: "indent"
-    };
-    return external;
-  });
-
-  CodeMirror.defineMIME("text/x-python", "python");
-
-  var words = function(str) { return str.split(" "); };
-
-  CodeMirror.defineMIME("text/x-cython", {
-    name: "python",
-    extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+
-                          "extern gil include nogil property public"+
-                          "readonly struct union DEF IF ELIF ELSE")
-  });
-
-});
--- a/kallithea/public/codemirror/mode/q/q.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("q",function(config){
-  var indentUnit=config.indentUnit,
-      curPunc,
-      keywords=buildRE(["abs","acos","aj","aj0","all","and","any","asc","asin","asof","atan","attr","avg","avgs","bin","by","ceiling","cols","cor","cos","count","cov","cross","csv","cut","delete","deltas","desc","dev","differ","distinct","div","do","each","ej","enlist","eval","except","exec","exit","exp","fby","fills","first","fkeys","flip","floor","from","get","getenv","group","gtime","hclose","hcount","hdel","hopen","hsym","iasc","idesc","if","ij","in","insert","inter","inv","key","keys","last","like","list","lj","load","log","lower","lsq","ltime","ltrim","mavg","max","maxs","mcount","md5","mdev","med","meta","min","mins","mmax","mmin","mmu","mod","msum","neg","next","not","null","or","over","parse","peach","pj","plist","prd","prds","prev","prior","rand","rank","ratios","raze","read0","read1","reciprocal","reverse","rload","rotate","rsave","rtrim","save","scan","select","set","setenv","show","signum","sin","sqrt","ss","ssr","string","sublist","sum","sums","sv","system","tables","tan","til","trim","txf","type","uj","ungroup","union","update","upper","upsert","value","var","view","views","vs","wavg","where","where","while","within","wj","wj1","wsum","xasc","xbar","xcol","xcols","xdesc","xexp","xgroup","xkey","xlog","xprev","xrank"]),
-      E=/[|/&^!+:\\\-*%$=~#;@><,?_\'\"\[\(\]\)\s{}]/;
-  function buildRE(w){return new RegExp("^("+w.join("|")+")$");}
-  function tokenBase(stream,state){
-    var sol=stream.sol(),c=stream.next();
-    curPunc=null;
-    if(sol)
-      if(c=="/")
-        return(state.tokenize=tokenLineComment)(stream,state);
-      else if(c=="\\"){
-        if(stream.eol()||/\s/.test(stream.peek()))
-          return stream.skipToEnd(),/^\\\s*$/.test(stream.current())?(state.tokenize=tokenCommentToEOF)(stream, state):state.tokenize=tokenBase,"comment";
-        else
-          return state.tokenize=tokenBase,"builtin";
-      }
-    if(/\s/.test(c))
-      return stream.peek()=="/"?(stream.skipToEnd(),"comment"):"whitespace";
-    if(c=='"')
-      return(state.tokenize=tokenString)(stream,state);
-    if(c=='`')
-      return stream.eatWhile(/[A-Z|a-z|\d|_|:|\/|\.]/),"symbol";
-    if(("."==c&&/\d/.test(stream.peek()))||/\d/.test(c)){
-      var t=null;
-      stream.backUp(1);
-      if(stream.match(/^\d{4}\.\d{2}(m|\.\d{2}([D|T](\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)?)?)/)
-      || stream.match(/^\d+D(\d{2}(:\d{2}(:\d{2}(\.\d{1,9})?)?)?)/)
-      || stream.match(/^\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?/)
-      || stream.match(/^\d+[ptuv]{1}/))
-        t="temporal";
-      else if(stream.match(/^0[NwW]{1}/)
-      || stream.match(/^0x[\d|a-f|A-F]*/)
-      || stream.match(/^[0|1]+[b]{1}/)
-      || stream.match(/^\d+[chijn]{1}/)
-      || stream.match(/-?\d*(\.\d*)?(e[+\-]?\d+)?(e|f)?/))
-        t="number";
-      return(t&&(!(c=stream.peek())||E.test(c)))?t:(stream.next(),"error");
-    }
-    if(/[A-Z|a-z]|\./.test(c))
-      return stream.eatWhile(/[A-Z|a-z|\.|_|\d]/),keywords.test(stream.current())?"keyword":"variable";
-    if(/[|/&^!+:\\\-*%$=~#;@><\.,?_\']/.test(c))
-      return null;
-    if(/[{}\(\[\]\)]/.test(c))
-      return null;
-    return"error";
-  }
-  function tokenLineComment(stream,state){
-    return stream.skipToEnd(),/\/\s*$/.test(stream.current())?(state.tokenize=tokenBlockComment)(stream,state):(state.tokenize=tokenBase),"comment";
-  }
-  function tokenBlockComment(stream,state){
-    var f=stream.sol()&&stream.peek()=="\\";
-    stream.skipToEnd();
-    if(f&&/^\\\s*$/.test(stream.current()))
-      state.tokenize=tokenBase;
-    return"comment";
-  }
-  function tokenCommentToEOF(stream){return stream.skipToEnd(),"comment";}
-  function tokenString(stream,state){
-    var escaped=false,next,end=false;
-    while((next=stream.next())){
-      if(next=="\""&&!escaped){end=true;break;}
-      escaped=!escaped&&next=="\\";
-    }
-    if(end)state.tokenize=tokenBase;
-    return"string";
-  }
-  function pushContext(state,type,col){state.context={prev:state.context,indent:state.indent,col:col,type:type};}
-  function popContext(state){state.indent=state.context.indent;state.context=state.context.prev;}
-  return{
-    startState:function(){
-      return{tokenize:tokenBase,
-             context:null,
-             indent:0,
-             col:0};
-    },
-    token:function(stream,state){
-      if(stream.sol()){
-        if(state.context&&state.context.align==null)
-          state.context.align=false;
-        state.indent=stream.indentation();
-      }
-      //if (stream.eatSpace()) return null;
-      var style=state.tokenize(stream,state);
-      if(style!="comment"&&state.context&&state.context.align==null&&state.context.type!="pattern"){
-        state.context.align=true;
-      }
-      if(curPunc=="(")pushContext(state,")",stream.column());
-      else if(curPunc=="[")pushContext(state,"]",stream.column());
-      else if(curPunc=="{")pushContext(state,"}",stream.column());
-      else if(/[\]\}\)]/.test(curPunc)){
-        while(state.context&&state.context.type=="pattern")popContext(state);
-        if(state.context&&curPunc==state.context.type)popContext(state);
-      }
-      else if(curPunc=="."&&state.context&&state.context.type=="pattern")popContext(state);
-      else if(/atom|string|variable/.test(style)&&state.context){
-        if(/[\}\]]/.test(state.context.type))
-          pushContext(state,"pattern",stream.column());
-        else if(state.context.type=="pattern"&&!state.context.align){
-          state.context.align=true;
-          state.context.col=stream.column();
-        }
-      }
-      return style;
-    },
-    indent:function(state,textAfter){
-      var firstChar=textAfter&&textAfter.charAt(0);
-      var context=state.context;
-      if(/[\]\}]/.test(firstChar))
-        while (context&&context.type=="pattern")context=context.prev;
-      var closing=context&&firstChar==context.type;
-      if(!context)
-        return 0;
-      else if(context.type=="pattern")
-        return context.col;
-      else if(context.align)
-        return context.col+(closing?0:1);
-      else
-        return context.indent+(closing?0:indentUnit);
-    }
-  };
-});
-CodeMirror.defineMIME("text/x-q","q");
-
-});
--- a/kallithea/public/codemirror/mode/r/r.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("r", function(config) {
-  function wordObj(str) {
-    var words = str.split(" "), res = {};
-    for (var i = 0; i < words.length; ++i) res[words[i]] = true;
-    return res;
-  }
-  var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_");
-  var builtins = wordObj("list quote bquote eval return call parse deparse");
-  var keywords = wordObj("if else repeat while function for in next break");
-  var blockkeywords = wordObj("if else repeat while function for");
-  var opChars = /[+\-*\/^<>=!&|~$:]/;
-  var curPunc;
-
-  function tokenBase(stream, state) {
-    curPunc = null;
-    var ch = stream.next();
-    if (ch == "#") {
-      stream.skipToEnd();
-      return "comment";
-    } else if (ch == "0" && stream.eat("x")) {
-      stream.eatWhile(/[\da-f]/i);
-      return "number";
-    } else if (ch == "." && stream.eat(/\d/)) {
-      stream.match(/\d*(?:e[+\-]?\d+)?/);
-      return "number";
-    } else if (/\d/.test(ch)) {
-      stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/);
-      return "number";
-    } else if (ch == "'" || ch == '"') {
-      state.tokenize = tokenString(ch);
-      return "string";
-    } else if (ch == "." && stream.match(/.[.\d]+/)) {
-      return "keyword";
-    } else if (/[\w\.]/.test(ch) && ch != "_") {
-      stream.eatWhile(/[\w\.]/);
-      var word = stream.current();
-      if (atoms.propertyIsEnumerable(word)) return "atom";
-      if (keywords.propertyIsEnumerable(word)) {
-        // Block keywords start new blocks, except 'else if', which only starts
-        // one new block for the 'if', no block for the 'else'.
-        if (blockkeywords.propertyIsEnumerable(word) &&
-            !stream.match(/\s*if(\s+|$)/, false))
-          curPunc = "block";
-        return "keyword";
-      }
-      if (builtins.propertyIsEnumerable(word)) return "builtin";
-      return "variable";
-    } else if (ch == "%") {
-      if (stream.skipTo("%")) stream.next();
-      return "variable-2";
-    } else if (ch == "<" && stream.eat("-")) {
-      return "arrow";
-    } else if (ch == "=" && state.ctx.argList) {
-      return "arg-is";
-    } else if (opChars.test(ch)) {
-      if (ch == "$") return "dollar";
-      stream.eatWhile(opChars);
-      return "operator";
-    } else if (/[\(\){}\[\];]/.test(ch)) {
-      curPunc = ch;
-      if (ch == ";") return "semi";
-      return null;
-    } else {
-      return null;
-    }
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      if (stream.eat("\\")) {
-        var ch = stream.next();
-        if (ch == "x") stream.match(/^[a-f0-9]{2}/i);
-        else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next();
-        else if (ch == "u") stream.match(/^[a-f0-9]{4}/i);
-        else if (ch == "U") stream.match(/^[a-f0-9]{8}/i);
-        else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/);
-        return "string-2";
-      } else {
-        var next;
-        while ((next = stream.next()) != null) {
-          if (next == quote) { state.tokenize = tokenBase; break; }
-          if (next == "\\") { stream.backUp(1); break; }
-        }
-        return "string";
-      }
-    };
-  }
-
-  function push(state, type, stream) {
-    state.ctx = {type: type,
-                 indent: state.indent,
-                 align: null,
-                 column: stream.column(),
-                 prev: state.ctx};
-  }
-  function pop(state) {
-    state.indent = state.ctx.indent;
-    state.ctx = state.ctx.prev;
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: tokenBase,
-              ctx: {type: "top",
-                    indent: -config.indentUnit,
-                    align: false},
-              indent: 0,
-              afterIdent: false};
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (state.ctx.align == null) state.ctx.align = false;
-        state.indent = stream.indentation();
-      }
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      if (style != "comment" && state.ctx.align == null) state.ctx.align = true;
-
-      var ctype = state.ctx.type;
-      if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state);
-      if (curPunc == "{") push(state, "}", stream);
-      else if (curPunc == "(") {
-        push(state, ")", stream);
-        if (state.afterIdent) state.ctx.argList = true;
-      }
-      else if (curPunc == "[") push(state, "]", stream);
-      else if (curPunc == "block") push(state, "block", stream);
-      else if (curPunc == ctype) pop(state);
-      state.afterIdent = style == "variable" || style == "keyword";
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx,
-          closing = firstChar == ctx.type;
-      if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit);
-      else if (ctx.align) return ctx.column + (closing ? 0 : 1);
-      else return ctx.indent + (closing ? 0 : config.indentUnit);
-    },
-
-    lineComment: "#"
-  };
-});
-
-CodeMirror.defineMIME("text/x-rsrc", "r");
-
-});
--- a/kallithea/public/codemirror/mode/rpm/rpm.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("rpm-changes", function() {
-  var headerSeperator = /^-+$/;
-  var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)  ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /;
-  var simpleEmail = /^[\w+.-]+@[\w.-]+/;
-
-  return {
-    token: function(stream) {
-      if (stream.sol()) {
-        if (stream.match(headerSeperator)) { return 'tag'; }
-        if (stream.match(headerLine)) { return 'tag'; }
-      }
-      if (stream.match(simpleEmail)) { return 'string'; }
-      stream.next();
-      return null;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes");
-
-// Quick and dirty spec file highlighting
-
-CodeMirror.defineMode("rpm-spec", function() {
-  var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
-
-  var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/;
-  var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
-  var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros
-  var control_flow_simple = /^%(else|endif)/; // rpm control flow macros
-  var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros
-
-  return {
-    startState: function () {
-        return {
-          controlFlow: false,
-          macroParameters: false,
-          section: false
-        };
-    },
-    token: function (stream, state) {
-      var ch = stream.peek();
-      if (ch == "#") { stream.skipToEnd(); return "comment"; }
-
-      if (stream.sol()) {
-        if (stream.match(preamble)) { return "preamble"; }
-        if (stream.match(section)) { return "section"; }
-      }
-
-      if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT'
-      if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}'
-
-      if (stream.match(control_flow_simple)) { return "keyword"; }
-      if (stream.match(control_flow_complex)) {
-        state.controlFlow = true;
-        return "keyword";
-      }
-      if (state.controlFlow) {
-        if (stream.match(operators)) { return "operator"; }
-        if (stream.match(/^(\d+)/)) { return "number"; }
-        if (stream.eol()) { state.controlFlow = false; }
-      }
-
-      if (stream.match(arch)) { return "number"; }
-
-      // Macros like '%make_install' or '%attr(0775,root,root)'
-      if (stream.match(/^%[\w]+/)) {
-        if (stream.match(/^\(/)) { state.macroParameters = true; }
-        return "macro";
-      }
-      if (state.macroParameters) {
-        if (stream.match(/^\d+/)) { return "number";}
-        if (stream.match(/^\)/)) {
-          state.macroParameters = false;
-          return "macro";
-        }
-      }
-      if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}'
-
-      //TODO: Include bash script sub-parser (CodeMirror supports that)
-      stream.next();
-      return null;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-rpm-spec", "rpm-spec");
-
-});
--- a/kallithea/public/codemirror/mode/rst/rst.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,557 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../python/python"), require("../stex/stex"), require("../../addon/mode/overlay"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../python/python", "../stex/stex", "../../addon/mode/overlay"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('rst', function (config, options) {
-
-  var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/;
-  var rx_emphasis = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/;
-  var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``/;
-
-  var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/;
-  var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/;
-  var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/;
-
-  var rx_uri_protocol = "[Hh][Tt][Tt][Pp][Ss]?://";
-  var rx_uri_domain = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})";
-  var rx_uri_path = "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*";
-  var rx_uri = new RegExp("^" + rx_uri_protocol + rx_uri_domain + rx_uri_path);
-
-  var overlay = {
-    token: function (stream) {
-
-      if (stream.match(rx_strong) && stream.match (/\W+|$/, false))
-        return 'strong';
-      if (stream.match(rx_emphasis) && stream.match (/\W+|$/, false))
-        return 'em';
-      if (stream.match(rx_literal) && stream.match (/\W+|$/, false))
-        return 'string-2';
-      if (stream.match(rx_number))
-        return 'number';
-      if (stream.match(rx_positive))
-        return 'positive';
-      if (stream.match(rx_negative))
-        return 'negative';
-      if (stream.match(rx_uri))
-        return 'link';
-
-      while (stream.next() != null) {
-        if (stream.match(rx_strong, false)) break;
-        if (stream.match(rx_emphasis, false)) break;
-        if (stream.match(rx_literal, false)) break;
-        if (stream.match(rx_number, false)) break;
-        if (stream.match(rx_positive, false)) break;
-        if (stream.match(rx_negative, false)) break;
-        if (stream.match(rx_uri, false)) break;
-      }
-
-      return null;
-    }
-  };
-
-  var mode = CodeMirror.getMode(
-    config, options.backdrop || 'rst-base'
-  );
-
-  return CodeMirror.overlayMode(mode, overlay, true); // combine
-}, 'python', 'stex');
-
-///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-
-CodeMirror.defineMode('rst-base', function (config) {
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function format(string) {
-    var args = Array.prototype.slice.call(arguments, 1);
-    return string.replace(/{(\d+)}/g, function (match, n) {
-      return typeof args[n] != 'undefined' ? args[n] : match;
-    });
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  var mode_python = CodeMirror.getMode(config, 'python');
-  var mode_stex = CodeMirror.getMode(config, 'stex');
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  var SEPA = "\\s+";
-  var TAIL = "(?:\\s*|\\W|$)",
-  rx_TAIL = new RegExp(format('^{0}', TAIL));
-
-  var NAME =
-    "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)",
-  rx_NAME = new RegExp(format('^{0}', NAME));
-  var NAME_WWS =
-    "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)";
-  var REF_NAME = format('(?:{0}|`{1}`)', NAME, NAME_WWS);
-
-  var TEXT1 = "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)";
-  var TEXT2 = "(?:[^\\`]+)",
-  rx_TEXT2 = new RegExp(format('^{0}', TEXT2));
-
-  var rx_section = new RegExp(
-    "^([!'#$%&\"()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~])\\1{3,}\\s*$");
-  var rx_explicit = new RegExp(
-    format('^\\.\\.{0}', SEPA));
-  var rx_link = new RegExp(
-    format('^_{0}:{1}|^__:{1}', REF_NAME, TAIL));
-  var rx_directive = new RegExp(
-    format('^{0}::{1}', REF_NAME, TAIL));
-  var rx_substitution = new RegExp(
-    format('^\\|{0}\\|{1}{2}::{3}', TEXT1, SEPA, REF_NAME, TAIL));
-  var rx_footnote = new RegExp(
-    format('^\\[(?:\\d+|#{0}?|\\*)]{1}', REF_NAME, TAIL));
-  var rx_citation = new RegExp(
-    format('^\\[{0}\\]{1}', REF_NAME, TAIL));
-
-  var rx_substitution_ref = new RegExp(
-    format('^\\|{0}\\|', TEXT1));
-  var rx_footnote_ref = new RegExp(
-    format('^\\[(?:\\d+|#{0}?|\\*)]_', REF_NAME));
-  var rx_citation_ref = new RegExp(
-    format('^\\[{0}\\]_', REF_NAME));
-  var rx_link_ref1 = new RegExp(
-    format('^{0}__?', REF_NAME));
-  var rx_link_ref2 = new RegExp(
-    format('^`{0}`_', TEXT2));
-
-  var rx_role_pre = new RegExp(
-    format('^:{0}:`{1}`{2}', NAME, TEXT2, TAIL));
-  var rx_role_suf = new RegExp(
-    format('^`{1}`:{0}:{2}', NAME, TEXT2, TAIL));
-  var rx_role = new RegExp(
-    format('^:{0}:{1}', NAME, TAIL));
-
-  var rx_directive_name = new RegExp(format('^{0}', REF_NAME));
-  var rx_directive_tail = new RegExp(format('^::{0}', TAIL));
-  var rx_substitution_text = new RegExp(format('^\\|{0}\\|', TEXT1));
-  var rx_substitution_sepa = new RegExp(format('^{0}', SEPA));
-  var rx_substitution_name = new RegExp(format('^{0}', REF_NAME));
-  var rx_substitution_tail = new RegExp(format('^::{0}', TAIL));
-  var rx_link_head = new RegExp("^_");
-  var rx_link_name = new RegExp(format('^{0}|_', REF_NAME));
-  var rx_link_tail = new RegExp(format('^:{0}', TAIL));
-
-  var rx_verbatim = new RegExp('^::\\s*$');
-  var rx_examples = new RegExp('^\\s+(?:>>>|In \\[\\d+\\]:)\\s');
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function to_normal(stream, state) {
-    var token = null;
-
-    if (stream.sol() && stream.match(rx_examples, false)) {
-      change(state, to_mode, {
-        mode: mode_python, local: CodeMirror.startState(mode_python)
-      });
-    } else if (stream.sol() && stream.match(rx_explicit)) {
-      change(state, to_explicit);
-      token = 'meta';
-    } else if (stream.sol() && stream.match(rx_section)) {
-      change(state, to_normal);
-      token = 'header';
-    } else if (phase(state) == rx_role_pre ||
-               stream.match(rx_role_pre, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_normal, context(rx_role_pre, 1));
-        stream.match(/^:/);
-        token = 'meta';
-        break;
-      case 1:
-        change(state, to_normal, context(rx_role_pre, 2));
-        stream.match(rx_NAME);
-        token = 'keyword';
-
-        if (stream.current().match(/^(?:math|latex)/)) {
-          state.tmp_stex = true;
-        }
-        break;
-      case 2:
-        change(state, to_normal, context(rx_role_pre, 3));
-        stream.match(/^:`/);
-        token = 'meta';
-        break;
-      case 3:
-        if (state.tmp_stex) {
-          state.tmp_stex = undefined; state.tmp = {
-            mode: mode_stex, local: CodeMirror.startState(mode_stex)
-          };
-        }
-
-        if (state.tmp) {
-          if (stream.peek() == '`') {
-            change(state, to_normal, context(rx_role_pre, 4));
-            state.tmp = undefined;
-            break;
-          }
-
-          token = state.tmp.mode.token(stream, state.tmp.local);
-          break;
-        }
-
-        change(state, to_normal, context(rx_role_pre, 4));
-        stream.match(rx_TEXT2);
-        token = 'string';
-        break;
-      case 4:
-        change(state, to_normal, context(rx_role_pre, 5));
-        stream.match(/^`/);
-        token = 'meta';
-        break;
-      case 5:
-        change(state, to_normal, context(rx_role_pre, 6));
-        stream.match(rx_TAIL);
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (phase(state) == rx_role_suf ||
-               stream.match(rx_role_suf, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_normal, context(rx_role_suf, 1));
-        stream.match(/^`/);
-        token = 'meta';
-        break;
-      case 1:
-        change(state, to_normal, context(rx_role_suf, 2));
-        stream.match(rx_TEXT2);
-        token = 'string';
-        break;
-      case 2:
-        change(state, to_normal, context(rx_role_suf, 3));
-        stream.match(/^`:/);
-        token = 'meta';
-        break;
-      case 3:
-        change(state, to_normal, context(rx_role_suf, 4));
-        stream.match(rx_NAME);
-        token = 'keyword';
-        break;
-      case 4:
-        change(state, to_normal, context(rx_role_suf, 5));
-        stream.match(/^:/);
-        token = 'meta';
-        break;
-      case 5:
-        change(state, to_normal, context(rx_role_suf, 6));
-        stream.match(rx_TAIL);
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (phase(state) == rx_role || stream.match(rx_role, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_normal, context(rx_role, 1));
-        stream.match(/^:/);
-        token = 'meta';
-        break;
-      case 1:
-        change(state, to_normal, context(rx_role, 2));
-        stream.match(rx_NAME);
-        token = 'keyword';
-        break;
-      case 2:
-        change(state, to_normal, context(rx_role, 3));
-        stream.match(/^:/);
-        token = 'meta';
-        break;
-      case 3:
-        change(state, to_normal, context(rx_role, 4));
-        stream.match(rx_TAIL);
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (phase(state) == rx_substitution_ref ||
-               stream.match(rx_substitution_ref, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_normal, context(rx_substitution_ref, 1));
-        stream.match(rx_substitution_text);
-        token = 'variable-2';
-        break;
-      case 1:
-        change(state, to_normal, context(rx_substitution_ref, 2));
-        if (stream.match(/^_?_?/)) token = 'link';
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (stream.match(rx_footnote_ref)) {
-      change(state, to_normal);
-      token = 'quote';
-    } else if (stream.match(rx_citation_ref)) {
-      change(state, to_normal);
-      token = 'quote';
-    } else if (stream.match(rx_link_ref1)) {
-      change(state, to_normal);
-      if (!stream.peek() || stream.peek().match(/^\W$/)) {
-        token = 'link';
-      }
-    } else if (phase(state) == rx_link_ref2 ||
-               stream.match(rx_link_ref2, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        if (!stream.peek() || stream.peek().match(/^\W$/)) {
-          change(state, to_normal, context(rx_link_ref2, 1));
-        } else {
-          stream.match(rx_link_ref2);
-        }
-        break;
-      case 1:
-        change(state, to_normal, context(rx_link_ref2, 2));
-        stream.match(/^`/);
-        token = 'link';
-        break;
-      case 2:
-        change(state, to_normal, context(rx_link_ref2, 3));
-        stream.match(rx_TEXT2);
-        break;
-      case 3:
-        change(state, to_normal, context(rx_link_ref2, 4));
-        stream.match(/^`_/);
-        token = 'link';
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (stream.match(rx_verbatim)) {
-      change(state, to_verbatim);
-    }
-
-    else {
-      if (stream.next()) change(state, to_normal);
-    }
-
-    return token;
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function to_explicit(stream, state) {
-    var token = null;
-
-    if (phase(state) == rx_substitution ||
-        stream.match(rx_substitution, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_explicit, context(rx_substitution, 1));
-        stream.match(rx_substitution_text);
-        token = 'variable-2';
-        break;
-      case 1:
-        change(state, to_explicit, context(rx_substitution, 2));
-        stream.match(rx_substitution_sepa);
-        break;
-      case 2:
-        change(state, to_explicit, context(rx_substitution, 3));
-        stream.match(rx_substitution_name);
-        token = 'keyword';
-        break;
-      case 3:
-        change(state, to_explicit, context(rx_substitution, 4));
-        stream.match(rx_substitution_tail);
-        token = 'meta';
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (phase(state) == rx_directive ||
-               stream.match(rx_directive, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_explicit, context(rx_directive, 1));
-        stream.match(rx_directive_name);
-        token = 'keyword';
-
-        if (stream.current().match(/^(?:math|latex)/))
-          state.tmp_stex = true;
-        else if (stream.current().match(/^python/))
-          state.tmp_py = true;
-        break;
-      case 1:
-        change(state, to_explicit, context(rx_directive, 2));
-        stream.match(rx_directive_tail);
-        token = 'meta';
-
-        if (stream.match(/^latex\s*$/) || state.tmp_stex) {
-          state.tmp_stex = undefined; change(state, to_mode, {
-            mode: mode_stex, local: CodeMirror.startState(mode_stex)
-          });
-        }
-        break;
-      case 2:
-        change(state, to_explicit, context(rx_directive, 3));
-        if (stream.match(/^python\s*$/) || state.tmp_py) {
-          state.tmp_py = undefined; change(state, to_mode, {
-            mode: mode_python, local: CodeMirror.startState(mode_python)
-          });
-        }
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (phase(state) == rx_link || stream.match(rx_link, false)) {
-
-      switch (stage(state)) {
-      case 0:
-        change(state, to_explicit, context(rx_link, 1));
-        stream.match(rx_link_head);
-        stream.match(rx_link_name);
-        token = 'link';
-        break;
-      case 1:
-        change(state, to_explicit, context(rx_link, 2));
-        stream.match(rx_link_tail);
-        token = 'meta';
-        break;
-      default:
-        change(state, to_normal);
-      }
-    } else if (stream.match(rx_footnote)) {
-      change(state, to_normal);
-      token = 'quote';
-    } else if (stream.match(rx_citation)) {
-      change(state, to_normal);
-      token = 'quote';
-    }
-
-    else {
-      stream.eatSpace();
-      if (stream.eol()) {
-        change(state, to_normal);
-      } else {
-        stream.skipToEnd();
-        change(state, to_comment);
-        token = 'comment';
-      }
-    }
-
-    return token;
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function to_comment(stream, state) {
-    return as_block(stream, state, 'comment');
-  }
-
-  function to_verbatim(stream, state) {
-    return as_block(stream, state, 'meta');
-  }
-
-  function as_block(stream, state, token) {
-    if (stream.eol() || stream.eatSpace()) {
-      stream.skipToEnd();
-      return token;
-    } else {
-      change(state, to_normal);
-      return null;
-    }
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function to_mode(stream, state) {
-
-    if (state.ctx.mode && state.ctx.local) {
-
-      if (stream.sol()) {
-        if (!stream.eatSpace()) change(state, to_normal);
-        return null;
-      }
-
-      return state.ctx.mode.token(stream, state.ctx.local);
-    }
-
-    change(state, to_normal);
-    return null;
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  function context(phase, stage, mode, local) {
-    return {phase: phase, stage: stage, mode: mode, local: local};
-  }
-
-  function change(state, tok, ctx) {
-    state.tok = tok;
-    state.ctx = ctx || {};
-  }
-
-  function stage(state) {
-    return state.ctx.stage || 0;
-  }
-
-  function phase(state) {
-    return state.ctx.phase;
-  }
-
-  ///////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////
-
-  return {
-    startState: function () {
-      return {tok: to_normal, ctx: context(undefined, 0)};
-    },
-
-    copyState: function (state) {
-      var ctx = state.ctx, tmp = state.tmp;
-      if (ctx.local)
-        ctx = {mode: ctx.mode, local: CodeMirror.copyState(ctx.mode, ctx.local)};
-      if (tmp)
-        tmp = {mode: tmp.mode, local: CodeMirror.copyState(tmp.mode, tmp.local)};
-      return {tok: state.tok, ctx: ctx, tmp: tmp};
-    },
-
-    innerMode: function (state) {
-      return state.tmp      ? {state: state.tmp.local, mode: state.tmp.mode}
-      : state.ctx.mode ? {state: state.ctx.local, mode: state.ctx.mode}
-      : null;
-    },
-
-    token: function (stream, state) {
-      return state.tok(stream, state);
-    }
-  };
-}, 'python', 'stex');
-
-///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-
-CodeMirror.defineMIME('text/x-rst', 'rst');
-
-///////////////////////////////////////////////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-
-});
--- a/kallithea/public/codemirror/mode/ruby/ruby.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("ruby", function(config) {
-  function wordObj(words) {
-    var o = {};
-    for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
-    return o;
-  }
-  var keywords = wordObj([
-    "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
-    "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or",
-    "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
-    "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc",
-    "caller", "lambda", "proc", "public", "protected", "private", "require", "load",
-    "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
-  ]);
-  var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
-                             "catch", "loop", "proc", "begin"]);
-  var dedentWords = wordObj(["end", "until"]);
-  var matching = {"[": "]", "{": "}", "(": ")"};
-  var curPunc;
-
-  function chain(newtok, stream, state) {
-    state.tokenize.push(newtok);
-    return newtok(stream, state);
-  }
-
-  function tokenBase(stream, state) {
-    curPunc = null;
-    if (stream.sol() && stream.match("=begin") && stream.eol()) {
-      state.tokenize.push(readBlockComment);
-      return "comment";
-    }
-    if (stream.eatSpace()) return null;
-    var ch = stream.next(), m;
-    if (ch == "`" || ch == "'" || ch == '"') {
-      return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
-    } else if (ch == "/") {
-      var currentIndex = stream.current().length;
-      if (stream.skipTo("/")) {
-        var search_till = stream.current().length;
-        stream.backUp(stream.current().length - currentIndex);
-        var balance = 0;  // balance brackets
-        while (stream.current().length < search_till) {
-          var chchr = stream.next();
-          if (chchr == "(") balance += 1;
-          else if (chchr == ")") balance -= 1;
-          if (balance < 0) break;
-        }
-        stream.backUp(stream.current().length - currentIndex);
-        if (balance == 0)
-          return chain(readQuoted(ch, "string-2", true), stream, state);
-      }
-      return "operator";
-    } else if (ch == "%") {
-      var style = "string", embed = true;
-      if (stream.eat("s")) style = "atom";
-      else if (stream.eat(/[WQ]/)) style = "string";
-      else if (stream.eat(/[r]/)) style = "string-2";
-      else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
-      var delim = stream.eat(/[^\w\s=]/);
-      if (!delim) return "operator";
-      if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
-      return chain(readQuoted(delim, style, embed, true), stream, state);
-    } else if (ch == "#") {
-      stream.skipToEnd();
-      return "comment";
-    } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) {
-      return chain(readHereDoc(m[1]), stream, state);
-    } else if (ch == "0") {
-      if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/);
-      else if (stream.eat("b")) stream.eatWhile(/[01]/);
-      else stream.eatWhile(/[0-7]/);
-      return "number";
-    } else if (/\d/.test(ch)) {
-      stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/);
-      return "number";
-    } else if (ch == "?") {
-      while (stream.match(/^\\[CM]-/)) {}
-      if (stream.eat("\\")) stream.eatWhile(/\w/);
-      else stream.next();
-      return "string";
-    } else if (ch == ":") {
-      if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state);
-      if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state);
-
-      // :> :>> :< :<< are valid symbols
-      if (stream.eat(/[\<\>]/)) {
-        stream.eat(/[\<\>]/);
-        return "atom";
-      }
-
-      // :+ :- :/ :* :| :& :! are valid symbols
-      if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) {
-        return "atom";
-      }
-
-      // Symbols can't start by a digit
-      if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) {
-        stream.eatWhile(/[\w$\xa1-\uffff]/);
-        // Only one ? ! = is allowed and only as the last character
-        stream.eat(/[\?\!\=]/);
-        return "atom";
-      }
-      return "operator";
-    } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) {
-      stream.eat("@");
-      stream.eatWhile(/[\w\xa1-\uffff]/);
-      return "variable-2";
-    } else if (ch == "$") {
-      if (stream.eat(/[a-zA-Z_]/)) {
-        stream.eatWhile(/[\w]/);
-      } else if (stream.eat(/\d/)) {
-        stream.eat(/\d/);
-      } else {
-        stream.next(); // Must be a special global like $: or $!
-      }
-      return "variable-3";
-    } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) {
-      stream.eatWhile(/[\w\xa1-\uffff]/);
-      stream.eat(/[\?\!]/);
-      if (stream.eat(":")) return "atom";
-      return "ident";
-    } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) {
-      curPunc = "|";
-      return null;
-    } else if (/[\(\)\[\]{}\\;]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    } else if (ch == "-" && stream.eat(">")) {
-      return "arrow";
-    } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
-      var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
-      if (ch == "." && !more) curPunc = ".";
-      return "operator";
-    } else {
-      return null;
-    }
-  }
-
-  function tokenBaseUntilBrace(depth) {
-    if (!depth) depth = 1;
-    return function(stream, state) {
-      if (stream.peek() == "}") {
-        if (depth == 1) {
-          state.tokenize.pop();
-          return state.tokenize[state.tokenize.length-1](stream, state);
-        } else {
-          state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1);
-        }
-      } else if (stream.peek() == "{") {
-        state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1);
-      }
-      return tokenBase(stream, state);
-    };
-  }
-  function tokenBaseOnce() {
-    var alreadyCalled = false;
-    return function(stream, state) {
-      if (alreadyCalled) {
-        state.tokenize.pop();
-        return state.tokenize[state.tokenize.length-1](stream, state);
-      }
-      alreadyCalled = true;
-      return tokenBase(stream, state);
-    };
-  }
-  function readQuoted(quote, style, embed, unescaped) {
-    return function(stream, state) {
-      var escaped = false, ch;
-
-      if (state.context.type === 'read-quoted-paused') {
-        state.context = state.context.prev;
-        stream.eat("}");
-      }
-
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && (unescaped || !escaped)) {
-          state.tokenize.pop();
-          break;
-        }
-        if (embed && ch == "#" && !escaped) {
-          if (stream.eat("{")) {
-            if (quote == "}") {
-              state.context = {prev: state.context, type: 'read-quoted-paused'};
-            }
-            state.tokenize.push(tokenBaseUntilBrace());
-            break;
-          } else if (/[@\$]/.test(stream.peek())) {
-            state.tokenize.push(tokenBaseOnce());
-            break;
-          }
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      return style;
-    };
-  }
-  function readHereDoc(phrase) {
-    return function(stream, state) {
-      if (stream.match(phrase)) state.tokenize.pop();
-      else stream.skipToEnd();
-      return "string";
-    };
-  }
-  function readBlockComment(stream, state) {
-    if (stream.sol() && stream.match("=end") && stream.eol())
-      state.tokenize.pop();
-    stream.skipToEnd();
-    return "comment";
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: [tokenBase],
-              indented: 0,
-              context: {type: "top", indented: -config.indentUnit},
-              continuedLine: false,
-              lastTok: null,
-              varList: false};
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) state.indented = stream.indentation();
-      var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
-      var thisTok = curPunc;
-      if (style == "ident") {
-        var word = stream.current();
-        style = state.lastTok == "." ? "property"
-          : keywords.propertyIsEnumerable(stream.current()) ? "keyword"
-          : /^[A-Z]/.test(word) ? "tag"
-          : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
-          : "variable";
-        if (style == "keyword") {
-          thisTok = word;
-          if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
-          else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
-          else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
-            kwtype = "indent";
-          else if (word == "do" && state.context.indented < state.indented)
-            kwtype = "indent";
-        }
-      }
-      if (curPunc || (style && style != "comment")) state.lastTok = thisTok;
-      if (curPunc == "|") state.varList = !state.varList;
-
-      if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
-        state.context = {prev: state.context, type: curPunc || style, indented: state.indented};
-      else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev)
-        state.context = state.context.prev;
-
-      if (stream.eol())
-        state.continuedLine = (curPunc == "\\" || style == "operator");
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0);
-      var ct = state.context;
-      var closing = ct.type == matching[firstChar] ||
-        ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
-      return ct.indented + (closing ? 0 : config.indentUnit) +
-        (state.continuedLine ? config.indentUnit : 0);
-    },
-
-    electricChars: "}de", // enD and rescuE
-    lineComment: "#"
-  };
-});
-
-CodeMirror.defineMIME("text/x-ruby", "ruby");
-
-});
--- a/kallithea/public/codemirror/mode/rust/rust.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,451 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("rust", function() {
-  var indentUnit = 4, altIndentUnit = 2;
-  var valKeywords = {
-    "if": "if-style", "while": "if-style", "loop": "else-style", "else": "else-style",
-    "do": "else-style", "ret": "else-style", "fail": "else-style",
-    "break": "atom", "cont": "atom", "const": "let", "resource": "fn",
-    "let": "let", "fn": "fn", "for": "for", "alt": "alt", "iface": "iface",
-    "impl": "impl", "type": "type", "enum": "enum", "mod": "mod",
-    "as": "op", "true": "atom", "false": "atom", "assert": "op", "check": "op",
-    "claim": "op", "native": "ignore", "unsafe": "ignore", "import": "else-style",
-    "export": "else-style", "copy": "op", "log": "op", "log_err": "op",
-    "use": "op", "bind": "op", "self": "atom", "struct": "enum"
-  };
-  var typeKeywords = function() {
-    var keywords = {"fn": "fn", "block": "fn", "obj": "obj"};
-    var atoms = "bool uint int i8 i16 i32 i64 u8 u16 u32 u64 float f32 f64 str char".split(" ");
-    for (var i = 0, e = atoms.length; i < e; ++i) keywords[atoms[i]] = "atom";
-    return keywords;
-  }();
-  var operatorChar = /[+\-*&%=<>!?|\.@]/;
-
-  // Tokenizer
-
-  // Used as scratch variable to communicate multiple values without
-  // consing up tons of objects.
-  var tcat, content;
-  function r(tc, style) {
-    tcat = tc;
-    return style;
-  }
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"') {
-      state.tokenize = tokenString;
-      return state.tokenize(stream, state);
-    }
-    if (ch == "'") {
-      tcat = "atom";
-      if (stream.eat("\\")) {
-        if (stream.skipTo("'")) { stream.next(); return "string"; }
-        else { return "error"; }
-      } else {
-        stream.next();
-        return stream.eat("'") ? "string" : "error";
-      }
-    }
-    if (ch == "/") {
-      if (stream.eat("/")) { stream.skipToEnd(); return "comment"; }
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment(1);
-        return state.tokenize(stream, state);
-      }
-    }
-    if (ch == "#") {
-      if (stream.eat("[")) { tcat = "open-attr"; return null; }
-      stream.eatWhile(/\w/);
-      return r("macro", "meta");
-    }
-    if (ch == ":" && stream.match(":<")) {
-      return r("op", null);
-    }
-    if (ch.match(/\d/) || (ch == "." && stream.eat(/\d/))) {
-      var flp = false;
-      if (!stream.match(/^x[\da-f]+/i) && !stream.match(/^b[01]+/)) {
-        stream.eatWhile(/\d/);
-        if (stream.eat(".")) { flp = true; stream.eatWhile(/\d/); }
-        if (stream.match(/^e[+\-]?\d+/i)) { flp = true; }
-      }
-      if (flp) stream.match(/^f(?:32|64)/);
-      else stream.match(/^[ui](?:8|16|32|64)/);
-      return r("atom", "number");
-    }
-    if (ch.match(/[()\[\]{}:;,]/)) return r(ch, null);
-    if (ch == "-" && stream.eat(">")) return r("->", null);
-    if (ch.match(operatorChar)) {
-      stream.eatWhile(operatorChar);
-      return r("op", null);
-    }
-    stream.eatWhile(/\w/);
-    content = stream.current();
-    if (stream.match(/^::\w/)) {
-      stream.backUp(1);
-      return r("prefix", "variable-2");
-    }
-    if (state.keywords.propertyIsEnumerable(content))
-      return r(state.keywords[content], content.match(/true|false/) ? "atom" : "keyword");
-    return r("name", "variable");
-  }
-
-  function tokenString(stream, state) {
-    var ch, escaped = false;
-    while (ch = stream.next()) {
-      if (ch == '"' && !escaped) {
-        state.tokenize = tokenBase;
-        return r("atom", "string");
-      }
-      escaped = !escaped && ch == "\\";
-    }
-    // Hack to not confuse the parser when a string is split in
-    // pieces.
-    return r("op", "string");
-  }
-
-  function tokenComment(depth) {
-    return function(stream, state) {
-      var lastCh = null, ch;
-      while (ch = stream.next()) {
-        if (ch == "/" && lastCh == "*") {
-          if (depth == 1) {
-            state.tokenize = tokenBase;
-            break;
-          } else {
-            state.tokenize = tokenComment(depth - 1);
-            return state.tokenize(stream, state);
-          }
-        }
-        if (ch == "*" && lastCh == "/") {
-          state.tokenize = tokenComment(depth + 1);
-          return state.tokenize(stream, state);
-        }
-        lastCh = ch;
-      }
-      return "comment";
-    };
-  }
-
-  // Parser
-
-  var cx = {state: null, stream: null, marked: null, cc: null};
-  function pass() {
-    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
-  }
-  function cont() {
-    pass.apply(null, arguments);
-    return true;
-  }
-
-  function pushlex(type, info) {
-    var result = function() {
-      var state = cx.state;
-      state.lexical = {indented: state.indented, column: cx.stream.column(),
-                       type: type, prev: state.lexical, info: info};
-    };
-    result.lex = true;
-    return result;
-  }
-  function poplex() {
-    var state = cx.state;
-    if (state.lexical.prev) {
-      if (state.lexical.type == ")")
-        state.indented = state.lexical.indented;
-      state.lexical = state.lexical.prev;
-    }
-  }
-  function typecx() { cx.state.keywords = typeKeywords; }
-  function valcx() { cx.state.keywords = valKeywords; }
-  poplex.lex = typecx.lex = valcx.lex = true;
-
-  function commasep(comb, end) {
-    function more(type) {
-      if (type == ",") return cont(comb, more);
-      if (type == end) return cont();
-      return cont(more);
-    }
-    return function(type) {
-      if (type == end) return cont();
-      return pass(comb, more);
-    };
-  }
-
-  function stat_of(comb, tag) {
-    return cont(pushlex("stat", tag), comb, poplex, block);
-  }
-  function block(type) {
-    if (type == "}") return cont();
-    if (type == "let") return stat_of(letdef1, "let");
-    if (type == "fn") return stat_of(fndef);
-    if (type == "type") return cont(pushlex("stat"), tydef, endstatement, poplex, block);
-    if (type == "enum") return stat_of(enumdef);
-    if (type == "mod") return stat_of(mod);
-    if (type == "iface") return stat_of(iface);
-    if (type == "impl") return stat_of(impl);
-    if (type == "open-attr") return cont(pushlex("]"), commasep(expression, "]"), poplex);
-    if (type == "ignore" || type.match(/[\]\);,]/)) return cont(block);
-    return pass(pushlex("stat"), expression, poplex, endstatement, block);
-  }
-  function endstatement(type) {
-    if (type == ";") return cont();
-    return pass();
-  }
-  function expression(type) {
-    if (type == "atom" || type == "name") return cont(maybeop);
-    if (type == "{") return cont(pushlex("}"), exprbrace, poplex);
-    if (type.match(/[\[\(]/)) return matchBrackets(type, expression);
-    if (type.match(/[\]\)\};,]/)) return pass();
-    if (type == "if-style") return cont(expression, expression);
-    if (type == "else-style" || type == "op") return cont(expression);
-    if (type == "for") return cont(pattern, maybetype, inop, expression, expression);
-    if (type == "alt") return cont(expression, altbody);
-    if (type == "fn") return cont(fndef);
-    if (type == "macro") return cont(macro);
-    return cont();
-  }
-  function maybeop(type) {
-    if (content == ".") return cont(maybeprop);
-    if (content == "::<"){return cont(typarams, maybeop);}
-    if (type == "op" || content == ":") return cont(expression);
-    if (type == "(" || type == "[") return matchBrackets(type, expression);
-    return pass();
-  }
-  function maybeprop() {
-    if (content.match(/^\w+$/)) {cx.marked = "variable"; return cont(maybeop);}
-    return pass(expression);
-  }
-  function exprbrace(type) {
-    if (type == "op") {
-      if (content == "|") return cont(blockvars, poplex, pushlex("}", "block"), block);
-      if (content == "||") return cont(poplex, pushlex("}", "block"), block);
-    }
-    if (content == "mutable" || (content.match(/^\w+$/) && cx.stream.peek() == ":"
-                                 && !cx.stream.match("::", false)))
-      return pass(record_of(expression));
-    return pass(block);
-  }
-  function record_of(comb) {
-    function ro(type) {
-      if (content == "mutable" || content == "with") {cx.marked = "keyword"; return cont(ro);}
-      if (content.match(/^\w*$/)) {cx.marked = "variable"; return cont(ro);}
-      if (type == ":") return cont(comb, ro);
-      if (type == "}") return cont();
-      return cont(ro);
-    }
-    return ro;
-  }
-  function blockvars(type) {
-    if (type == "name") {cx.marked = "def"; return cont(blockvars);}
-    if (type == "op" && content == "|") return cont();
-    return cont(blockvars);
-  }
-
-  function letdef1(type) {
-    if (type.match(/[\]\)\};]/)) return cont();
-    if (content == "=") return cont(expression, letdef2);
-    if (type == ",") return cont(letdef1);
-    return pass(pattern, maybetype, letdef1);
-  }
-  function letdef2(type) {
-    if (type.match(/[\]\)\};,]/)) return pass(letdef1);
-    else return pass(expression, letdef2);
-  }
-  function maybetype(type) {
-    if (type == ":") return cont(typecx, rtype, valcx);
-    return pass();
-  }
-  function inop(type) {
-    if (type == "name" && content == "in") {cx.marked = "keyword"; return cont();}
-    return pass();
-  }
-  function fndef(type) {
-    if (content == "@" || content == "~") {cx.marked = "keyword"; return cont(fndef);}
-    if (type == "name") {cx.marked = "def"; return cont(fndef);}
-    if (content == "<") return cont(typarams, fndef);
-    if (type == "{") return pass(expression);
-    if (type == "(") return cont(pushlex(")"), commasep(argdef, ")"), poplex, fndef);
-    if (type == "->") return cont(typecx, rtype, valcx, fndef);
-    if (type == ";") return cont();
-    return cont(fndef);
-  }
-  function tydef(type) {
-    if (type == "name") {cx.marked = "def"; return cont(tydef);}
-    if (content == "<") return cont(typarams, tydef);
-    if (content == "=") return cont(typecx, rtype, valcx);
-    return cont(tydef);
-  }
-  function enumdef(type) {
-    if (type == "name") {cx.marked = "def"; return cont(enumdef);}
-    if (content == "<") return cont(typarams, enumdef);
-    if (content == "=") return cont(typecx, rtype, valcx, endstatement);
-    if (type == "{") return cont(pushlex("}"), typecx, enumblock, valcx, poplex);
-    return cont(enumdef);
-  }
-  function enumblock(type) {
-    if (type == "}") return cont();
-    if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, enumblock);
-    if (content.match(/^\w+$/)) cx.marked = "def";
-    return cont(enumblock);
-  }
-  function mod(type) {
-    if (type == "name") {cx.marked = "def"; return cont(mod);}
-    if (type == "{") return cont(pushlex("}"), block, poplex);
-    return pass();
-  }
-  function iface(type) {
-    if (type == "name") {cx.marked = "def"; return cont(iface);}
-    if (content == "<") return cont(typarams, iface);
-    if (type == "{") return cont(pushlex("}"), block, poplex);
-    return pass();
-  }
-  function impl(type) {
-    if (content == "<") return cont(typarams, impl);
-    if (content == "of" || content == "for") {cx.marked = "keyword"; return cont(rtype, impl);}
-    if (type == "name") {cx.marked = "def"; return cont(impl);}
-    if (type == "{") return cont(pushlex("}"), block, poplex);
-    return pass();
-  }
-  function typarams() {
-    if (content == ">") return cont();
-    if (content == ",") return cont(typarams);
-    if (content == ":") return cont(rtype, typarams);
-    return pass(rtype, typarams);
-  }
-  function argdef(type) {
-    if (type == "name") {cx.marked = "def"; return cont(argdef);}
-    if (type == ":") return cont(typecx, rtype, valcx);
-    return pass();
-  }
-  function rtype(type) {
-    if (type == "name") {cx.marked = "variable-3"; return cont(rtypemaybeparam); }
-    if (content == "mutable") {cx.marked = "keyword"; return cont(rtype);}
-    if (type == "atom") return cont(rtypemaybeparam);
-    if (type == "op" || type == "obj") return cont(rtype);
-    if (type == "fn") return cont(fntype);
-    if (type == "{") return cont(pushlex("{"), record_of(rtype), poplex);
-    return matchBrackets(type, rtype);
-  }
-  function rtypemaybeparam() {
-    if (content == "<") return cont(typarams);
-    return pass();
-  }
-  function fntype(type) {
-    if (type == "(") return cont(pushlex("("), commasep(rtype, ")"), poplex, fntype);
-    if (type == "->") return cont(rtype);
-    return pass();
-  }
-  function pattern(type) {
-    if (type == "name") {cx.marked = "def"; return cont(patternmaybeop);}
-    if (type == "atom") return cont(patternmaybeop);
-    if (type == "op") return cont(pattern);
-    if (type.match(/[\]\)\};,]/)) return pass();
-    return matchBrackets(type, pattern);
-  }
-  function patternmaybeop(type) {
-    if (type == "op" && content == ".") return cont();
-    if (content == "to") {cx.marked = "keyword"; return cont(pattern);}
-    else return pass();
-  }
-  function altbody(type) {
-    if (type == "{") return cont(pushlex("}", "alt"), altblock1, poplex);
-    return pass();
-  }
-  function altblock1(type) {
-    if (type == "}") return cont();
-    if (type == "|") return cont(altblock1);
-    if (content == "when") {cx.marked = "keyword"; return cont(expression, altblock2);}
-    if (type.match(/[\]\);,]/)) return cont(altblock1);
-    return pass(pattern, altblock2);
-  }
-  function altblock2(type) {
-    if (type == "{") return cont(pushlex("}", "alt"), block, poplex, altblock1);
-    else return pass(altblock1);
-  }
-
-  function macro(type) {
-    if (type.match(/[\[\(\{]/)) return matchBrackets(type, expression);
-    return pass();
-  }
-  function matchBrackets(type, comb) {
-    if (type == "[") return cont(pushlex("]"), commasep(comb, "]"), poplex);
-    if (type == "(") return cont(pushlex(")"), commasep(comb, ")"), poplex);
-    if (type == "{") return cont(pushlex("}"), commasep(comb, "}"), poplex);
-    return cont();
-  }
-
-  function parse(state, stream, style) {
-    var cc = state.cc;
-    // Communicate our context to the combinators.
-    // (Less wasteful than consing up a hundred closures on every call.)
-    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
-
-    while (true) {
-      var combinator = cc.length ? cc.pop() : block;
-      if (combinator(tcat)) {
-        while(cc.length && cc[cc.length - 1].lex)
-          cc.pop()();
-        return cx.marked || style;
-      }
-    }
-  }
-
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        cc: [],
-        lexical: {indented: -indentUnit, column: 0, type: "top", align: false},
-        keywords: valKeywords,
-        indented: 0
-      };
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (!state.lexical.hasOwnProperty("align"))
-          state.lexical.align = false;
-        state.indented = stream.indentation();
-      }
-      if (stream.eatSpace()) return null;
-      tcat = content = null;
-      var style = state.tokenize(stream, state);
-      if (style == "comment") return style;
-      if (!state.lexical.hasOwnProperty("align"))
-        state.lexical.align = true;
-      if (tcat == "prefix") return style;
-      if (!content) content = stream.current();
-      return parse(state, stream, style);
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase) return 0;
-      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
-          type = lexical.type, closing = firstChar == type;
-      if (type == "stat") return lexical.indented + indentUnit;
-      if (lexical.align) return lexical.column + (closing ? 0 : 1);
-      return lexical.indented + (closing ? 0 : (lexical.info == "alt" ? altIndentUnit : indentUnit));
-    },
-
-    electricChars: "{}",
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: "//",
-    fold: "brace"
-  };
-});
-
-CodeMirror.defineMIME("text/x-rustsrc", "rust");
-
-});
--- a/kallithea/public/codemirror/mode/sass/sass.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,327 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("sass", function(config) {
-  function tokenRegexp(words) {
-    return new RegExp("^" + words.join("|"));
-  }
-
-  var keywords = ["true", "false", "null", "auto"];
-  var keywordsRegexp = new RegExp("^" + keywords.join("|"));
-
-  var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", "\\!=", "/", "\\*", "%", "and", "or", "not"];
-  var opRegexp = tokenRegexp(operators);
-
-  var pseudoElementsRegexp = /^::?[\w\-]+/;
-
-  function urlTokens(stream, state) {
-    var ch = stream.peek();
-
-    if (ch === ")") {
-      stream.next();
-      state.tokenizer = tokenBase;
-      return "operator";
-    } else if (ch === "(") {
-      stream.next();
-      stream.eatSpace();
-
-      return "operator";
-    } else if (ch === "'" || ch === '"') {
-      state.tokenizer = buildStringTokenizer(stream.next());
-      return "string";
-    } else {
-      state.tokenizer = buildStringTokenizer(")", false);
-      return "string";
-    }
-  }
-  function comment(indentation, multiLine) {
-    return function(stream, state) {
-      if (stream.sol() && stream.indentation() <= indentation) {
-        state.tokenizer = tokenBase;
-        return tokenBase(stream, state);
-      }
-
-      if (multiLine && stream.skipTo("*/")) {
-        stream.next();
-        stream.next();
-        state.tokenizer = tokenBase;
-      } else {
-        stream.next();
-      }
-
-      return "comment";
-    };
-  }
-
-  function buildStringTokenizer(quote, greedy) {
-    if(greedy == null) { greedy = true; }
-
-    function stringTokenizer(stream, state) {
-      var nextChar = stream.next();
-      var peekChar = stream.peek();
-      var previousChar = stream.string.charAt(stream.pos-2);
-
-      var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\"));
-
-      if (endingString) {
-        if (nextChar !== quote && greedy) { stream.next(); }
-        state.tokenizer = tokenBase;
-        return "string";
-      } else if (nextChar === "#" && peekChar === "{") {
-        state.tokenizer = buildInterpolationTokenizer(stringTokenizer);
-        stream.next();
-        return "operator";
-      } else {
-        return "string";
-      }
-    }
-
-    return stringTokenizer;
-  }
-
-  function buildInterpolationTokenizer(currentTokenizer) {
-    return function(stream, state) {
-      if (stream.peek() === "}") {
-        stream.next();
-        state.tokenizer = currentTokenizer;
-        return "operator";
-      } else {
-        return tokenBase(stream, state);
-      }
-    };
-  }
-
-  function indent(state) {
-    if (state.indentCount == 0) {
-      state.indentCount++;
-      var lastScopeOffset = state.scopes[0].offset;
-      var currentOffset = lastScopeOffset + config.indentUnit;
-      state.scopes.unshift({ offset:currentOffset });
-    }
-  }
-
-  function dedent(state) {
-    if (state.scopes.length == 1) return;
-
-    state.scopes.shift();
-  }
-
-  function tokenBase(stream, state) {
-    var ch = stream.peek();
-
-    // Comment
-    if (stream.match("/*")) {
-      state.tokenizer = comment(stream.indentation(), true);
-      return state.tokenizer(stream, state);
-    }
-    if (stream.match("//")) {
-      state.tokenizer = comment(stream.indentation(), false);
-      return state.tokenizer(stream, state);
-    }
-
-    // Interpolation
-    if (stream.match("#{")) {
-      state.tokenizer = buildInterpolationTokenizer(tokenBase);
-      return "operator";
-    }
-
-    if (ch === ".") {
-      stream.next();
-
-      // Match class selectors
-      if (stream.match(/^[\w-]+/)) {
-        indent(state);
-        return "atom";
-      } else if (stream.peek() === "#") {
-        indent(state);
-        return "atom";
-      } else {
-        return "operator";
-      }
-    }
-
-    if (ch === "#") {
-      stream.next();
-
-      // Hex numbers
-      if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/))
-        return "number";
-
-      // ID selectors
-      if (stream.match(/^[\w-]+/)) {
-        indent(state);
-        return "atom";
-      }
-
-      if (stream.peek() === "#") {
-        indent(state);
-        return "atom";
-      }
-    }
-
-    // Numbers
-    if (stream.match(/^-?[0-9\.]+/))
-      return "number";
-
-    // Units
-    if (stream.match(/^(px|em|in)\b/))
-      return "unit";
-
-    if (stream.match(keywordsRegexp))
-      return "keyword";
-
-    if (stream.match(/^url/) && stream.peek() === "(") {
-      state.tokenizer = urlTokens;
-      return "atom";
-    }
-
-    // Variables
-    if (ch === "$") {
-      stream.next();
-      stream.eatWhile(/[\w-]/);
-
-      if (stream.peek() === ":") {
-        stream.next();
-        return "variable-2";
-      } else {
-        return "variable-3";
-      }
-    }
-
-    if (ch === "!") {
-      stream.next();
-      return stream.match(/^[\w]+/) ? "keyword": "operator";
-    }
-
-    if (ch === "=") {
-      stream.next();
-
-      // Match shortcut mixin definition
-      if (stream.match(/^[\w-]+/)) {
-        indent(state);
-        return "meta";
-      } else {
-        return "operator";
-      }
-    }
-
-    if (ch === "+") {
-      stream.next();
-
-      // Match shortcut mixin definition
-      if (stream.match(/^[\w-]+/))
-        return "variable-3";
-      else
-        return "operator";
-    }
-
-    // Indent Directives
-    if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
-      indent(state);
-      return "meta";
-    }
-
-    // Other Directives
-    if (ch === "@") {
-      stream.next();
-      stream.eatWhile(/[\w-]/);
-      return "meta";
-    }
-
-    // Strings
-    if (ch === '"' || ch === "'") {
-      stream.next();
-      state.tokenizer = buildStringTokenizer(ch);
-      return "string";
-    }
-
-    // Pseudo element selectors
-    if (ch == ":" && stream.match(pseudoElementsRegexp))
-      return "keyword";
-
-    // atoms
-    if (stream.eatWhile(/[\w-&]/)) {
-      // matches a property definition
-      if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false))
-        return "property";
-      else
-        return "atom";
-    }
-
-    if (stream.match(opRegexp))
-      return "operator";
-
-    // If we haven't returned by now, we move 1 character
-    // and return an error
-    stream.next();
-    return null;
-  }
-
-  function tokenLexer(stream, state) {
-    if (stream.sol()) state.indentCount = 0;
-    var style = state.tokenizer(stream, state);
-    var current = stream.current();
-
-    if (current === "@return")
-      dedent(state);
-
-    if (style === "atom")
-      indent(state);
-
-    if (style !== null) {
-      var startOfToken = stream.pos - current.length;
-      var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount);
-
-      var newScopes = [];
-
-      for (var i = 0; i < state.scopes.length; i++) {
-        var scope = state.scopes[i];
-
-        if (scope.offset <= withCurrentIndent)
-          newScopes.push(scope);
-      }
-
-      state.scopes = newScopes;
-    }
-
-
-    return style;
-  }
-
-  return {
-    startState: function() {
-      return {
-        tokenizer: tokenBase,
-        scopes: [{offset: 0, type: "sass"}],
-        indentCount: 0,
-        definedVars: [],
-        definedMixins: []
-      };
-    },
-    token: function(stream, state) {
-      var style = tokenLexer(stream, state);
-
-      state.lastToken = { style: style, content: stream.current() };
-
-      return style;
-    },
-
-    indent: function(state) {
-      return state.scopes[0].offset;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-sass", "sass");
-
-});
--- a/kallithea/public/codemirror/mode/scheme/scheme.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Author: Koh Zi Han, based on implementation by Koh Zi Chun
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("scheme", function () {
-    var BUILTIN = "builtin", COMMENT = "comment", STRING = "string",
-        ATOM = "atom", NUMBER = "number", BRACKET = "bracket";
-    var INDENT_WORD_SKIP = 2;
-
-    function makeKeywords(str) {
-        var obj = {}, words = str.split(" ");
-        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-        return obj;
-    }
-
-    var keywords = makeKeywords("λ case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?");
-    var indentKeys = makeKeywords("define let letrec let* lambda");
-
-    function stateStack(indent, type, prev) { // represents a state stack object
-        this.indent = indent;
-        this.type = type;
-        this.prev = prev;
-    }
-
-    function pushStack(state, indent, type) {
-        state.indentStack = new stateStack(indent, type, state.indentStack);
-    }
-
-    function popStack(state) {
-        state.indentStack = state.indentStack.prev;
-    }
-
-    var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i);
-    var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i);
-    var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i);
-    var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i);
-
-    function isBinaryNumber (stream) {
-        return stream.match(binaryMatcher);
-    }
-
-    function isOctalNumber (stream) {
-        return stream.match(octalMatcher);
-    }
-
-    function isDecimalNumber (stream, backup) {
-        if (backup === true) {
-            stream.backUp(1);
-        }
-        return stream.match(decimalMatcher);
-    }
-
-    function isHexNumber (stream) {
-        return stream.match(hexMatcher);
-    }
-
-    return {
-        startState: function () {
-            return {
-                indentStack: null,
-                indentation: 0,
-                mode: false,
-                sExprComment: false
-            };
-        },
-
-        token: function (stream, state) {
-            if (state.indentStack == null && stream.sol()) {
-                // update indentation, but only if indentStack is empty
-                state.indentation = stream.indentation();
-            }
-
-            // skip spaces
-            if (stream.eatSpace()) {
-                return null;
-            }
-            var returnType = null;
-
-            switch(state.mode){
-                case "string": // multi-line string parsing mode
-                    var next, escaped = false;
-                    while ((next = stream.next()) != null) {
-                        if (next == "\"" && !escaped) {
-
-                            state.mode = false;
-                            break;
-                        }
-                        escaped = !escaped && next == "\\";
-                    }
-                    returnType = STRING; // continue on in scheme-string mode
-                    break;
-                case "comment": // comment parsing mode
-                    var next, maybeEnd = false;
-                    while ((next = stream.next()) != null) {
-                        if (next == "#" && maybeEnd) {
-
-                            state.mode = false;
-                            break;
-                        }
-                        maybeEnd = (next == "|");
-                    }
-                    returnType = COMMENT;
-                    break;
-                case "s-expr-comment": // s-expr commenting mode
-                    state.mode = false;
-                    if(stream.peek() == "(" || stream.peek() == "["){
-                        // actually start scheme s-expr commenting mode
-                        state.sExprComment = 0;
-                    }else{
-                        // if not we just comment the entire of the next token
-                        stream.eatWhile(/[^/s]/); // eat non spaces
-                        returnType = COMMENT;
-                        break;
-                    }
-                default: // default parsing mode
-                    var ch = stream.next();
-
-                    if (ch == "\"") {
-                        state.mode = "string";
-                        returnType = STRING;
-
-                    } else if (ch == "'") {
-                        returnType = ATOM;
-                    } else if (ch == '#') {
-                        if (stream.eat("|")) {                    // Multi-line comment
-                            state.mode = "comment"; // toggle to comment mode
-                            returnType = COMMENT;
-                        } else if (stream.eat(/[tf]/i)) {            // #t/#f (atom)
-                            returnType = ATOM;
-                        } else if (stream.eat(';')) {                // S-Expr comment
-                            state.mode = "s-expr-comment";
-                            returnType = COMMENT;
-                        } else {
-                            var numTest = null, hasExactness = false, hasRadix = true;
-                            if (stream.eat(/[ei]/i)) {
-                                hasExactness = true;
-                            } else {
-                                stream.backUp(1);       // must be radix specifier
-                            }
-                            if (stream.match(/^#b/i)) {
-                                numTest = isBinaryNumber;
-                            } else if (stream.match(/^#o/i)) {
-                                numTest = isOctalNumber;
-                            } else if (stream.match(/^#x/i)) {
-                                numTest = isHexNumber;
-                            } else if (stream.match(/^#d/i)) {
-                                numTest = isDecimalNumber;
-                            } else if (stream.match(/^[-+0-9.]/, false)) {
-                                hasRadix = false;
-                                numTest = isDecimalNumber;
-                            // re-consume the intial # if all matches failed
-                            } else if (!hasExactness) {
-                                stream.eat('#');
-                            }
-                            if (numTest != null) {
-                                if (hasRadix && !hasExactness) {
-                                    // consume optional exactness after radix
-                                    stream.match(/^#[ei]/i);
-                                }
-                                if (numTest(stream))
-                                    returnType = NUMBER;
-                            }
-                        }
-                    } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal
-                        returnType = NUMBER;
-                    } else if (ch == ";") { // comment
-                        stream.skipToEnd(); // rest of the line is a comment
-                        returnType = COMMENT;
-                    } else if (ch == "(" || ch == "[") {
-                      var keyWord = ''; var indentTemp = stream.column(), letter;
-                        /**
-                        Either
-                        (indent-word ..
-                        (non-indent-word ..
-                        (;something else, bracket, etc.
-                        */
-
-                        while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) {
-                            keyWord += letter;
-                        }
-
-                        if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word
-
-                            pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
-                        } else { // non-indent word
-                            // we continue eating the spaces
-                            stream.eatSpace();
-                            if (stream.eol() || stream.peek() == ";") {
-                                // nothing significant after
-                                // we restart indentation 1 space after
-                                pushStack(state, indentTemp + 1, ch);
-                            } else {
-                                pushStack(state, indentTemp + stream.current().length, ch); // else we match
-                            }
-                        }
-                        stream.backUp(stream.current().length - 1); // undo all the eating
-
-                        if(typeof state.sExprComment == "number") state.sExprComment++;
-
-                        returnType = BRACKET;
-                    } else if (ch == ")" || ch == "]") {
-                        returnType = BRACKET;
-                        if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) {
-                            popStack(state);
-
-                            if(typeof state.sExprComment == "number"){
-                                if(--state.sExprComment == 0){
-                                    returnType = COMMENT; // final closing bracket
-                                    state.sExprComment = false; // turn off s-expr commenting mode
-                                }
-                            }
-                        }
-                    } else {
-                        stream.eatWhile(/[\w\$_\-!$%&*+\.\/:<=>?@\^~]/);
-
-                        if (keywords && keywords.propertyIsEnumerable(stream.current())) {
-                            returnType = BUILTIN;
-                        } else returnType = "variable";
-                    }
-            }
-            return (typeof state.sExprComment == "number") ? COMMENT : returnType;
-        },
-
-        indent: function (state) {
-            if (state.indentStack == null) return state.indentation;
-            return state.indentStack.indent;
-        },
-
-        lineComment: ";;"
-    };
-});
-
-CodeMirror.defineMIME("text/x-scheme", "scheme");
-
-});
--- a/kallithea/public/codemirror/mode/shell/shell.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('shell', function() {
-
-  var words = {};
-  function define(style, string) {
-    var split = string.split(' ');
-    for(var i = 0; i < split.length; i++) {
-      words[split[i]] = style;
-    }
-  };
-
-  // Atoms
-  define('atom', 'true false');
-
-  // Keywords
-  define('keyword', 'if then do else elif while until for in esac fi fin ' +
-    'fil done exit set unset export function');
-
-  // Commands
-  define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' +
-    'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' +
-    'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' +
-    'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' +
-    'touch vi vim wall wc wget who write yes zsh');
-
-  function tokenBase(stream, state) {
-    if (stream.eatSpace()) return null;
-
-    var sol = stream.sol();
-    var ch = stream.next();
-
-    if (ch === '\\') {
-      stream.next();
-      return null;
-    }
-    if (ch === '\'' || ch === '"' || ch === '`') {
-      state.tokens.unshift(tokenString(ch));
-      return tokenize(stream, state);
-    }
-    if (ch === '#') {
-      if (sol && stream.eat('!')) {
-        stream.skipToEnd();
-        return 'meta'; // 'comment'?
-      }
-      stream.skipToEnd();
-      return 'comment';
-    }
-    if (ch === '$') {
-      state.tokens.unshift(tokenDollar);
-      return tokenize(stream, state);
-    }
-    if (ch === '+' || ch === '=') {
-      return 'operator';
-    }
-    if (ch === '-') {
-      stream.eat('-');
-      stream.eatWhile(/\w/);
-      return 'attribute';
-    }
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/\d/);
-      if(stream.eol() || !/\w/.test(stream.peek())) {
-        return 'number';
-      }
-    }
-    stream.eatWhile(/[\w-]/);
-    var cur = stream.current();
-    if (stream.peek() === '=' && /\w+/.test(cur)) return 'def';
-    return words.hasOwnProperty(cur) ? words[cur] : null;
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var next, end = false, escaped = false;
-      while ((next = stream.next()) != null) {
-        if (next === quote && !escaped) {
-          end = true;
-          break;
-        }
-        if (next === '$' && !escaped && quote !== '\'') {
-          escaped = true;
-          stream.backUp(1);
-          state.tokens.unshift(tokenDollar);
-          break;
-        }
-        escaped = !escaped && next === '\\';
-      }
-      if (end || !escaped) {
-        state.tokens.shift();
-      }
-      return (quote === '`' || quote === ')' ? 'quote' : 'string');
-    };
-  };
-
-  var tokenDollar = function(stream, state) {
-    if (state.tokens.length > 1) stream.eat('$');
-    var ch = stream.next(), hungry = /\w/;
-    if (ch === '{') hungry = /[^}]/;
-    if (ch === '(') {
-      state.tokens[0] = tokenString(')');
-      return tokenize(stream, state);
-    }
-    if (!/\d/.test(ch)) {
-      stream.eatWhile(hungry);
-      stream.eat('}');
-    }
-    state.tokens.shift();
-    return 'def';
-  };
-
-  function tokenize(stream, state) {
-    return (state.tokens[0] || tokenBase) (stream, state);
-  };
-
-  return {
-    startState: function() {return {tokens:[]};},
-    token: function(stream, state) {
-      return tokenize(stream, state);
-    },
-    lineComment: '#'
-  };
-});
-
-CodeMirror.defineMIME('text/x-sh', 'shell');
-
-});
--- a/kallithea/public/codemirror/mode/sieve/sieve.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("sieve", function(config) {
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  var keywords = words("if elsif else stop require");
-  var atoms = words("true false not");
-  var indentUnit = config.indentUnit;
-
-  function tokenBase(stream, state) {
-
-    var ch = stream.next();
-    if (ch == "/" && stream.eat("*")) {
-      state.tokenize = tokenCComment;
-      return tokenCComment(stream, state);
-    }
-
-    if (ch === '#') {
-      stream.skipToEnd();
-      return "comment";
-    }
-
-    if (ch == "\"") {
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-
-    if (ch == "(") {
-      state._indent.push("(");
-      // add virtual angel wings so that editor behaves...
-      // ...more sane incase of broken brackets
-      state._indent.push("{");
-      return null;
-    }
-
-    if (ch === "{") {
-      state._indent.push("{");
-      return null;
-    }
-
-    if (ch == ")")  {
-      state._indent.pop();
-      state._indent.pop();
-    }
-
-    if (ch === "}") {
-      state._indent.pop();
-      return null;
-    }
-
-    if (ch == ",")
-      return null;
-
-    if (ch == ";")
-      return null;
-
-
-    if (/[{}\(\),;]/.test(ch))
-      return null;
-
-    // 1*DIGIT "K" / "M" / "G"
-    if (/\d/.test(ch)) {
-      stream.eatWhile(/[\d]/);
-      stream.eat(/[KkMmGg]/);
-      return "number";
-    }
-
-    // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_")
-    if (ch == ":") {
-      stream.eatWhile(/[a-zA-Z_]/);
-      stream.eatWhile(/[a-zA-Z0-9_]/);
-
-      return "operator";
-    }
-
-    stream.eatWhile(/\w/);
-    var cur = stream.current();
-
-    // "text:" *(SP / HTAB) (hash-comment / CRLF)
-    // *(multiline-literal / multiline-dotstart)
-    // "." CRLF
-    if ((cur == "text") && stream.eat(":"))
-    {
-      state.tokenize = tokenMultiLineString;
-      return "string";
-    }
-
-    if (keywords.propertyIsEnumerable(cur))
-      return "keyword";
-
-    if (atoms.propertyIsEnumerable(cur))
-      return "atom";
-
-    return null;
-  }
-
-  function tokenMultiLineString(stream, state)
-  {
-    state._multiLineString = true;
-    // the first line is special it may contain a comment
-    if (!stream.sol()) {
-      stream.eatSpace();
-
-      if (stream.peek() == "#") {
-        stream.skipToEnd();
-        return "comment";
-      }
-
-      stream.skipToEnd();
-      return "string";
-    }
-
-    if ((stream.next() == ".")  && (stream.eol()))
-    {
-      state._multiLineString = false;
-      state.tokenize = tokenBase;
-    }
-
-    return "string";
-  }
-
-  function tokenCComment(stream, state) {
-    var maybeEnd = false, ch;
-    while ((ch = stream.next()) != null) {
-      if (maybeEnd && ch == "/") {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped)
-          break;
-        escaped = !escaped && ch == "\\";
-      }
-      if (!escaped) state.tokenize = tokenBase;
-      return "string";
-    };
-  }
-
-  return {
-    startState: function(base) {
-      return {tokenize: tokenBase,
-              baseIndent: base || 0,
-              _indent: []};
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace())
-        return null;
-
-      return (state.tokenize || tokenBase)(stream, state);;
-    },
-
-    indent: function(state, _textAfter) {
-      var length = state._indent.length;
-      if (_textAfter && (_textAfter[0] == "}"))
-        length--;
-
-      if (length <0)
-        length = 0;
-
-      return length * indentUnit;
-    },
-
-    electricChars: "}"
-  };
-});
-
-CodeMirror.defineMIME("application/sieve", "sieve");
-
-});
--- a/kallithea/public/codemirror/mode/slim/slim.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,575 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-  CodeMirror.defineMode("slim", function(config) {
-    var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"});
-    var rubyMode = CodeMirror.getMode(config, "ruby");
-    var modes = { html: htmlMode, ruby: rubyMode };
-    var embedded = {
-      ruby: "ruby",
-      javascript: "javascript",
-      css: "text/css",
-      sass: "text/x-sass",
-      scss: "text/x-scss",
-      less: "text/x-less",
-      styl: "text/x-styl", // no highlighting so far
-      coffee: "coffeescript",
-      asciidoc: "text/x-asciidoc",
-      markdown: "text/x-markdown",
-      textile: "text/x-textile", // no highlighting so far
-      creole: "text/x-creole", // no highlighting so far
-      wiki: "text/x-wiki", // no highlighting so far
-      mediawiki: "text/x-mediawiki", // no highlighting so far
-      rdoc: "text/x-rdoc", // no highlighting so far
-      builder: "text/x-builder", // no highlighting so far
-      nokogiri: "text/x-nokogiri", // no highlighting so far
-      erb: "application/x-erb"
-    };
-    var embeddedRegexp = function(map){
-      var arr = [];
-      for(var key in map) arr.push(key);
-      return new RegExp("^("+arr.join('|')+"):");
-    }(embedded);
-
-    var styleMap = {
-      "commentLine": "comment",
-      "slimSwitch": "operator special",
-      "slimTag": "tag",
-      "slimId": "attribute def",
-      "slimClass": "attribute qualifier",
-      "slimAttribute": "attribute",
-      "slimSubmode": "keyword special",
-      "closeAttributeTag": null,
-      "slimDoctype": null,
-      "lineContinuation": null
-    };
-    var closing = {
-      "{": "}",
-      "[": "]",
-      "(": ")"
-    };
-
-    var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";
-    var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040";
-    var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)");
-    var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)");
-    var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*");
-    var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/;
-    var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/;
-
-    function backup(pos, tokenize, style) {
-      var restore = function(stream, state) {
-        state.tokenize = tokenize;
-        if (stream.pos < pos) {
-          stream.pos = pos;
-          return style;
-        }
-        return state.tokenize(stream, state);
-      };
-      return function(stream, state) {
-        state.tokenize = restore;
-        return tokenize(stream, state);
-      };
-    }
-
-    function maybeBackup(stream, state, pat, offset, style) {
-      var cur = stream.current();
-      var idx = cur.search(pat);
-      if (idx > -1) {
-        state.tokenize = backup(stream.pos, state.tokenize, style);
-        stream.backUp(cur.length - idx - offset);
-      }
-      return style;
-    }
-
-    function continueLine(state, column) {
-      state.stack = {
-        parent: state.stack,
-        style: "continuation",
-        indented: column,
-        tokenize: state.line
-      };
-      state.line = state.tokenize;
-    }
-    function finishContinue(state) {
-      if (state.line == state.tokenize) {
-        state.line = state.stack.tokenize;
-        state.stack = state.stack.parent;
-      }
-    }
-
-    function lineContinuable(column, tokenize) {
-      return function(stream, state) {
-        finishContinue(state);
-        if (stream.match(/^\\$/)) {
-          continueLine(state, column);
-          return "lineContinuation";
-        }
-        var style = tokenize(stream, state);
-        if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) {
-          stream.backUp(1);
-        }
-        return style;
-      };
-    }
-    function commaContinuable(column, tokenize) {
-      return function(stream, state) {
-        finishContinue(state);
-        var style = tokenize(stream, state);
-        if (stream.eol() && stream.current().match(/,$/)) {
-          continueLine(state, column);
-        }
-        return style;
-      };
-    }
-
-    function rubyInQuote(endQuote, tokenize) {
-      // TODO: add multi line support
-      return function(stream, state) {
-        var ch = stream.peek();
-        if (ch == endQuote && state.rubyState.tokenize.length == 1) {
-          // step out of ruby context as it seems to complete processing all the braces
-          stream.next();
-          state.tokenize = tokenize;
-          return "closeAttributeTag";
-        } else {
-          return ruby(stream, state);
-        }
-      };
-    }
-    function startRubySplat(tokenize) {
-      var rubyState;
-      var runSplat = function(stream, state) {
-        if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) {
-          stream.backUp(1);
-          if (stream.eatSpace()) {
-            state.rubyState = rubyState;
-            state.tokenize = tokenize;
-            return tokenize(stream, state);
-          }
-          stream.next();
-        }
-        return ruby(stream, state);
-      };
-      return function(stream, state) {
-        rubyState = state.rubyState;
-        state.rubyState = rubyMode.startState();
-        state.tokenize = runSplat;
-        return ruby(stream, state);
-      };
-    }
-
-    function ruby(stream, state) {
-      return rubyMode.token(stream, state.rubyState);
-    }
-
-    function htmlLine(stream, state) {
-      if (stream.match(/^\\$/)) {
-        return "lineContinuation";
-      }
-      return html(stream, state);
-    }
-    function html(stream, state) {
-      if (stream.match(/^#\{/)) {
-        state.tokenize = rubyInQuote("}", state.tokenize);
-        return null;
-      }
-      return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState));
-    }
-
-    function startHtmlLine(lastTokenize) {
-      return function(stream, state) {
-        var style = htmlLine(stream, state);
-        if (stream.eol()) state.tokenize = lastTokenize;
-        return style;
-      };
-    }
-
-    function startHtmlMode(stream, state, offset) {
-      state.stack = {
-        parent: state.stack,
-        style: "html",
-        indented: stream.column() + offset, // pipe + space
-        tokenize: state.line
-      };
-      state.line = state.tokenize = html;
-      return null;
-    }
-
-    function comment(stream, state) {
-      stream.skipToEnd();
-      return state.stack.style;
-    }
-
-    function commentMode(stream, state) {
-      state.stack = {
-        parent: state.stack,
-        style: "comment",
-        indented: state.indented + 1,
-        tokenize: state.line
-      };
-      state.line = comment;
-      return comment(stream, state);
-    }
-
-    function attributeWrapper(stream, state) {
-      if (stream.eat(state.stack.endQuote)) {
-        state.line = state.stack.line;
-        state.tokenize = state.stack.tokenize;
-        state.stack = state.stack.parent;
-        return null;
-      }
-      if (stream.match(wrappedAttributeNameRegexp)) {
-        state.tokenize = attributeWrapperAssign;
-        return "slimAttribute";
-      }
-      stream.next();
-      return null;
-    }
-    function attributeWrapperAssign(stream, state) {
-      if (stream.match(/^==?/)) {
-        state.tokenize = attributeWrapperValue;
-        return null;
-      }
-      return attributeWrapper(stream, state);
-    }
-    function attributeWrapperValue(stream, state) {
-      var ch = stream.peek();
-      if (ch == '"' || ch == "\'") {
-        state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper);
-        stream.next();
-        return state.tokenize(stream, state);
-      }
-      if (ch == '[') {
-        return startRubySplat(attributeWrapper)(stream, state);
-      }
-      if (stream.match(/^(true|false|nil)\b/)) {
-        state.tokenize = attributeWrapper;
-        return "keyword";
-      }
-      return startRubySplat(attributeWrapper)(stream, state);
-    }
-
-    function startAttributeWrapperMode(state, endQuote, tokenize) {
-      state.stack = {
-        parent: state.stack,
-        style: "wrapper",
-        indented: state.indented + 1,
-        tokenize: tokenize,
-        line: state.line,
-        endQuote: endQuote
-      };
-      state.line = state.tokenize = attributeWrapper;
-      return null;
-    }
-
-    function sub(stream, state) {
-      if (stream.match(/^#\{/)) {
-        state.tokenize = rubyInQuote("}", state.tokenize);
-        return null;
-      }
-      var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize);
-      subStream.pos = stream.pos - state.stack.indented;
-      subStream.start = stream.start - state.stack.indented;
-      subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented;
-      subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented;
-      var style = state.subMode.token(subStream, state.subState);
-      stream.pos = subStream.pos + state.stack.indented;
-      return style;
-    }
-    function firstSub(stream, state) {
-      state.stack.indented = stream.column();
-      state.line = state.tokenize = sub;
-      return state.tokenize(stream, state);
-    }
-
-    function createMode(mode) {
-      var query = embedded[mode];
-      var spec = CodeMirror.mimeModes[query];
-      if (spec) {
-        return CodeMirror.getMode(config, spec);
-      }
-      var factory = CodeMirror.modes[query];
-      if (factory) {
-        return factory(config, {name: query});
-      }
-      return CodeMirror.getMode(config, "null");
-    }
-
-    function getMode(mode) {
-      if (!modes.hasOwnProperty(mode)) {
-        return modes[mode] = createMode(mode);
-      }
-      return modes[mode];
-    }
-
-    function startSubMode(mode, state) {
-      var subMode = getMode(mode);
-      var subState = subMode.startState && subMode.startState();
-
-      state.subMode = subMode;
-      state.subState = subState;
-
-      state.stack = {
-        parent: state.stack,
-        style: "sub",
-        indented: state.indented + 1,
-        tokenize: state.line
-      };
-      state.line = state.tokenize = firstSub;
-      return "slimSubmode";
-    }
-
-    function doctypeLine(stream, _state) {
-      stream.skipToEnd();
-      return "slimDoctype";
-    }
-
-    function startLine(stream, state) {
-      var ch = stream.peek();
-      if (ch == '<') {
-        return (state.tokenize = startHtmlLine(state.tokenize))(stream, state);
-      }
-      if (stream.match(/^[|']/)) {
-        return startHtmlMode(stream, state, 1);
-      }
-      if (stream.match(/^\/(!|\[\w+])?/)) {
-        return commentMode(stream, state);
-      }
-      if (stream.match(/^(-|==?[<>]?)/)) {
-        state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby));
-        return "slimSwitch";
-      }
-      if (stream.match(/^doctype\b/)) {
-        state.tokenize = doctypeLine;
-        return "keyword";
-      }
-
-      var m = stream.match(embeddedRegexp);
-      if (m) {
-        return startSubMode(m[1], state);
-      }
-
-      return slimTag(stream, state);
-    }
-
-    function slim(stream, state) {
-      if (state.startOfLine) {
-        return startLine(stream, state);
-      }
-      return slimTag(stream, state);
-    }
-
-    function slimTag(stream, state) {
-      if (stream.eat('*')) {
-        state.tokenize = startRubySplat(slimTagExtras);
-        return null;
-      }
-      if (stream.match(nameRegexp)) {
-        state.tokenize = slimTagExtras;
-        return "slimTag";
-      }
-      return slimClass(stream, state);
-    }
-    function slimTagExtras(stream, state) {
-      if (stream.match(/^(<>?|><?)/)) {
-        state.tokenize = slimClass;
-        return null;
-      }
-      return slimClass(stream, state);
-    }
-    function slimClass(stream, state) {
-      if (stream.match(classIdRegexp)) {
-        state.tokenize = slimClass;
-        return "slimId";
-      }
-      if (stream.match(classNameRegexp)) {
-        state.tokenize = slimClass;
-        return "slimClass";
-      }
-      return slimAttribute(stream, state);
-    }
-    function slimAttribute(stream, state) {
-      if (stream.match(/^([\[\{\(])/)) {
-        return startAttributeWrapperMode(state, closing[RegExp.$1], slimAttribute);
-      }
-      if (stream.match(attributeNameRegexp)) {
-        state.tokenize = slimAttributeAssign;
-        return "slimAttribute";
-      }
-      if (stream.peek() == '*') {
-        stream.next();
-        state.tokenize = startRubySplat(slimContent);
-        return null;
-      }
-      return slimContent(stream, state);
-    }
-    function slimAttributeAssign(stream, state) {
-      if (stream.match(/^==?/)) {
-        state.tokenize = slimAttributeValue;
-        return null;
-      }
-      // should never happen, because of forward lookup
-      return slimAttribute(stream, state);
-    }
-
-    function slimAttributeValue(stream, state) {
-      var ch = stream.peek();
-      if (ch == '"' || ch == "\'") {
-        state.tokenize = readQuoted(ch, "string", true, false, slimAttribute);
-        stream.next();
-        return state.tokenize(stream, state);
-      }
-      if (ch == '[') {
-        return startRubySplat(slimAttribute)(stream, state);
-      }
-      if (ch == ':') {
-        return startRubySplat(slimAttributeSymbols)(stream, state);
-      }
-      if (stream.match(/^(true|false|nil)\b/)) {
-        state.tokenize = slimAttribute;
-        return "keyword";
-      }
-      return startRubySplat(slimAttribute)(stream, state);
-    }
-    function slimAttributeSymbols(stream, state) {
-      stream.backUp(1);
-      if (stream.match(/^[^\s],(?=:)/)) {
-        state.tokenize = startRubySplat(slimAttributeSymbols);
-        return null;
-      }
-      stream.next();
-      return slimAttribute(stream, state);
-    }
-    function readQuoted(quote, style, embed, unescaped, nextTokenize) {
-      return function(stream, state) {
-        finishContinue(state);
-        var fresh = stream.current().length == 0;
-        if (stream.match(/^\\$/, fresh)) {
-          if (!fresh) return style;
-          continueLine(state, state.indented);
-          return "lineContinuation";
-        }
-        if (stream.match(/^#\{/, fresh)) {
-          if (!fresh) return style;
-          state.tokenize = rubyInQuote("}", state.tokenize);
-          return null;
-        }
-        var escaped = false, ch;
-        while ((ch = stream.next()) != null) {
-          if (ch == quote && (unescaped || !escaped)) {
-            state.tokenize = nextTokenize;
-            break;
-          }
-          if (embed && ch == "#" && !escaped) {
-            if (stream.eat("{")) {
-              stream.backUp(2);
-              break;
-            }
-          }
-          escaped = !escaped && ch == "\\";
-        }
-        if (stream.eol() && escaped) {
-          stream.backUp(1);
-        }
-        return style;
-      };
-    }
-    function slimContent(stream, state) {
-      if (stream.match(/^==?/)) {
-        state.tokenize = ruby;
-        return "slimSwitch";
-      }
-      if (stream.match(/^\/$/)) { // tag close hint
-        state.tokenize = slim;
-        return null;
-      }
-      if (stream.match(/^:/)) { // inline tag
-        state.tokenize = slimTag;
-        return "slimSwitch";
-      }
-      startHtmlMode(stream, state, 0);
-      return state.tokenize(stream, state);
-    }
-
-    var mode = {
-      // default to html mode
-      startState: function() {
-        var htmlState = htmlMode.startState();
-        var rubyState = rubyMode.startState();
-        return {
-          htmlState: htmlState,
-          rubyState: rubyState,
-          stack: null,
-          last: null,
-          tokenize: slim,
-          line: slim,
-          indented: 0
-        };
-      },
-
-      copyState: function(state) {
-        return {
-          htmlState : CodeMirror.copyState(htmlMode, state.htmlState),
-          rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
-          subMode: state.subMode,
-          subState: state.subMode && CodeMirror.copyState(state.subMode, state.subState),
-          stack: state.stack,
-          last: state.last,
-          tokenize: state.tokenize,
-          line: state.line
-        };
-      },
-
-      token: function(stream, state) {
-        if (stream.sol()) {
-          state.indented = stream.indentation();
-          state.startOfLine = true;
-          state.tokenize = state.line;
-          while (state.stack && state.stack.indented > state.indented && state.last != "slimSubmode") {
-            state.line = state.tokenize = state.stack.tokenize;
-            state.stack = state.stack.parent;
-            state.subMode = null;
-            state.subState = null;
-          }
-        }
-        if (stream.eatSpace()) return null;
-        var style = state.tokenize(stream, state);
-        state.startOfLine = false;
-        if (style) state.last = style;
-        return styleMap.hasOwnProperty(style) ? styleMap[style] : style;
-      },
-
-      blankLine: function(state) {
-        if (state.subMode && state.subMode.blankLine) {
-          return state.subMode.blankLine(state.subState);
-        }
-      },
-
-      innerMode: function(state) {
-        if (state.subMode) return {state: state.subState, mode: state.subMode};
-        return {state: state, mode: mode};
-      }
-
-      //indent: function(state) {
-      //  return state.indented;
-      //}
-    };
-    return mode;
-  }, "htmlmixed", "ruby");
-
-  CodeMirror.defineMIME("text/x-slim", "slim");
-  CodeMirror.defineMIME("application/x-slim", "slim");
-});
--- a/kallithea/public/codemirror/mode/smalltalk/smalltalk.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('smalltalk', function(config) {
-
-  var specialChars = /[+\-\/\\*~<>=@%|&?!.,:;^]/;
-  var keywords = /true|false|nil|self|super|thisContext/;
-
-  var Context = function(tokenizer, parent) {
-    this.next = tokenizer;
-    this.parent = parent;
-  };
-
-  var Token = function(name, context, eos) {
-    this.name = name;
-    this.context = context;
-    this.eos = eos;
-  };
-
-  var State = function() {
-    this.context = new Context(next, null);
-    this.expectVariable = true;
-    this.indentation = 0;
-    this.userIndentationDelta = 0;
-  };
-
-  State.prototype.userIndent = function(indentation) {
-    this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0;
-  };
-
-  var next = function(stream, context, state) {
-    var token = new Token(null, context, false);
-    var aChar = stream.next();
-
-    if (aChar === '"') {
-      token = nextComment(stream, new Context(nextComment, context));
-
-    } else if (aChar === '\'') {
-      token = nextString(stream, new Context(nextString, context));
-
-    } else if (aChar === '#') {
-      if (stream.peek() === '\'') {
-        stream.next();
-        token = nextSymbol(stream, new Context(nextSymbol, context));
-      } else {
-        if (stream.eatWhile(/[^\s.{}\[\]()]/))
-          token.name = 'string-2';
-        else
-          token.name = 'meta';
-      }
-
-    } else if (aChar === '$') {
-      if (stream.next() === '<') {
-        stream.eatWhile(/[^\s>]/);
-        stream.next();
-      }
-      token.name = 'string-2';
-
-    } else if (aChar === '|' && state.expectVariable) {
-      token.context = new Context(nextTemporaries, context);
-
-    } else if (/[\[\]{}()]/.test(aChar)) {
-      token.name = 'bracket';
-      token.eos = /[\[{(]/.test(aChar);
-
-      if (aChar === '[') {
-        state.indentation++;
-      } else if (aChar === ']') {
-        state.indentation = Math.max(0, state.indentation - 1);
-      }
-
-    } else if (specialChars.test(aChar)) {
-      stream.eatWhile(specialChars);
-      token.name = 'operator';
-      token.eos = aChar !== ';'; // ; cascaded message expression
-
-    } else if (/\d/.test(aChar)) {
-      stream.eatWhile(/[\w\d]/);
-      token.name = 'number';
-
-    } else if (/[\w_]/.test(aChar)) {
-      stream.eatWhile(/[\w\d_]/);
-      token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null;
-
-    } else {
-      token.eos = state.expectVariable;
-    }
-
-    return token;
-  };
-
-  var nextComment = function(stream, context) {
-    stream.eatWhile(/[^"]/);
-    return new Token('comment', stream.eat('"') ? context.parent : context, true);
-  };
-
-  var nextString = function(stream, context) {
-    stream.eatWhile(/[^']/);
-    return new Token('string', stream.eat('\'') ? context.parent : context, false);
-  };
-
-  var nextSymbol = function(stream, context) {
-    stream.eatWhile(/[^']/);
-    return new Token('string-2', stream.eat('\'') ? context.parent : context, false);
-  };
-
-  var nextTemporaries = function(stream, context) {
-    var token = new Token(null, context, false);
-    var aChar = stream.next();
-
-    if (aChar === '|') {
-      token.context = context.parent;
-      token.eos = true;
-
-    } else {
-      stream.eatWhile(/[^|]/);
-      token.name = 'variable';
-    }
-
-    return token;
-  };
-
-  return {
-    startState: function() {
-      return new State;
-    },
-
-    token: function(stream, state) {
-      state.userIndent(stream.indentation());
-
-      if (stream.eatSpace()) {
-        return null;
-      }
-
-      var token = state.context.next(stream, state.context, state);
-      state.context = token.context;
-      state.expectVariable = token.eos;
-
-      return token.name;
-    },
-
-    blankLine: function(state) {
-      state.userIndent(0);
-    },
-
-    indent: function(state, textAfter) {
-      var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta;
-      return (state.indentation + i) * config.indentUnit;
-    },
-
-    electricChars: ']'
-  };
-
-});
-
-CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'});
-
-});
--- a/kallithea/public/codemirror/mode/smarty/smarty.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,221 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Smarty 2 and 3 mode.
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("smarty", function(config) {
-  "use strict";
-
-  // our default settings; check to see if they're overridden
-  var settings = {
-    rightDelimiter: '}',
-    leftDelimiter: '{',
-    smartyVersion: 2 // for backward compatibility
-  };
-  if (config.hasOwnProperty("leftDelimiter")) {
-    settings.leftDelimiter = config.leftDelimiter;
-  }
-  if (config.hasOwnProperty("rightDelimiter")) {
-    settings.rightDelimiter = config.rightDelimiter;
-  }
-  if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) {
-    settings.smartyVersion = 3;
-  }
-
-  var keyFunctions = ["debug", "extends", "function", "include", "literal"];
-  var last;
-  var regs = {
-    operatorChars: /[+\-*&%=<>!?]/,
-    validIdentifier: /[a-zA-Z0-9_]/,
-    stringChar: /['"]/
-  };
-
-  var helpers = {
-    cont: function(style, lastType) {
-      last = lastType;
-      return style;
-    },
-    chain: function(stream, state, parser) {
-      state.tokenize = parser;
-      return parser(stream, state);
-    }
-  };
-
-
-  // our various parsers
-  var parsers = {
-
-    // the main tokenizer
-    tokenizer: function(stream, state) {
-      if (stream.match(settings.leftDelimiter, true)) {
-        if (stream.eat("*")) {
-          return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
-        } else {
-          // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode
-          state.depth++;
-          var isEol = stream.eol();
-          var isFollowedByWhitespace = /\s/.test(stream.peek());
-          if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) {
-            state.depth--;
-            return null;
-          } else {
-            state.tokenize = parsers.smarty;
-            last = "startTag";
-            return "tag";
-          }
-        }
-      } else {
-        stream.next();
-        return null;
-      }
-    },
-
-    // parsing Smarty content
-    smarty: function(stream, state) {
-      if (stream.match(settings.rightDelimiter, true)) {
-        if (settings.smartyVersion === 3) {
-          state.depth--;
-          if (state.depth <= 0) {
-            state.tokenize = parsers.tokenizer;
-          }
-        } else {
-          state.tokenize = parsers.tokenizer;
-        }
-        return helpers.cont("tag", null);
-      }
-
-      if (stream.match(settings.leftDelimiter, true)) {
-        state.depth++;
-        return helpers.cont("tag", "startTag");
-      }
-
-      var ch = stream.next();
-      if (ch == "$") {
-        stream.eatWhile(regs.validIdentifier);
-        return helpers.cont("variable-2", "variable");
-      } else if (ch == "|") {
-        return helpers.cont("operator", "pipe");
-      } else if (ch == ".") {
-        return helpers.cont("operator", "property");
-      } else if (regs.stringChar.test(ch)) {
-        state.tokenize = parsers.inAttribute(ch);
-        return helpers.cont("string", "string");
-      } else if (regs.operatorChars.test(ch)) {
-        stream.eatWhile(regs.operatorChars);
-        return helpers.cont("operator", "operator");
-      } else if (ch == "[" || ch == "]") {
-        return helpers.cont("bracket", "bracket");
-      } else if (ch == "(" || ch == ")") {
-        return helpers.cont("bracket", "operator");
-      } else if (/\d/.test(ch)) {
-        stream.eatWhile(/\d/);
-        return helpers.cont("number", "number");
-      } else {
-
-        if (state.last == "variable") {
-          if (ch == "@") {
-            stream.eatWhile(regs.validIdentifier);
-            return helpers.cont("property", "property");
-          } else if (ch == "|") {
-            stream.eatWhile(regs.validIdentifier);
-            return helpers.cont("qualifier", "modifier");
-          }
-        } else if (state.last == "pipe") {
-          stream.eatWhile(regs.validIdentifier);
-          return helpers.cont("qualifier", "modifier");
-        } else if (state.last == "whitespace") {
-          stream.eatWhile(regs.validIdentifier);
-          return helpers.cont("attribute", "modifier");
-        } if (state.last == "property") {
-          stream.eatWhile(regs.validIdentifier);
-          return helpers.cont("property", null);
-        } else if (/\s/.test(ch)) {
-          last = "whitespace";
-          return null;
-        }
-
-        var str = "";
-        if (ch != "/") {
-          str += ch;
-        }
-        var c = null;
-        while (c = stream.eat(regs.validIdentifier)) {
-          str += c;
-        }
-        for (var i=0, j=keyFunctions.length; i<j; i++) {
-          if (keyFunctions[i] == str) {
-            return helpers.cont("keyword", "keyword");
-          }
-        }
-        if (/\s/.test(ch)) {
-          return null;
-        }
-        return helpers.cont("tag", "tag");
-      }
-    },
-
-    inAttribute: function(quote) {
-      return function(stream, state) {
-        var prevChar = null;
-        var currChar = null;
-        while (!stream.eol()) {
-          currChar = stream.peek();
-          if (stream.next() == quote && prevChar !== '\\') {
-            state.tokenize = parsers.smarty;
-            break;
-          }
-          prevChar = currChar;
-        }
-        return "string";
-      };
-    },
-
-    inBlock: function(style, terminator) {
-      return function(stream, state) {
-        while (!stream.eol()) {
-          if (stream.match(terminator)) {
-            state.tokenize = parsers.tokenizer;
-            break;
-          }
-          stream.next();
-        }
-        return style;
-      };
-    }
-  };
-
-
-  // the public API for CodeMirror
-  return {
-    startState: function() {
-      return {
-        tokenize: parsers.tokenizer,
-        mode: "smarty",
-        last: null,
-        depth: 0
-      };
-    },
-    token: function(stream, state) {
-      var style = state.tokenize(stream, state);
-      state.last = last;
-      return style;
-    },
-    electricChars: ""
-  };
-});
-
-CodeMirror.defineMIME("text/x-smarty", "smarty");
-
-});
--- a/kallithea/public/codemirror/mode/smartymixed/smartymixed.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
-* @file smartymixed.js
-* @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML)
-* @author Ruslan Osmanov <rrosmanov at gmail dot com>
-* @version 3.0
-* @date 05.07.2013
-*/
-
-// Warning: Don't base other modes on this one. This here is a
-// terrible way to write a mixed mode.
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../smarty/smarty"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("smartymixed", function(config) {
-  var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
-  var smartyMode = CodeMirror.getMode(config, "smarty");
-
-  var settings = {
-    rightDelimiter: '}',
-    leftDelimiter: '{'
-  };
-
-  if (config.hasOwnProperty("leftDelimiter")) {
-    settings.leftDelimiter = config.leftDelimiter;
-  }
-  if (config.hasOwnProperty("rightDelimiter")) {
-    settings.rightDelimiter = config.rightDelimiter;
-  }
-
-  function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }
-
-  var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter);
-  var regs = {
-    smartyComment: new RegExp("^" + reRight + "\\*"),
-    literalOpen: new RegExp(reLeft + "literal" + reRight),
-    literalClose: new RegExp(reLeft + "\/literal" + reRight),
-    hasLeftDelimeter: new RegExp(".*" + reLeft),
-    htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft)
-  };
-
-  var helpers = {
-    chain: function(stream, state, parser) {
-      state.tokenize = parser;
-      return parser(stream, state);
-    },
-
-    cleanChain: function(stream, state, parser) {
-      state.tokenize = null;
-      state.localState = null;
-      state.localMode = null;
-      return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
-    },
-
-    maybeBackup: function(stream, pat, style) {
-      var cur = stream.current();
-      var close = cur.search(pat),
-      m;
-      if (close > - 1) stream.backUp(cur.length - close);
-      else if (m = cur.match(/<\/?$/)) {
-        stream.backUp(cur.length);
-        if (!stream.match(pat, false)) stream.match(cur[0]);
-      }
-      return style;
-    }
-  };
-
-  var parsers = {
-    html: function(stream, state) {
-      var htmlTagName = state.htmlMixedState.htmlState.context && state.htmlMixedState.htmlState.context.tagName
-        ? state.htmlMixedState.htmlState.context.tagName
-        : null;
-
-      if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && htmlTagName === null) {
-        state.tokenize = parsers.smarty;
-        state.localMode = smartyMode;
-        state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
-        return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
-      } else if (!state.inLiteral && stream.match(settings.leftDelimiter, false)) {
-        state.tokenize = parsers.smarty;
-        state.localMode = smartyMode;
-        state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, ""));
-        return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState));
-      }
-      return htmlMixedMode.token(stream, state.htmlMixedState);
-    },
-
-    smarty: function(stream, state) {
-      if (stream.match(settings.leftDelimiter, false)) {
-        if (stream.match(regs.smartyComment, false)) {
-          return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter));
-        }
-      } else if (stream.match(settings.rightDelimiter, false)) {
-        stream.eat(settings.rightDelimiter);
-        state.tokenize = parsers.html;
-        state.localMode = htmlMixedMode;
-        state.localState = state.htmlMixedState;
-        return "tag";
-      }
-
-      return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState));
-    },
-
-    inBlock: function(style, terminator) {
-      return function(stream, state) {
-        while (!stream.eol()) {
-          if (stream.match(terminator)) {
-            helpers.cleanChain(stream, state, "");
-            break;
-          }
-          stream.next();
-        }
-        return style;
-      };
-    }
-  };
-
-  return {
-    startState: function() {
-      var state = htmlMixedMode.startState();
-      return {
-        token: parsers.html,
-        localMode: null,
-        localState: null,
-        htmlMixedState: state,
-        tokenize: null,
-        inLiteral: false
-      };
-    },
-
-    copyState: function(state) {
-      var local = null, tok = (state.tokenize || state.token);
-      if (state.localState) {
-        local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState);
-      }
-      return {
-        token: state.token,
-        tokenize: state.tokenize,
-        localMode: state.localMode,
-        localState: local,
-        htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
-        inLiteral: state.inLiteral
-      };
-    },
-
-    token: function(stream, state) {
-      if (stream.match(settings.leftDelimiter, false)) {
-        if (!state.inLiteral && stream.match(regs.literalOpen, true)) {
-          state.inLiteral = true;
-          return "keyword";
-        } else if (state.inLiteral && stream.match(regs.literalClose, true)) {
-          state.inLiteral = false;
-          return "keyword";
-        }
-      }
-      if (state.inLiteral && state.localState != state.htmlMixedState) {
-        state.tokenize = parsers.html;
-        state.localMode = htmlMixedMode;
-        state.localState = state.htmlMixedState;
-      }
-
-      var style = (state.tokenize || state.token)(stream, state);
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.localMode == smartyMode
-          || (state.inLiteral && !state.localMode)
-         || regs.hasLeftDelimeter.test(textAfter)) {
-        return CodeMirror.Pass;
-      }
-      return htmlMixedMode.indent(state.htmlMixedState, textAfter);
-    },
-
-    innerMode: function(state) {
-      return {
-        state: state.localState || state.htmlMixedState,
-        mode: state.localMode || htmlMixedMode
-      };
-    }
-  };
-}, "htmlmixed", "smarty");
-
-CodeMirror.defineMIME("text/x-smarty", "smartymixed");
-// vim: et ts=2 sts=2 sw=2
-
-});
--- a/kallithea/public/codemirror/mode/solr/solr.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("solr", function() {
-  "use strict";
-
-  var isStringChar = /[^\s\|\!\+\-\*\?\~\^\&\:\(\)\[\]\{\}\^\"\\]/;
-  var isOperatorChar = /[\|\!\+\-\*\?\~\^\&]/;
-  var isOperatorString = /^(OR|AND|NOT|TO)$/i;
-
-  function isNumber(word) {
-    return parseFloat(word, 10).toString() === word;
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) break;
-        escaped = !escaped && next == "\\";
-      }
-
-      if (!escaped) state.tokenize = tokenBase;
-      return "string";
-    };
-  }
-
-  function tokenOperator(operator) {
-    return function(stream, state) {
-      var style = "operator";
-      if (operator == "+")
-        style += " positive";
-      else if (operator == "-")
-        style += " negative";
-      else if (operator == "|")
-        stream.eat(/\|/);
-      else if (operator == "&")
-        stream.eat(/\&/);
-      else if (operator == "^")
-        style += " boost";
-
-      state.tokenize = tokenBase;
-      return style;
-    };
-  }
-
-  function tokenWord(ch) {
-    return function(stream, state) {
-      var word = ch;
-      while ((ch = stream.peek()) && ch.match(isStringChar) != null) {
-        word += stream.next();
-      }
-
-      state.tokenize = tokenBase;
-      if (isOperatorString.test(word))
-        return "operator";
-      else if (isNumber(word))
-        return "number";
-      else if (stream.peek() == ":")
-        return "field";
-      else
-        return "string";
-    };
-  }
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    if (ch == '"')
-      state.tokenize = tokenString(ch);
-    else if (isOperatorChar.test(ch))
-      state.tokenize = tokenOperator(ch);
-    else if (isStringChar.test(ch))
-      state.tokenize = tokenWord(ch);
-
-    return (state.tokenize != tokenBase) ? state.tokenize(stream, state) : null;
-  }
-
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase
-      };
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      return state.tokenize(stream, state);
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-solr", "solr");
-
-});
--- a/kallithea/public/codemirror/mode/sparql/sparql.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("sparql", function(config) {
-  var indentUnit = config.indentUnit;
-  var curPunc;
-
-  function wordRegexp(words) {
-    return new RegExp("^(?:" + words.join("|") + ")$", "i");
-  }
-  var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
-                        "isblank", "isliteral", "a"]);
-  var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
-                             "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
-                             "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group",
-                             "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union",
-                             "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]);
-  var operatorChars = /[*+\-<>=&|]/;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    curPunc = null;
-    if (ch == "$" || ch == "?") {
-      stream.match(/^[\w\d]*/);
-      return "variable-2";
-    }
-    else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) {
-      stream.match(/^[^\s\u00a0>]*>?/);
-      return "atom";
-    }
-    else if (ch == "\"" || ch == "'") {
-      state.tokenize = tokenLiteral(ch);
-      return state.tokenize(stream, state);
-    }
-    else if (/[{}\(\),\.;\[\]]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    else if (ch == "#") {
-      stream.skipToEnd();
-      return "comment";
-    }
-    else if (operatorChars.test(ch)) {
-      stream.eatWhile(operatorChars);
-      return null;
-    }
-    else if (ch == ":") {
-      stream.eatWhile(/[\w\d\._\-]/);
-      return "atom";
-    }
-    else {
-      stream.eatWhile(/[_\w\d]/);
-      if (stream.eat(":")) {
-        stream.eatWhile(/[\w\d_\-]/);
-        return "atom";
-      }
-      var word = stream.current();
-      if (ops.test(word))
-        return null;
-      else if (keywords.test(word))
-        return "keyword";
-      else
-        return "variable";
-    }
-  }
-
-  function tokenLiteral(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      return "string";
-    };
-  }
-
-  function pushContext(state, type, col) {
-    state.context = {prev: state.context, indent: state.indent, col: col, type: type};
-  }
-  function popContext(state) {
-    state.indent = state.context.indent;
-    state.context = state.context.prev;
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: tokenBase,
-              context: null,
-              indent: 0,
-              col: 0};
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (state.context && state.context.align == null) state.context.align = false;
-        state.indent = stream.indentation();
-      }
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-
-      if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") {
-        state.context.align = true;
-      }
-
-      if (curPunc == "(") pushContext(state, ")", stream.column());
-      else if (curPunc == "[") pushContext(state, "]", stream.column());
-      else if (curPunc == "{") pushContext(state, "}", stream.column());
-      else if (/[\]\}\)]/.test(curPunc)) {
-        while (state.context && state.context.type == "pattern") popContext(state);
-        if (state.context && curPunc == state.context.type) popContext(state);
-      }
-      else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state);
-      else if (/atom|string|variable/.test(style) && state.context) {
-        if (/[\}\]]/.test(state.context.type))
-          pushContext(state, "pattern", stream.column());
-        else if (state.context.type == "pattern" && !state.context.align) {
-          state.context.align = true;
-          state.context.col = stream.column();
-        }
-      }
-
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var firstChar = textAfter && textAfter.charAt(0);
-      var context = state.context;
-      if (/[\]\}]/.test(firstChar))
-        while (context && context.type == "pattern") context = context.prev;
-
-      var closing = context && firstChar == context.type;
-      if (!context)
-        return 0;
-      else if (context.type == "pattern")
-        return context.col;
-      else if (context.align)
-        return context.col + (closing ? 0 : 1);
-      else
-        return context.indent + (closing ? 0 : indentUnit);
-    }
-  };
-});
-
-CodeMirror.defineMIME("application/x-sparql-query", "sparql");
-
-});
--- a/kallithea/public/codemirror/mode/sql/sql.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,393 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("sql", function(config, parserConfig) {
-  "use strict";
-
-  var client         = parserConfig.client || {},
-      atoms          = parserConfig.atoms || {"false": true, "true": true, "null": true},
-      builtin        = parserConfig.builtin || {},
-      keywords       = parserConfig.keywords || {},
-      operatorChars  = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/,
-      support        = parserConfig.support || {},
-      hooks          = parserConfig.hooks || {},
-      dateSQL        = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true};
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-
-    // call hooks from the mime type
-    if (hooks[ch]) {
-      var result = hooks[ch](stream, state);
-      if (result !== false) return result;
-    }
-
-    if (support.hexNumber == true &&
-      ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/))
-      || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) {
-      // hex
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html
-      return "number";
-    } else if (support.binaryNumber == true &&
-      (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/))
-      || (ch == "0" && stream.match(/^b[01]+/)))) {
-      // bitstring
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html
-      return "number";
-    } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) {
-      // numbers
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html
-          stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/);
-      support.decimallessFloat == true && stream.eat('.');
-      return "number";
-    } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) {
-      // placeholders
-      return "variable-3";
-    } else if (ch == "'" || (ch == '"' && support.doubleQuote)) {
-      // strings
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
-      state.tokenize = tokenLiteral(ch);
-      return state.tokenize(stream, state);
-    } else if ((((support.nCharCast == true && (ch == "n" || ch == "N"))
-        || (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i)))
-        && (stream.peek() == "'" || stream.peek() == '"'))) {
-      // charset casting: _utf8'str', N'str', n'str'
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html
-      return "keyword";
-    } else if (/^[\(\),\;\[\]]/.test(ch)) {
-      // no highlightning
-      return null;
-    } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) {
-      // 1-line comment
-      stream.skipToEnd();
-      return "comment";
-    } else if ((support.commentHash && ch == "#")
-        || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) {
-      // 1-line comments
-      // ref: https://kb.askmonty.org/en/comment-syntax/
-      stream.skipToEnd();
-      return "comment";
-    } else if (ch == "/" && stream.eat("*")) {
-      // multi-line comments
-      // ref: https://kb.askmonty.org/en/comment-syntax/
-      state.tokenize = tokenComment;
-      return state.tokenize(stream, state);
-    } else if (ch == ".") {
-      // .1 for 0.1
-      if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) {
-        return "number";
-      }
-      // .table_name (ODBC)
-      // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
-      if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) {
-        return "variable-2";
-      }
-    } else if (operatorChars.test(ch)) {
-      // operators
-      stream.eatWhile(operatorChars);
-      return null;
-    } else if (ch == '{' &&
-        (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) {
-      // dates (weird ODBC syntax)
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html
-      return "number";
-    } else {
-      stream.eatWhile(/^[_\w\d]/);
-      var word = stream.current().toLowerCase();
-      // dates (standard SQL syntax)
-      // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html
-      if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/)))
-        return "number";
-      if (atoms.hasOwnProperty(word)) return "atom";
-      if (builtin.hasOwnProperty(word)) return "builtin";
-      if (keywords.hasOwnProperty(word)) return "keyword";
-      if (client.hasOwnProperty(word)) return "string-2";
-      return null;
-    }
-  }
-
-  // 'string', with char specified in quote escaped by '\'
-  function tokenLiteral(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      return "string";
-    };
-  }
-  function tokenComment(stream, state) {
-    while (true) {
-      if (stream.skipTo("*")) {
-        stream.next();
-        if (stream.eat("/")) {
-          state.tokenize = tokenBase;
-          break;
-        }
-      } else {
-        stream.skipToEnd();
-        break;
-      }
-    }
-    return "comment";
-  }
-
-  function pushContext(stream, state, type) {
-    state.context = {
-      prev: state.context,
-      indent: stream.indentation(),
-      col: stream.column(),
-      type: type
-    };
-  }
-
-  function popContext(state) {
-    state.indent = state.context.indent;
-    state.context = state.context.prev;
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: tokenBase, context: null};
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (state.context && state.context.align == null)
-          state.context.align = false;
-      }
-      if (stream.eatSpace()) return null;
-
-      var style = state.tokenize(stream, state);
-      if (style == "comment") return style;
-
-      if (state.context && state.context.align == null)
-        state.context.align = true;
-
-      var tok = stream.current();
-      if (tok == "(")
-        pushContext(stream, state, ")");
-      else if (tok == "[")
-        pushContext(stream, state, "]");
-      else if (state.context && state.context.type == tok)
-        popContext(state);
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var cx = state.context;
-      if (!cx) return 0;
-      var closing = textAfter.charAt(0) == cx.type;
-      if (cx.align) return cx.col + (closing ? 0 : 1);
-      else return cx.indent + (closing ? 0 : config.indentUnit);
-    },
-
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: support.commentSlashSlash ? "//" : support.commentHash ? "#" : null
-  };
-});
-
-(function() {
-  "use strict";
-
-  // `identifier`
-  function hookIdentifier(stream) {
-    // MySQL/MariaDB identifiers
-    // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html
-    var ch;
-    while ((ch = stream.next()) != null) {
-      if (ch == "`" && !stream.eat("`")) return "variable-2";
-    }
-    stream.backUp(stream.current().length - 1);
-    return stream.eatWhile(/\w/) ? "variable-2" : null;
-  }
-
-  // variable token
-  function hookVar(stream) {
-    // variables
-    // @@prefix.varName @varName
-    // varName can be quoted with ` or ' or "
-    // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html
-    if (stream.eat("@")) {
-      stream.match(/^session\./);
-      stream.match(/^local\./);
-      stream.match(/^global\./);
-    }
-
-    if (stream.eat("'")) {
-      stream.match(/^.*'/);
-      return "variable-2";
-    } else if (stream.eat('"')) {
-      stream.match(/^.*"/);
-      return "variable-2";
-    } else if (stream.eat("`")) {
-      stream.match(/^.*`/);
-      return "variable-2";
-    } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) {
-      return "variable-2";
-    }
-    return null;
-  };
-
-  // short client keyword token
-  function hookClient(stream) {
-    // \N means NULL
-    // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html
-    if (stream.eat("N")) {
-        return "atom";
-    }
-    // \g, etc
-    // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html
-    return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null;
-  }
-
-  // these keywords are used by all SQL dialects (however, a mode can still overwrite it)
-  var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from having in insert into is join like not on or order select set table union update values where ";
-
-  // turn a space-separated list into an array
-  function set(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  // A generic SQL Mode. It's not a standard, it just try to support what is generally supported
-  CodeMirror.defineMIME("text/x-sql", {
-    name: "sql",
-    keywords: set(sqlKeywords + "begin"),
-    builtin: set("bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric"),
-    atoms: set("false true null unknown"),
-    operatorChars: /^[*+\-%<>!=]/,
-    dateSQL: set("date time timestamp"),
-    support: set("ODBCdotTable doubleQuote binaryNumber hexNumber")
-  });
-
-  CodeMirror.defineMIME("text/x-mssql", {
-    name: "sql",
-    client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
-    keywords: set(sqlKeywords + "begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered"),
-    builtin: set("bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table "),
-    atoms: set("false true null unknown"),
-    operatorChars: /^[*+\-%<>!=]/,
-    dateSQL: set("date datetimeoffset datetime2 smalldatetime datetime time"),
-    hooks: {
-      "@":   hookVar
-    }
-  });
-
-  CodeMirror.defineMIME("text/x-mysql", {
-    name: "sql",
-    client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
-    keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group groupby_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),
-    builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),
-    atoms: set("false true null unknown"),
-    operatorChars: /^[*+\-%<>!=&|^]/,
-    dateSQL: set("date time timestamp"),
-    support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),
-    hooks: {
-      "@":   hookVar,
-      "`":   hookIdentifier,
-      "\\":  hookClient
-    }
-  });
-
-  CodeMirror.defineMIME("text/x-mariadb", {
-    name: "sql",
-    client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"),
-    keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"),
-    builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"),
-    atoms: set("false true null unknown"),
-    operatorChars: /^[*+\-%<>!=&|^]/,
-    dateSQL: set("date time timestamp"),
-    support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"),
-    hooks: {
-      "@":   hookVar,
-      "`":   hookIdentifier,
-      "\\":  hookClient
-    }
-  });
-
-  // the query language used by Apache Cassandra is called CQL, but this mime type
-  // is called Cassandra to avoid confusion with Contextual Query Language
-  CodeMirror.defineMIME("text/x-cassandra", {
-    name: "sql",
-    client: { },
-    keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"),
-    builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"),
-    atoms: set("false true"),
-    operatorChars: /^[<>=]/,
-    dateSQL: { },
-    support: set("commentSlashSlash decimallessFloat"),
-    hooks: { }
-  });
-
-  // this is based on Peter Raganitsch's 'plsql' mode
-  CodeMirror.defineMIME("text/x-plsql", {
-    name:       "sql",
-    client:     set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"),
-    keywords:   set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"),
-    builtin:    set("abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml"),
-    operatorChars: /^[*+\-%<>!=~]/,
-    dateSQL:    set("date time timestamp"),
-    support:    set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber")
-  });
-
-  // Created to support specific hive keywords
-  CodeMirror.defineMIME("text/x-hive", {
-    name: "sql",
-    keywords: set("select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external false fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger true unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with"),
-    builtin: set("bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype"),
-    atoms: set("false true null unknown"),
-    operatorChars: /^[*+\-%<>!=]/,
-    dateSQL: set("date timestamp"),
-    support: set("ODBCdotTable doubleQuote binaryNumber hexNumber")
-  });
-}());
-
-});
-
-/*
-  How Properties of Mime Types are used by SQL Mode
-  =================================================
-
-  keywords:
-    A list of keywords you want to be highlighted.
-  functions:
-    A list of function names you want to be highlighted.
-  builtin:
-    A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword").
-  operatorChars:
-    All characters that must be handled as operators.
-  client:
-    Commands parsed and executed by the client (not the server).
-  support:
-    A list of supported syntaxes which are not common, but are supported by more than 1 DBMS.
-    * ODBCdotTable: .tableName
-    * zerolessFloat: .1
-    * doubleQuote
-    * nCharCast: N'string'
-    * charsetCast: _utf8'string'
-    * commentHash: use # char for comments
-    * commentSlashSlash: use // for comments
-    * commentSpaceRequired: require a space after -- for comments
-  atoms:
-    Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others:
-    UNKNOWN, INFINITY, UNDERFLOW, NaN...
-  dateSQL:
-    Used for date/time SQL standard syntax, because not all DBMS's support same temporal types.
-*/
--- a/kallithea/public/codemirror/mode/stex/stex.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,262 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
- * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de)
- * Licence: MIT
- */
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("stex", function() {
-    "use strict";
-
-    function pushCommand(state, command) {
-        state.cmdState.push(command);
-    }
-
-    function peekCommand(state) {
-        if (state.cmdState.length > 0) {
-            return state.cmdState[state.cmdState.length - 1];
-        } else {
-            return null;
-        }
-    }
-
-    function popCommand(state) {
-        var plug = state.cmdState.pop();
-        if (plug) {
-            plug.closeBracket();
-        }
-    }
-
-    // returns the non-default plugin closest to the end of the list
-    function getMostPowerful(state) {
-        var context = state.cmdState;
-        for (var i = context.length - 1; i >= 0; i--) {
-            var plug = context[i];
-            if (plug.name == "DEFAULT") {
-                continue;
-            }
-            return plug;
-        }
-        return { styleIdentifier: function() { return null; } };
-    }
-
-    function addPluginPattern(pluginName, cmdStyle, styles) {
-        return function () {
-            this.name = pluginName;
-            this.bracketNo = 0;
-            this.style = cmdStyle;
-            this.styles = styles;
-            this.argument = null;   // \begin and \end have arguments that follow. These are stored in the plugin
-
-            this.styleIdentifier = function() {
-                return this.styles[this.bracketNo - 1] || null;
-            };
-            this.openBracket = function() {
-                this.bracketNo++;
-                return "bracket";
-            };
-            this.closeBracket = function() {};
-        };
-    }
-
-    var plugins = {};
-
-    plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]);
-    plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]);
-    plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]);
-    plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]);
-    plugins["end"] = addPluginPattern("end", "tag", ["atom"]);
-
-    plugins["DEFAULT"] = function () {
-        this.name = "DEFAULT";
-        this.style = "tag";
-
-        this.styleIdentifier = this.openBracket = this.closeBracket = function() {};
-    };
-
-    function setState(state, f) {
-        state.f = f;
-    }
-
-    // called when in a normal (no environment) context
-    function normal(source, state) {
-        var plug;
-        // Do we look like '\command' ?  If so, attempt to apply the plugin 'command'
-        if (source.match(/^\\[a-zA-Z@]+/)) {
-            var cmdName = source.current().slice(1);
-            plug = plugins[cmdName] || plugins["DEFAULT"];
-            plug = new plug();
-            pushCommand(state, plug);
-            setState(state, beginParams);
-            return plug.style;
-        }
-
-        // escape characters
-        if (source.match(/^\\[$&%#{}_]/)) {
-          return "tag";
-        }
-
-        // white space control characters
-        if (source.match(/^\\[,;!\/\\]/)) {
-          return "tag";
-        }
-
-        // find if we're starting various math modes
-        if (source.match("\\[")) {
-            setState(state, function(source, state){ return inMathMode(source, state, "\\]"); });
-            return "keyword";
-        }
-        if (source.match("$$")) {
-            setState(state, function(source, state){ return inMathMode(source, state, "$$"); });
-            return "keyword";
-        }
-        if (source.match("$")) {
-            setState(state, function(source, state){ return inMathMode(source, state, "$"); });
-            return "keyword";
-        }
-
-        var ch = source.next();
-        if (ch == "%") {
-            // special case: % at end of its own line; stay in same state
-            if (!source.eol()) {
-              setState(state, inCComment);
-            }
-            return "comment";
-        }
-        else if (ch == '}' || ch == ']') {
-            plug = peekCommand(state);
-            if (plug) {
-                plug.closeBracket(ch);
-                setState(state, beginParams);
-            } else {
-                return "error";
-            }
-            return "bracket";
-        } else if (ch == '{' || ch == '[') {
-            plug = plugins["DEFAULT"];
-            plug = new plug();
-            pushCommand(state, plug);
-            return "bracket";
-        }
-        else if (/\d/.test(ch)) {
-            source.eatWhile(/[\w.%]/);
-            return "atom";
-        }
-        else {
-            source.eatWhile(/[\w\-_]/);
-            plug = getMostPowerful(state);
-            if (plug.name == 'begin') {
-                plug.argument = source.current();
-            }
-            return plug.styleIdentifier();
-        }
-    }
-
-    function inCComment(source, state) {
-        source.skipToEnd();
-        setState(state, normal);
-        return "comment";
-    }
-
-    function inMathMode(source, state, endModeSeq) {
-        if (source.eatSpace()) {
-            return null;
-        }
-        if (source.match(endModeSeq)) {
-            setState(state, normal);
-            return "keyword";
-        }
-        if (source.match(/^\\[a-zA-Z@]+/)) {
-            return "tag";
-        }
-        if (source.match(/^[a-zA-Z]+/)) {
-            return "variable-2";
-        }
-        // escape characters
-        if (source.match(/^\\[$&%#{}_]/)) {
-          return "tag";
-        }
-        // white space control characters
-        if (source.match(/^\\[,;!\/]/)) {
-          return "tag";
-        }
-        // special math-mode characters
-        if (source.match(/^[\^_&]/)) {
-          return "tag";
-        }
-        // non-special characters
-        if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) {
-            return null;
-        }
-        if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) {
-          return "number";
-        }
-        var ch = source.next();
-        if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") {
-            return "bracket";
-        }
-
-        // eat comments here, because inCComment returns us to normal state!
-        if (ch == "%") {
-            if (!source.eol()) {
-                source.skipToEnd();
-            }
-            return "comment";
-        }
-        return "error";
-    }
-
-    function beginParams(source, state) {
-        var ch = source.peek(), lastPlug;
-        if (ch == '{' || ch == '[') {
-            lastPlug = peekCommand(state);
-            lastPlug.openBracket(ch);
-            source.eat(ch);
-            setState(state, normal);
-            return "bracket";
-        }
-        if (/[ \t\r]/.test(ch)) {
-            source.eat(ch);
-            return null;
-        }
-        setState(state, normal);
-        popCommand(state);
-
-        return normal(source, state);
-    }
-
-    return {
-        startState: function() {
-            return {
-                cmdState: [],
-                f: normal
-            };
-        },
-        copyState: function(s) {
-            return {
-                cmdState: s.cmdState.slice(),
-                f: s.f
-            };
-        },
-        token: function(stream, state) {
-            return state.f(stream, state);
-        },
-        lineComment: "%"
-    };
-});
-
-CodeMirror.defineMIME("text/x-stex", "stex");
-CodeMirror.defineMIME("text/x-latex", "stex");
-
-});
--- a/kallithea/public/codemirror/mode/tcl/tcl.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-//tcl mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("tcl", function() {
-  function parseWords(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-  var keywords = parseWords("Tcl safe after append array auto_execok auto_import auto_load " +
-        "auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror " +
-        "binary break catch cd close concat continue dde eof encoding error " +
-        "eval exec exit expr fblocked fconfigure fcopy file fileevent filename " +
-        "filename flush for foreach format gets glob global history http if " +
-        "incr info interp join lappend lindex linsert list llength load lrange " +
-        "lreplace lsearch lset lsort memory msgcat namespace open package parray " +
-        "pid pkg::create pkg_mkIndex proc puts pwd re_syntax read regex regexp " +
-        "registry regsub rename resource return scan seek set socket source split " +
-        "string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord " +
-        "tcl_wordBreakAfter tcl_startOfPreviousWord tcl_wordBreakBefore tcltest " +
-        "tclvars tell time trace unknown unset update uplevel upvar variable " +
-    "vwait");
-    var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch");
-    var isOperatorChar = /[+\-*&%=<>!?^\/\|]/;
-    function chain(stream, state, f) {
-      state.tokenize = f;
-      return f(stream, state);
-    }
-    function tokenBase(stream, state) {
-      var beforeParams = state.beforeParams;
-      state.beforeParams = false;
-      var ch = stream.next();
-      if ((ch == '"' || ch == "'") && state.inParams)
-        return chain(stream, state, tokenString(ch));
-      else if (/[\[\]{}\(\),;\.]/.test(ch)) {
-        if (ch == "(" && beforeParams) state.inParams = true;
-        else if (ch == ")") state.inParams = false;
-          return null;
-      }
-      else if (/\d/.test(ch)) {
-        stream.eatWhile(/[\w\.]/);
-        return "number";
-      }
-      else if (ch == "#" && stream.eat("*")) {
-        return chain(stream, state, tokenComment);
-      }
-      else if (ch == "#" && stream.match(/ *\[ *\[/)) {
-        return chain(stream, state, tokenUnparsed);
-      }
-      else if (ch == "#" && stream.eat("#")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      else if (ch == '"') {
-        stream.skipTo(/"/);
-        return "comment";
-      }
-      else if (ch == "$") {
-        stream.eatWhile(/[$_a-z0-9A-Z\.{:]/);
-        stream.eatWhile(/}/);
-        state.beforeParams = true;
-        return "builtin";
-      }
-      else if (isOperatorChar.test(ch)) {
-        stream.eatWhile(isOperatorChar);
-        return "comment";
-      }
-      else {
-        stream.eatWhile(/[\w\$_{}\xa1-\uffff]/);
-        var word = stream.current().toLowerCase();
-        if (keywords && keywords.propertyIsEnumerable(word))
-          return "keyword";
-        if (functions && functions.propertyIsEnumerable(word)) {
-          state.beforeParams = true;
-          return "keyword";
-        }
-        return null;
-      }
-    }
-    function tokenString(quote) {
-      return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {
-          end = true;
-          break;
-        }
-        escaped = !escaped && next == "\\";
-      }
-      if (end) state.tokenize = tokenBase;
-        return "string";
-      };
-    }
-    function tokenComment(stream, state) {
-      var maybeEnd = false, ch;
-      while (ch = stream.next()) {
-        if (ch == "#" && maybeEnd) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        maybeEnd = (ch == "*");
-      }
-      return "comment";
-    }
-    function tokenUnparsed(stream, state) {
-      var maybeEnd = 0, ch;
-      while (ch = stream.next()) {
-        if (ch == "#" && maybeEnd == 2) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        if (ch == "]")
-          maybeEnd++;
-        else if (ch != " ")
-          maybeEnd = 0;
-      }
-      return "meta";
-    }
-    return {
-      startState: function() {
-        return {
-          tokenize: tokenBase,
-          beforeParams: false,
-          inParams: false
-        };
-      },
-      token: function(stream, state) {
-        if (stream.eatSpace()) return null;
-        return state.tokenize(stream, state);
-      }
-    };
-});
-CodeMirror.defineMIME("text/x-tcl", "tcl");
-
-});
--- a/kallithea/public/codemirror/mode/textile/textile.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,553 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == 'object' && typeof module == 'object') { // CommonJS
-    mod(require('../../lib/codemirror'));
-  } else if (typeof define == 'function' && define.amd) { // AMD
-    define(['../../lib/codemirror'], mod);
-  } else { // Plain browser env
-    mod(CodeMirror);
-  }
-})(function(CodeMirror) {
-'use strict';
-
-var TOKEN_STYLES = {
-  addition: 'positive',
-  attributes: 'attribute',
-  bold: 'strong',
-  cite: 'keyword',
-  code: 'atom',
-  definitionList: 'number',
-  deletion: 'negative',
-  div: 'punctuation',
-  em: 'em',
-  footnote: 'variable',
-  footCite: 'qualifier',
-  header: 'header',
-  html: 'comment',
-  image: 'string',
-  italic: 'em',
-  link: 'link',
-  linkDefinition: 'link',
-  list1: 'variable-2',
-  list2: 'variable-3',
-  list3: 'keyword',
-  notextile: 'string-2',
-  pre: 'operator',
-  p: 'property',
-  quote: 'bracket',
-  span: 'quote',
-  specialChar: 'tag',
-  strong: 'strong',
-  sub: 'builtin',
-  sup: 'builtin',
-  table: 'variable-3',
-  tableHeading: 'operator'
-};
-
-function Parser(regExpFactory, state, stream) {
-  this.regExpFactory = regExpFactory;
-  this.state = state;
-  this.stream = stream;
-  this.styles = TOKEN_STYLES;
-
-  this.state.specialChar = null;
-}
-
-Parser.prototype.eat = function(name) {
-  return this.stream.match(this.regExpFactory.pattern(name), true);
-};
-
-Parser.prototype.check = function(name) {
-  return this.stream.match(this.regExpFactory.pattern(name), false);
-};
-
-Parser.prototype.setModeForNextToken = function(mode) {
-  return this.state.mode = mode;
-};
-
-Parser.prototype.execMode = function(newMode) {
-  return this.setModeForNextToken(newMode).call(this);
-};
-
-Parser.prototype.startNewLine = function() {
-  this.setModeForNextToken(Modes.newLayout);
-  this.state.tableHeading = false;
-
-  if (this.state.layoutType === 'definitionList' && this.state.spanningLayout) {
-    if (this.check('definitionListEnd')) {
-      this.state.spanningLayout = false;
-    }
-  }
-};
-
-Parser.prototype.nextToken = function() {
-  return this.state.mode.call(this);
-};
-
-Parser.prototype.styleFor = function(token) {
-  if (this.styles.hasOwnProperty(token)) {
-    return this.styles[token];
-  }
-  throw 'unknown token';
-};
-
-Parser.prototype.handlePhraseModifier = function(ch) {
-  if (ch === '_') {
-    if (this.stream.eat('_')) {
-      return this.togglePhraseModifier('italic', /^.*__/);
-    }
-    return this.togglePhraseModifier('em', /^.*_/);
-  }
-
-  if (ch === '*') {
-    if (this.stream.eat('*')) {
-      return this.togglePhraseModifier('bold', /^.*\*\*/);
-    }
-    return this.togglePhraseModifier('strong', /^.*\*/);
-  }
-
-  if (ch === '[') {
-    if (this.stream.match(/\d+\]/)) {
-      this.state.footCite = true;
-    }
-    return this.tokenStyles();
-  }
-
-  if (ch === '(') {
-    if (this.stream.match('r)')) {
-      this.state.specialChar = 'r';
-    } else if (this.stream.match('tm)')) {
-      this.state.specialChar = 'tm';
-    } else if (this.stream.match('c)')) {
-      this.state.specialChar = 'c';
-    }
-    return this.tokenStyles();
-  }
-
-  if (ch === '<') {
-    if (this.stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) {
-      return this.tokenStylesWith(this.styleFor('html'));
-    }
-  }
-
-  if (ch === '?' && this.stream.eat('?')) {
-    return this.togglePhraseModifier('cite', /^.*\?\?/);
-  }
-  if (ch === '=' && this.stream.eat('=')) {
-    return this.togglePhraseModifier('notextile', /^.*==/);
-  }
-  if (ch === '-') {
-    return this.togglePhraseModifier('deletion', /^.*-/);
-  }
-  if (ch === '+') {
-    return this.togglePhraseModifier('addition', /^.*\+/);
-  }
-  if (ch === '~') {
-    return this.togglePhraseModifier('sub', /^.*~/);
-  }
-  if (ch === '^') {
-    return this.togglePhraseModifier('sup', /^.*\^/);
-  }
-  if (ch === '%') {
-    return this.togglePhraseModifier('span', /^.*%/);
-  }
-  if (ch === '@') {
-    return this.togglePhraseModifier('code', /^.*@/);
-  }
-  if (ch === '!') {
-    var type = this.togglePhraseModifier('image', /^.*(?:\([^\)]+\))?!/);
-    this.stream.match(/^:\S+/); // optional Url portion
-    return type;
-  }
-  return this.tokenStyles();
-};
-
-Parser.prototype.togglePhraseModifier = function(phraseModifier, closeRE) {
-  if (this.state[phraseModifier]) { // remove phrase modifier
-    var type = this.tokenStyles();
-    this.state[phraseModifier] = false;
-    return type;
-  }
-  if (this.stream.match(closeRE, false)) { // add phrase modifier
-    this.state[phraseModifier] = true;
-    this.setModeForNextToken(Modes.attributes);
-  }
-  return this.tokenStyles();
-};
-
-Parser.prototype.tokenStyles = function() {
-  var disabled = this.textileDisabled(),
-      styles = [];
-
-  if (disabled) return disabled;
-
-  if (this.state.layoutType) {
-    styles.push(this.styleFor(this.state.layoutType));
-  }
-
-  styles = styles.concat(this.activeStyles('addition', 'bold', 'cite', 'code',
-      'deletion', 'em', 'footCite', 'image', 'italic', 'link', 'span', 'specialChar', 'strong',
-      'sub', 'sup', 'table', 'tableHeading'));
-
-  if (this.state.layoutType === 'header') {
-    styles.push(this.styleFor('header') + '-' + this.state.header);
-  }
-  return styles.length ? styles.join(' ') : null;
-};
-
-Parser.prototype.textileDisabled = function() {
-  var type = this.state.layoutType;
-
-  switch(type) {
-    case 'notextile':
-    case 'code':
-    case 'pre':
-      return this.styleFor(type);
-    default:
-      if (this.state.notextile) {
-        return this.styleFor('notextile') + (type ? (' ' + this.styleFor(type)) : '');
-      }
-
-      return null;
-  }
-};
-
-Parser.prototype.tokenStylesWith = function(extraStyles) {
-  var disabled = this.textileDisabled(),
-      type;
-
-  if (disabled) return disabled;
-
-  type = this.tokenStyles();
-  if(extraStyles) {
-    return type ? (type + ' ' + extraStyles) : extraStyles;
-  }
-  return type;
-};
-
-Parser.prototype.activeStyles = function() {
-  var styles = [],
-      i;
-  for (i = 0; i < arguments.length; ++i) {
-    if (this.state[arguments[i]]) {
-      styles.push(this.styleFor(arguments[i]));
-    }
-  }
-  return styles;
-};
-
-Parser.prototype.blankLine = function() {
-  var spanningLayout = this.state.spanningLayout,
-      type = this.state.layoutType,
-      key;
-
-  for (key in this.state) {
-    if (this.state.hasOwnProperty(key)) {
-      delete this.state[key];
-    }
-  }
-
-  this.setModeForNextToken(Modes.newLayout);
-  if (spanningLayout) {
-    this.state.layoutType = type;
-    this.state.spanningLayout = true;
-  }
-};
-
-
-function RegExpFactory() {
-  this.cache = {};
-  this.single = {
-    bc: 'bc',
-    bq: 'bq',
-    definitionList: /- [^(?::=)]+:=+/,
-    definitionListEnd: /.*=:\s*$/,
-    div: 'div',
-    drawTable: /\|.*\|/,
-    foot: /fn\d+/,
-    header: /h[1-6]/,
-    html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
-    link: /[^"]+":\S/,
-    linkDefinition: /\[[^\s\]]+\]\S+/,
-    list: /(?:#+|\*+)/,
-    notextile: 'notextile',
-    para: 'p',
-    pre: 'pre',
-    table: 'table',
-    tableCellAttributes: /[/\\]\d+/,
-    tableHeading: /\|_\./,
-    tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
-    text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
-  };
-  this.attributes = {
-    align: /(?:<>|<|>|=)/,
-    selector: /\([^\(][^\)]+\)/,
-    lang: /\[[^\[\]]+\]/,
-    pad: /(?:\(+|\)+){1,2}/,
-    css: /\{[^\}]+\}/
-  };
-}
-
-RegExpFactory.prototype.pattern = function(name) {
-  return (this.cache[name] || this.createRe(name));
-};
-
-RegExpFactory.prototype.createRe = function(name) {
-  switch (name) {
-    case 'drawTable':
-      return this.makeRe('^', this.single.drawTable, '$');
-    case 'html':
-      return this.makeRe('^', this.single.html, '(?:', this.single.html, ')*', '$');
-    case 'linkDefinition':
-      return this.makeRe('^', this.single.linkDefinition, '$');
-    case 'listLayout':
-      return this.makeRe('^', this.single.list, this.pattern('allAttributes'), '*\\s+');
-    case 'tableCellAttributes':
-      return this.makeRe('^', this.choiceRe(this.single.tableCellAttributes,
-          this.pattern('allAttributes')), '+\\.');
-    case 'type':
-      return this.makeRe('^', this.pattern('allTypes'));
-    case 'typeLayout':
-      return this.makeRe('^', this.pattern('allTypes'), this.pattern('allAttributes'),
-          '*\\.\\.?', '(\\s+|$)');
-    case 'attributes':
-      return this.makeRe('^', this.pattern('allAttributes'), '+');
-
-    case 'allTypes':
-      return this.choiceRe(this.single.div, this.single.foot,
-          this.single.header, this.single.bc, this.single.bq,
-          this.single.notextile, this.single.pre, this.single.table,
-          this.single.para);
-
-    case 'allAttributes':
-      return this.choiceRe(this.attributes.selector, this.attributes.css,
-          this.attributes.lang, this.attributes.align, this.attributes.pad);
-
-    default:
-      return this.makeRe('^', this.single[name]);
-  }
-};
-
-
-RegExpFactory.prototype.makeRe = function() {
-  var pattern = '',
-      i,
-      arg;
-
-  for (i = 0; i < arguments.length; ++i) {
-    arg = arguments[i];
-    pattern += (typeof arg === 'string') ? arg : arg.source;
-  }
-  return new RegExp(pattern);
-};
-
-RegExpFactory.prototype.choiceRe = function() {
-  var parts = [arguments[0]],
-      i;
-
-  for (i = 1; i < arguments.length; ++i) {
-    parts[i * 2 - 1] = '|';
-    parts[i * 2] = arguments[i];
-  }
-
-  parts.unshift('(?:');
-  parts.push(')');
-  return this.makeRe.apply(this, parts);
-};
-
-
-var Modes = {
-  newLayout: function() {
-    if (this.check('typeLayout')) {
-      this.state.spanningLayout = false;
-      return this.execMode(Modes.blockType);
-    }
-    if (!this.textileDisabled()) {
-      if (this.check('listLayout')) {
-        return this.execMode(Modes.list);
-      } else if (this.check('drawTable')) {
-        return this.execMode(Modes.table);
-      } else if (this.check('linkDefinition')) {
-        return this.execMode(Modes.linkDefinition);
-      } else if (this.check('definitionList')) {
-        return this.execMode(Modes.definitionList);
-      } else if (this.check('html')) {
-        return this.execMode(Modes.html);
-      }
-    }
-    return this.execMode(Modes.text);
-  },
-
-  blockType: function() {
-    var match,
-        type;
-    this.state.layoutType = null;
-
-    if (match = this.eat('type')) {
-      type = match[0];
-    } else {
-      return this.execMode(Modes.text);
-    }
-
-    if(match = type.match(this.regExpFactory.pattern('header'))) {
-      this.state.layoutType = 'header';
-      this.state.header = parseInt(match[0][1]);
-    } else if (type.match(this.regExpFactory.pattern('bq'))) {
-      this.state.layoutType = 'quote';
-    } else if (type.match(this.regExpFactory.pattern('bc'))) {
-      this.state.layoutType = 'code';
-    } else if (type.match(this.regExpFactory.pattern('foot'))) {
-      this.state.layoutType = 'footnote';
-    } else if (type.match(this.regExpFactory.pattern('notextile'))) {
-      this.state.layoutType = 'notextile';
-    } else if (type.match(this.regExpFactory.pattern('pre'))) {
-      this.state.layoutType = 'pre';
-    } else if (type.match(this.regExpFactory.pattern('div'))) {
-      this.state.layoutType = 'div';
-    } else if (type.match(this.regExpFactory.pattern('table'))) {
-      this.state.layoutType = 'table';
-    }
-
-    this.setModeForNextToken(Modes.attributes);
-    return this.tokenStyles();
-  },
-
-  text: function() {
-    if (this.eat('text')) {
-      return this.tokenStyles();
-    }
-
-    var ch = this.stream.next();
-
-    if (ch === '"') {
-      return this.execMode(Modes.link);
-    }
-    return this.handlePhraseModifier(ch);
-  },
-
-  attributes: function() {
-    this.setModeForNextToken(Modes.layoutLength);
-
-    if (this.eat('attributes')) {
-      return this.tokenStylesWith(this.styleFor('attributes'));
-    }
-    return this.tokenStyles();
-  },
-
-  layoutLength: function() {
-    if (this.stream.eat('.') && this.stream.eat('.')) {
-      this.state.spanningLayout = true;
-    }
-
-    this.setModeForNextToken(Modes.text);
-    return this.tokenStyles();
-  },
-
-  list: function() {
-    var match = this.eat('list'),
-        listMod;
-    this.state.listDepth = match[0].length;
-    listMod = (this.state.listDepth - 1) % 3;
-    if (!listMod) {
-      this.state.layoutType = 'list1';
-    } else if (listMod === 1) {
-      this.state.layoutType = 'list2';
-    } else {
-      this.state.layoutType = 'list3';
-    }
-    this.setModeForNextToken(Modes.attributes);
-    return this.tokenStyles();
-  },
-
-  link: function() {
-    this.setModeForNextToken(Modes.text);
-    if (this.eat('link')) {
-      this.stream.match(/\S+/);
-      return this.tokenStylesWith(this.styleFor('link'));
-    }
-    return this.tokenStyles();
-  },
-
-  linkDefinition: function() {
-    this.stream.skipToEnd();
-    return this.tokenStylesWith(this.styleFor('linkDefinition'));
-  },
-
-  definitionList: function() {
-    this.eat('definitionList');
-
-    this.state.layoutType = 'definitionList';
-
-    if (this.stream.match(/\s*$/)) {
-      this.state.spanningLayout = true;
-    } else {
-      this.setModeForNextToken(Modes.attributes);
-    }
-    return this.tokenStyles();
-  },
-
-  html: function() {
-    this.stream.skipToEnd();
-    return this.tokenStylesWith(this.styleFor('html'));
-  },
-
-  table: function() {
-    this.state.layoutType = 'table';
-    return this.execMode(Modes.tableCell);
-  },
-
-  tableCell: function() {
-    if (this.eat('tableHeading')) {
-      this.state.tableHeading = true;
-    } else {
-      this.stream.eat('|');
-    }
-    this.setModeForNextToken(Modes.tableCellAttributes);
-    return this.tokenStyles();
-  },
-
-  tableCellAttributes: function() {
-    this.setModeForNextToken(Modes.tableText);
-
-    if (this.eat('tableCellAttributes')) {
-      return this.tokenStylesWith(this.styleFor('attributes'));
-    }
-    return this.tokenStyles();
-  },
-
-  tableText: function() {
-    if (this.eat('tableText')) {
-      return this.tokenStyles();
-    }
-
-    if (this.stream.peek() === '|') { // end of cell
-      this.setModeForNextToken(Modes.tableCell);
-      return this.tokenStyles();
-    }
-    return this.handlePhraseModifier(this.stream.next());
-  }
-};
-
-
-CodeMirror.defineMode('textile', function() {
-  var regExpFactory = new RegExpFactory();
-
-  return {
-    startState: function() {
-      return { mode: Modes.newLayout };
-    },
-    token: function(stream, state) {
-      var parser = new Parser(regExpFactory, state, stream);
-      if (stream.sol()) { parser.startNewLine(); }
-      return parser.nextToken();
-    },
-    blankLine: function(state) {
-      new Parser(regExpFactory, state).blankLine();
-    }
-  };
-});
-
-CodeMirror.defineMIME('text/x-textile', 'textile');
-});
--- a/kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-span.cm-underlined {
-  text-decoration: underline;
-}
-span.cm-strikethrough {
-  text-decoration: line-through;
-}
-span.cm-brace {
-  color: #170;
-  font-weight: bold;
-}
-span.cm-table {
-  color: blue;
-  font-weight: bold;
-}
--- a/kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,369 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/***
-    |''Name''|tiddlywiki.js|
-    |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|
-    |''Author''|PMario|
-    |''Version''|0.1.7|
-    |''Status''|''stable''|
-    |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]|
-    |''Documentation''|http://codemirror.tiddlyspace.com/|
-    |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]|
-    |''CoreVersion''|2.5.0|
-    |''Requires''|codemirror.js|
-    |''Keywords''|syntax highlighting color code mirror codemirror|
-    ! Info
-    CoreVersion parameter is needed for TiddlyWiki only!
-***/
-//{{{
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("tiddlywiki", function () {
-  // Tokenizer
-  var textwords = {};
-
-  var keywords = function () {
-    function kw(type) {
-      return { type: type, style: "macro"};
-    }
-    return {
-      "allTags": kw('allTags'), "closeAll": kw('closeAll'), "list": kw('list'),
-      "newJournal": kw('newJournal'), "newTiddler": kw('newTiddler'),
-      "permaview": kw('permaview'), "saveChanges": kw('saveChanges'),
-      "search": kw('search'), "slider": kw('slider'),   "tabs": kw('tabs'),
-      "tag": kw('tag'), "tagging": kw('tagging'),       "tags": kw('tags'),
-      "tiddler": kw('tiddler'), "timeline": kw('timeline'),
-      "today": kw('today'), "version": kw('version'),   "option": kw('option'),
-
-      "with": kw('with'),
-      "filter": kw('filter')
-    };
-  }();
-
-  var isSpaceName = /[\w_\-]/i,
-  reHR = /^\-\-\-\-+$/,                                 // <hr>
-  reWikiCommentStart = /^\/\*\*\*$/,            // /***
-  reWikiCommentStop = /^\*\*\*\/$/,             // ***/
-  reBlockQuote = /^<<<$/,
-
-  reJsCodeStart = /^\/\/\{\{\{$/,                       // //{{{ js block start
-  reJsCodeStop = /^\/\/\}\}\}$/,                        // //}}} js stop
-  reXmlCodeStart = /^<!--\{\{\{-->$/,           // xml block start
-  reXmlCodeStop = /^<!--\}\}\}-->$/,            // xml stop
-
-  reCodeBlockStart = /^\{\{\{$/,                        // {{{ TW text div block start
-  reCodeBlockStop = /^\}\}\}$/,                 // }}} TW text stop
-
-  reUntilCodeStop = /.*?\}\}\}/;
-
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-
-  // Used as scratch variables to communicate multiple values without
-  // consing up tons of objects.
-  var type, content;
-
-  function ret(tp, style, cont) {
-    type = tp;
-    content = cont;
-    return style;
-  }
-
-  function jsTokenBase(stream, state) {
-    var sol = stream.sol(), ch;
-
-    state.block = false;        // indicates the start of a code block.
-
-    ch = stream.peek();         // don't eat, to make matching simpler
-
-    // check start of  blocks
-    if (sol && /[<\/\*{}\-]/.test(ch)) {
-      if (stream.match(reCodeBlockStart)) {
-        state.block = true;
-        return chain(stream, state, twTokenCode);
-      }
-      if (stream.match(reBlockQuote)) {
-        return ret('quote', 'quote');
-      }
-      if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) {
-        return ret('code', 'comment');
-      }
-      if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) {
-        return ret('code', 'comment');
-      }
-      if (stream.match(reHR)) {
-        return ret('hr', 'hr');
-      }
-    } // sol
-    ch = stream.next();
-
-    if (sol && /[\/\*!#;:>|]/.test(ch)) {
-      if (ch == "!") { // tw header
-        stream.skipToEnd();
-        return ret("header", "header");
-      }
-      if (ch == "*") { // tw list
-        stream.eatWhile('*');
-        return ret("list", "comment");
-      }
-      if (ch == "#") { // tw numbered list
-        stream.eatWhile('#');
-        return ret("list", "comment");
-      }
-      if (ch == ";") { // definition list, term
-        stream.eatWhile(';');
-        return ret("list", "comment");
-      }
-      if (ch == ":") { // definition list, description
-        stream.eatWhile(':');
-        return ret("list", "comment");
-      }
-      if (ch == ">") { // single line quote
-        stream.eatWhile(">");
-        return ret("quote", "quote");
-      }
-      if (ch == '|') {
-        return ret('table', 'header');
-      }
-    }
-
-    if (ch == '{' && stream.match(/\{\{/)) {
-      return chain(stream, state, twTokenCode);
-    }
-
-    // rudimentary html:// file:// link matching. TW knows much more ...
-    if (/[hf]/i.test(ch)) {
-      if (/[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) {
-        return ret("link", "link");
-      }
-    }
-    // just a little string indicator, don't want to have the whole string covered
-    if (ch == '"') {
-      return ret('string', 'string');
-    }
-    if (ch == '~') {    // _no_ CamelCase indicator should be bold
-      return ret('text', 'brace');
-    }
-    if (/[\[\]]/.test(ch)) { // check for [[..]]
-      if (stream.peek() == ch) {
-        stream.next();
-        return ret('brace', 'brace');
-      }
-    }
-    if (ch == "@") {    // check for space link. TODO fix @@...@@ highlighting
-      stream.eatWhile(isSpaceName);
-      return ret("link", "link");
-    }
-    if (/\d/.test(ch)) {        // numbers
-      stream.eatWhile(/\d/);
-      return ret("number", "number");
-    }
-    if (ch == "/") { // tw invisible comment
-      if (stream.eat("%")) {
-        return chain(stream, state, twTokenComment);
-      }
-      else if (stream.eat("/")) { //
-        return chain(stream, state, twTokenEm);
-      }
-    }
-    if (ch == "_") { // tw underline
-      if (stream.eat("_")) {
-        return chain(stream, state, twTokenUnderline);
-      }
-    }
-    // strikethrough and mdash handling
-    if (ch == "-") {
-      if (stream.eat("-")) {
-        // if strikethrough looks ugly, change CSS.
-        if (stream.peek() != ' ')
-          return chain(stream, state, twTokenStrike);
-        // mdash
-        if (stream.peek() == ' ')
-          return ret('text', 'brace');
-      }
-    }
-    if (ch == "'") { // tw bold
-      if (stream.eat("'")) {
-        return chain(stream, state, twTokenStrong);
-      }
-    }
-    if (ch == "<") { // tw macro
-      if (stream.eat("<")) {
-        return chain(stream, state, twTokenMacro);
-      }
-    }
-    else {
-      return ret(ch);
-    }
-
-    // core macro handling
-    stream.eatWhile(/[\w\$_]/);
-    var word = stream.current(),
-    known = textwords.propertyIsEnumerable(word) && textwords[word];
-
-    return known ? ret(known.type, known.style, word) : ret("text", null, word);
-
-  } // jsTokenBase()
-
-  // tw invisible comment
-  function twTokenComment(stream, state) {
-    var maybeEnd = false,
-    ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = jsTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "%");
-    }
-    return ret("comment", "comment");
-  }
-
-  // tw strong / bold
-  function twTokenStrong(stream, state) {
-    var maybeEnd = false,
-    ch;
-    while (ch = stream.next()) {
-      if (ch == "'" && maybeEnd) {
-        state.tokenize = jsTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "'");
-    }
-    return ret("text", "strong");
-  }
-
-  // tw code
-  function twTokenCode(stream, state) {
-    var ch, sb = state.block;
-
-    if (sb && stream.current()) {
-      return ret("code", "comment");
-    }
-
-    if (!sb && stream.match(reUntilCodeStop)) {
-      state.tokenize = jsTokenBase;
-      return ret("code", "comment");
-    }
-
-    if (sb && stream.sol() && stream.match(reCodeBlockStop)) {
-      state.tokenize = jsTokenBase;
-      return ret("code", "comment");
-    }
-
-    ch = stream.next();
-    return (sb) ? ret("code", "comment") : ret("code", "comment");
-  }
-
-  // tw em / italic
-  function twTokenEm(stream, state) {
-    var maybeEnd = false,
-    ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = jsTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "/");
-    }
-    return ret("text", "em");
-  }
-
-  // tw underlined text
-  function twTokenUnderline(stream, state) {
-    var maybeEnd = false,
-    ch;
-    while (ch = stream.next()) {
-      if (ch == "_" && maybeEnd) {
-        state.tokenize = jsTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "_");
-    }
-    return ret("text", "underlined");
-  }
-
-  // tw strike through text looks ugly
-  // change CSS if needed
-  function twTokenStrike(stream, state) {
-    var maybeEnd = false, ch;
-
-    while (ch = stream.next()) {
-      if (ch == "-" && maybeEnd) {
-        state.tokenize = jsTokenBase;
-        break;
-      }
-      maybeEnd = (ch == "-");
-    }
-    return ret("text", "strikethrough");
-  }
-
-  // macro
-  function twTokenMacro(stream, state) {
-    var ch, word, known;
-
-    if (stream.current() == '<<') {
-      return ret('brace', 'macro');
-    }
-
-    ch = stream.next();
-    if (!ch) {
-      state.tokenize = jsTokenBase;
-      return ret(ch);
-    }
-    if (ch == ">") {
-      if (stream.peek() == '>') {
-        stream.next();
-        state.tokenize = jsTokenBase;
-        return ret("brace", "macro");
-      }
-    }
-
-    stream.eatWhile(/[\w\$_]/);
-    word = stream.current();
-    known = keywords.propertyIsEnumerable(word) && keywords[word];
-
-    if (known) {
-      return ret(known.type, known.style, word);
-    }
-    else {
-      return ret("macro", null, word);
-    }
-  }
-
-  // Interface
-  return {
-    startState: function () {
-      return {
-        tokenize: jsTokenBase,
-        indented: 0,
-        level: 0
-      };
-    },
-
-    token: function (stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      return style;
-    },
-
-    electricChars: ""
-  };
-});
-
-CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki");
-});
-
-//}}}
--- a/kallithea/public/codemirror/mode/tiki/tiki.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-.cm-tw-syntaxerror {
-	color: #FFF;
-	background-color: #900;
-}
-
-.cm-tw-deleted {
-	text-decoration: line-through;
-}
-
-.cm-tw-header5 {
-	font-weight: bold;
-}
-.cm-tw-listitem:first-child { /*Added first child to fix duplicate padding when highlighting*/
-	padding-left: 10px;
-}
-
-.cm-tw-box {
-	border-top-width: 0px ! important;
-	border-style: solid;
-	border-width: 1px;
-	border-color: inherit;
-}
-
-.cm-tw-underline {
-	text-decoration: underline;
-}
\ No newline at end of file
--- a/kallithea/public/codemirror/mode/tiki/tiki.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,323 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('tiki', function(config) {
-  function inBlock(style, terminator, returnTokenizer) {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        if (stream.match(terminator)) {
-          state.tokenize = inText;
-          break;
-        }
-        stream.next();
-      }
-
-      if (returnTokenizer) state.tokenize = returnTokenizer;
-
-      return style;
-    };
-  }
-
-  function inLine(style) {
-    return function(stream, state) {
-      while(!stream.eol()) {
-        stream.next();
-      }
-      state.tokenize = inText;
-      return style;
-    };
-  }
-
-  function inText(stream, state) {
-    function chain(parser) {
-      state.tokenize = parser;
-      return parser(stream, state);
-    }
-
-    var sol = stream.sol();
-    var ch = stream.next();
-
-    //non start of line
-    switch (ch) { //switch is generally much faster than if, so it is used here
-    case "{": //plugin
-      stream.eat("/");
-      stream.eatSpace();
-      var tagName = "";
-      var c;
-      while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c;
-      state.tokenize = inPlugin;
-      return "tag";
-      break;
-    case "_": //bold
-      if (stream.eat("_")) {
-        return chain(inBlock("strong", "__", inText));
-      }
-      break;
-    case "'": //italics
-      if (stream.eat("'")) {
-        // Italic text
-        return chain(inBlock("em", "''", inText));
-      }
-      break;
-    case "(":// Wiki Link
-      if (stream.eat("(")) {
-        return chain(inBlock("variable-2", "))", inText));
-      }
-      break;
-    case "[":// Weblink
-      return chain(inBlock("variable-3", "]", inText));
-      break;
-    case "|": //table
-      if (stream.eat("|")) {
-        return chain(inBlock("comment", "||"));
-      }
-      break;
-    case "-":
-      if (stream.eat("=")) {//titleBar
-        return chain(inBlock("header string", "=-", inText));
-      } else if (stream.eat("-")) {//deleted
-        return chain(inBlock("error tw-deleted", "--", inText));
-      }
-      break;
-    case "=": //underline
-      if (stream.match("==")) {
-        return chain(inBlock("tw-underline", "===", inText));
-      }
-      break;
-    case ":":
-      if (stream.eat(":")) {
-        return chain(inBlock("comment", "::"));
-      }
-      break;
-    case "^": //box
-      return chain(inBlock("tw-box", "^"));
-      break;
-    case "~": //np
-      if (stream.match("np~")) {
-        return chain(inBlock("meta", "~/np~"));
-      }
-      break;
-    }
-
-    //start of line types
-    if (sol) {
-      switch (ch) {
-      case "!": //header at start of line
-        if (stream.match('!!!!!')) {
-          return chain(inLine("header string"));
-        } else if (stream.match('!!!!')) {
-          return chain(inLine("header string"));
-        } else if (stream.match('!!!')) {
-          return chain(inLine("header string"));
-        } else if (stream.match('!!')) {
-          return chain(inLine("header string"));
-        } else {
-          return chain(inLine("header string"));
-        }
-        break;
-      case "*": //unordered list line item, or <li /> at start of line
-      case "#": //ordered list line item, or <li /> at start of line
-      case "+": //ordered list line item, or <li /> at start of line
-        return chain(inLine("tw-listitem bracket"));
-        break;
-      }
-    }
-
-    //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki
-    return null;
-  }
-
-  var indentUnit = config.indentUnit;
-
-  // Return variables for tokenizers
-  var pluginName, type;
-  function inPlugin(stream, state) {
-    var ch = stream.next();
-    var peek = stream.peek();
-
-    if (ch == "}") {
-      state.tokenize = inText;
-      //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin
-      return "tag";
-    } else if (ch == "(" || ch == ")") {
-      return "bracket";
-    } else if (ch == "=") {
-      type = "equals";
-
-      if (peek == ">") {
-        ch = stream.next();
-        peek = stream.peek();
-      }
-
-      //here we detect values directly after equal character with no quotes
-      if (!/[\'\"]/.test(peek)) {
-        state.tokenize = inAttributeNoQuote();
-      }
-      //end detect values
-
-      return "operator";
-    } else if (/[\'\"]/.test(ch)) {
-      state.tokenize = inAttribute(ch);
-      return state.tokenize(stream, state);
-    } else {
-      stream.eatWhile(/[^\s\u00a0=\"\'\/?]/);
-      return "keyword";
-    }
-  }
-
-  function inAttribute(quote) {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        if (stream.next() == quote) {
-          state.tokenize = inPlugin;
-          break;
-        }
-      }
-      return "string";
-    };
-  }
-
-  function inAttributeNoQuote() {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        var ch = stream.next();
-        var peek = stream.peek();
-        if (ch == " " || ch == "," || /[ )}]/.test(peek)) {
-      state.tokenize = inPlugin;
-      break;
-    }
-  }
-  return "string";
-};
-                     }
-
-var curState, setStyle;
-function pass() {
-  for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
-}
-
-function cont() {
-  pass.apply(null, arguments);
-  return true;
-}
-
-function pushContext(pluginName, startOfLine) {
-  var noIndent = curState.context && curState.context.noIndent;
-  curState.context = {
-    prev: curState.context,
-    pluginName: pluginName,
-    indent: curState.indented,
-    startOfLine: startOfLine,
-    noIndent: noIndent
-  };
-}
-
-function popContext() {
-  if (curState.context) curState.context = curState.context.prev;
-}
-
-function element(type) {
-  if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));}
-  else if (type == "closePlugin") {
-    var err = false;
-    if (curState.context) {
-      err = curState.context.pluginName != pluginName;
-      popContext();
-    } else {
-      err = true;
-    }
-    if (err) setStyle = "error";
-    return cont(endcloseplugin(err));
-  }
-  else if (type == "string") {
-    if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");
-    if (curState.tokenize == inText) popContext();
-    return cont();
-  }
-  else return cont();
-}
-
-function endplugin(startOfLine) {
-  return function(type) {
-    if (
-      type == "selfclosePlugin" ||
-        type == "endPlugin"
-    )
-      return cont();
-    if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();}
-    return cont();
-  };
-}
-
-function endcloseplugin(err) {
-  return function(type) {
-    if (err) setStyle = "error";
-    if (type == "endPlugin") return cont();
-    return pass();
-  };
-}
-
-function attributes(type) {
-  if (type == "keyword") {setStyle = "attribute"; return cont(attributes);}
-  if (type == "equals") return cont(attvalue, attributes);
-  return pass();
-}
-function attvalue(type) {
-  if (type == "keyword") {setStyle = "string"; return cont();}
-  if (type == "string") return cont(attvaluemaybe);
-  return pass();
-}
-function attvaluemaybe(type) {
-  if (type == "string") return cont(attvaluemaybe);
-  else return pass();
-}
-return {
-  startState: function() {
-    return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null};
-  },
-  token: function(stream, state) {
-    if (stream.sol()) {
-      state.startOfLine = true;
-      state.indented = stream.indentation();
-    }
-    if (stream.eatSpace()) return null;
-
-    setStyle = type = pluginName = null;
-    var style = state.tokenize(stream, state);
-    if ((style || type) && style != "comment") {
-      curState = state;
-      while (true) {
-        var comb = state.cc.pop() || element;
-        if (comb(type || style)) break;
-      }
-    }
-    state.startOfLine = false;
-    return setStyle || style;
-  },
-  indent: function(state, textAfter) {
-    var context = state.context;
-    if (context && context.noIndent) return 0;
-    if (context && /^{\//.test(textAfter))
-        context = context.prev;
-        while (context && !context.startOfLine)
-          context = context.prev;
-        if (context) return context.indent + indentUnit;
-        else return 0;
-       },
-    electricChars: "/"
-  };
-});
-
-CodeMirror.defineMIME("text/tiki", "tiki");
-
-});
--- a/kallithea/public/codemirror/mode/toml/toml.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("toml", function () {
-  return {
-    startState: function () {
-      return {
-        inString: false,
-        stringType: "",
-        lhs: true,
-        inArray: 0
-      };
-    },
-    token: function (stream, state) {
-      //check for state changes
-      if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) {
-        state.stringType = stream.peek();
-        stream.next(); // Skip quote
-        state.inString = true; // Update state
-      }
-      if (stream.sol() && state.inArray === 0) {
-        state.lhs = true;
-      }
-      //return state
-      if (state.inString) {
-        while (state.inString && !stream.eol()) {
-          if (stream.peek() === state.stringType) {
-            stream.next(); // Skip quote
-            state.inString = false; // Clear flag
-          } else if (stream.peek() === '\\') {
-            stream.next();
-            stream.next();
-          } else {
-            stream.match(/^.[^\\\"\']*/);
-          }
-        }
-        return state.lhs ? "property string" : "string"; // Token style
-      } else if (state.inArray && stream.peek() === ']') {
-        stream.next();
-        state.inArray--;
-        return 'bracket';
-      } else if (state.lhs && stream.peek() === '[' && stream.skipTo(']')) {
-        stream.next();//skip closing ]
-        // array of objects has an extra open & close []
-        if (stream.peek() === ']') stream.next();
-        return "atom";
-      } else if (stream.peek() === "#") {
-        stream.skipToEnd();
-        return "comment";
-      } else if (stream.eatSpace()) {
-        return null;
-      } else if (state.lhs && stream.eatWhile(function (c) { return c != '=' && c != ' '; })) {
-        return "property";
-      } else if (state.lhs && stream.peek() === "=") {
-        stream.next();
-        state.lhs = false;
-        return null;
-      } else if (!state.lhs && stream.match(/^\d\d\d\d[\d\-\:\.T]*Z/)) {
-        return 'atom'; //date
-      } else if (!state.lhs && (stream.match('true') || stream.match('false'))) {
-        return 'atom';
-      } else if (!state.lhs && stream.peek() === '[') {
-        state.inArray++;
-        stream.next();
-        return 'bracket';
-      } else if (!state.lhs && stream.match(/^\-?\d+(?:\.\d+)?/)) {
-        return 'number';
-      } else if (!stream.eatSpace()) {
-        stream.next();
-      }
-      return null;
-    }
-  };
-});
-
-CodeMirror.defineMIME('text/x-toml', 'toml');
-
-});
--- a/kallithea/public/codemirror/mode/tornado/tornado.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"),
-        require("../../addon/mode/overlay"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror", "../htmlmixed/htmlmixed",
-            "../../addon/mode/overlay"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-  "use strict";
-
-  CodeMirror.defineMode("tornado:inner", function() {
-    var keywords = ["and","as","assert","autoescape","block","break","class","comment","context",
-                    "continue","datetime","def","del","elif","else","end","escape","except",
-                    "exec","extends","false","finally","for","from","global","if","import","in",
-                    "include","is","json_encode","lambda","length","linkify","load","module",
-                    "none","not","or","pass","print","put","raise","raw","return","self","set",
-                    "squeeze","super","true","try","url_escape","while","with","without","xhtml_escape","yield"];
-    keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b");
-
-    function tokenBase (stream, state) {
-      stream.eatWhile(/[^\{]/);
-      var ch = stream.next();
-      if (ch == "{") {
-        if (ch = stream.eat(/\{|%|#/)) {
-          state.tokenize = inTag(ch);
-          return "tag";
-        }
-      }
-    }
-    function inTag (close) {
-      if (close == "{") {
-        close = "}";
-      }
-      return function (stream, state) {
-        var ch = stream.next();
-        if ((ch == close) && stream.eat("}")) {
-          state.tokenize = tokenBase;
-          return "tag";
-        }
-        if (stream.match(keywords)) {
-          return "keyword";
-        }
-        return close == "#" ? "comment" : "string";
-      };
-    }
-    return {
-      startState: function () {
-        return {tokenize: tokenBase};
-      },
-      token: function (stream, state) {
-        return state.tokenize(stream, state);
-      }
-    };
-  });
-
-  CodeMirror.defineMode("tornado", function(config) {
-    var htmlBase = CodeMirror.getMode(config, "text/html");
-    var tornadoInner = CodeMirror.getMode(config, "tornado:inner");
-    return CodeMirror.overlayMode(htmlBase, tornadoInner);
-  });
-
-  CodeMirror.defineMIME("text/x-tornado", "tornado");
-});
--- a/kallithea/public/codemirror/mode/turtle/turtle.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("turtle", function(config) {
-  var indentUnit = config.indentUnit;
-  var curPunc;
-
-  function wordRegexp(words) {
-    return new RegExp("^(?:" + words.join("|") + ")$", "i");
-  }
-  var ops = wordRegexp([]);
-  var keywords = wordRegexp(["@prefix", "@base", "a"]);
-  var operatorChars = /[*+\-<>=&|]/;
-
-  function tokenBase(stream, state) {
-    var ch = stream.next();
-    curPunc = null;
-    if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) {
-      stream.match(/^[^\s\u00a0>]*>?/);
-      return "atom";
-    }
-    else if (ch == "\"" || ch == "'") {
-      state.tokenize = tokenLiteral(ch);
-      return state.tokenize(stream, state);
-    }
-    else if (/[{}\(\),\.;\[\]]/.test(ch)) {
-      curPunc = ch;
-      return null;
-    }
-    else if (ch == "#") {
-      stream.skipToEnd();
-      return "comment";
-    }
-    else if (operatorChars.test(ch)) {
-      stream.eatWhile(operatorChars);
-      return null;
-    }
-    else if (ch == ":") {
-          return "operator";
-        } else {
-      stream.eatWhile(/[_\w\d]/);
-      if(stream.peek() == ":") {
-        return "variable-3";
-      } else {
-             var word = stream.current();
-
-             if(keywords.test(word)) {
-                        return "meta";
-             }
-
-             if(ch >= "A" && ch <= "Z") {
-                    return "comment";
-                 } else {
-                        return "keyword";
-                 }
-      }
-      var word = stream.current();
-      if (ops.test(word))
-        return null;
-      else if (keywords.test(word))
-        return "meta";
-      else
-        return "variable";
-    }
-  }
-
-  function tokenLiteral(quote) {
-    return function(stream, state) {
-      var escaped = false, ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == quote && !escaped) {
-          state.tokenize = tokenBase;
-          break;
-        }
-        escaped = !escaped && ch == "\\";
-      }
-      return "string";
-    };
-  }
-
-  function pushContext(state, type, col) {
-    state.context = {prev: state.context, indent: state.indent, col: col, type: type};
-  }
-  function popContext(state) {
-    state.indent = state.context.indent;
-    state.context = state.context.prev;
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: tokenBase,
-              context: null,
-              indent: 0,
-              col: 0};
-    },
-
-    token: function(stream, state) {
-      if (stream.sol()) {
-        if (state.context && state.context.align == null) state.context.align = false;
-        state.indent = stream.indentation();
-      }
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-
-      if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") {
-        state.context.align = true;
-      }
-
-      if (curPunc == "(") pushContext(state, ")", stream.column());
-      else if (curPunc == "[") pushContext(state, "]", stream.column());
-      else if (curPunc == "{") pushContext(state, "}", stream.column());
-      else if (/[\]\}\)]/.test(curPunc)) {
-        while (state.context && state.context.type == "pattern") popContext(state);
-        if (state.context && curPunc == state.context.type) popContext(state);
-      }
-      else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state);
-      else if (/atom|string|variable/.test(style) && state.context) {
-        if (/[\}\]]/.test(state.context.type))
-          pushContext(state, "pattern", stream.column());
-        else if (state.context.type == "pattern" && !state.context.align) {
-          state.context.align = true;
-          state.context.col = stream.column();
-        }
-      }
-
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      var firstChar = textAfter && textAfter.charAt(0);
-      var context = state.context;
-      if (/[\]\}]/.test(firstChar))
-        while (context && context.type == "pattern") context = context.prev;
-
-      var closing = context && firstChar == context.type;
-      if (!context)
-        return 0;
-      else if (context.type == "pattern")
-        return context.col;
-      else if (context.align)
-        return context.col + (closing ? 0 : 1);
-      else
-        return context.indent + (closing ? 0 : indentUnit);
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/turtle", "turtle");
-
-});
--- a/kallithea/public/codemirror/mode/vb/vb.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("vb", function(conf, parserConf) {
-    var ERRORCLASS = 'error';
-
-    function wordRegexp(words) {
-        return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
-    }
-
-    var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]");
-    var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
-    var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
-    var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
-    var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
-    var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
-
-    var openingKeywords = ['class','module', 'sub','enum','select','while','if','function',  'get','set','property', 'try'];
-    var middleKeywords = ['else','elseif','case', 'catch'];
-    var endKeywords = ['next','loop'];
-
-    var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']);
-    var commonkeywords = ['as', 'dim', 'break',  'continue','optional', 'then',  'until',
-                          'goto', 'byval','byref','new','handles','property', 'return',
-                          'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false'];
-    var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single'];
-
-    var keywords = wordRegexp(commonkeywords);
-    var types = wordRegexp(commontypes);
-    var stringPrefixes = '"';
-
-    var opening = wordRegexp(openingKeywords);
-    var middle = wordRegexp(middleKeywords);
-    var closing = wordRegexp(endKeywords);
-    var doubleClosing = wordRegexp(['end']);
-    var doOpening = wordRegexp(['do']);
-
-    var indentInfo = null;
-
-
-
-
-    function indent(_stream, state) {
-      state.currentIndent++;
-    }
-
-    function dedent(_stream, state) {
-      state.currentIndent--;
-    }
-    // tokenizers
-    function tokenBase(stream, state) {
-        if (stream.eatSpace()) {
-            return null;
-        }
-
-        var ch = stream.peek();
-
-        // Handle Comments
-        if (ch === "'") {
-            stream.skipToEnd();
-            return 'comment';
-        }
-
-
-        // Handle Number Literals
-        if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) {
-            var floatLiteral = false;
-            // Floats
-            if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; }
-            else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; }
-            else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; }
-
-            if (floatLiteral) {
-                // Float literals may be "imaginary"
-                stream.eat(/J/i);
-                return 'number';
-            }
-            // Integers
-            var intLiteral = false;
-            // Hex
-            if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; }
-            // Octal
-            else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; }
-            // Decimal
-            else if (stream.match(/^[1-9]\d*F?/)) {
-                // Decimal literals may be "imaginary"
-                stream.eat(/J/i);
-                // TODO - Can you have imaginary longs?
-                intLiteral = true;
-            }
-            // Zero by itself with no other piece of number.
-            else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
-            if (intLiteral) {
-                // Integer literals may be "long"
-                stream.eat(/L/i);
-                return 'number';
-            }
-        }
-
-        // Handle Strings
-        if (stream.match(stringPrefixes)) {
-            state.tokenize = tokenStringFactory(stream.current());
-            return state.tokenize(stream, state);
-        }
-
-        // Handle operators and Delimiters
-        if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
-            return null;
-        }
-        if (stream.match(doubleOperators)
-            || stream.match(singleOperators)
-            || stream.match(wordOperators)) {
-            return 'operator';
-        }
-        if (stream.match(singleDelimiters)) {
-            return null;
-        }
-        if (stream.match(doOpening)) {
-            indent(stream,state);
-            state.doInCurrentLine = true;
-            return 'keyword';
-        }
-        if (stream.match(opening)) {
-            if (! state.doInCurrentLine)
-              indent(stream,state);
-            else
-              state.doInCurrentLine = false;
-            return 'keyword';
-        }
-        if (stream.match(middle)) {
-            return 'keyword';
-        }
-
-        if (stream.match(doubleClosing)) {
-            dedent(stream,state);
-            dedent(stream,state);
-            return 'keyword';
-        }
-        if (stream.match(closing)) {
-            dedent(stream,state);
-            return 'keyword';
-        }
-
-        if (stream.match(types)) {
-            return 'keyword';
-        }
-
-        if (stream.match(keywords)) {
-            return 'keyword';
-        }
-
-        if (stream.match(identifiers)) {
-            return 'variable';
-        }
-
-        // Handle non-detected items
-        stream.next();
-        return ERRORCLASS;
-    }
-
-    function tokenStringFactory(delimiter) {
-        var singleline = delimiter.length == 1;
-        var OUTCLASS = 'string';
-
-        return function(stream, state) {
-            while (!stream.eol()) {
-                stream.eatWhile(/[^'"]/);
-                if (stream.match(delimiter)) {
-                    state.tokenize = tokenBase;
-                    return OUTCLASS;
-                } else {
-                    stream.eat(/['"]/);
-                }
-            }
-            if (singleline) {
-                if (parserConf.singleLineStringErrors) {
-                    return ERRORCLASS;
-                } else {
-                    state.tokenize = tokenBase;
-                }
-            }
-            return OUTCLASS;
-        };
-    }
-
-
-    function tokenLexer(stream, state) {
-        var style = state.tokenize(stream, state);
-        var current = stream.current();
-
-        // Handle '.' connected identifiers
-        if (current === '.') {
-            style = state.tokenize(stream, state);
-            current = stream.current();
-            if (style === 'variable') {
-                return 'variable';
-            } else {
-                return ERRORCLASS;
-            }
-        }
-
-
-        var delimiter_index = '[({'.indexOf(current);
-        if (delimiter_index !== -1) {
-            indent(stream, state );
-        }
-        if (indentInfo === 'dedent') {
-            if (dedent(stream, state)) {
-                return ERRORCLASS;
-            }
-        }
-        delimiter_index = '])}'.indexOf(current);
-        if (delimiter_index !== -1) {
-            if (dedent(stream, state)) {
-                return ERRORCLASS;
-            }
-        }
-
-        return style;
-    }
-
-    var external = {
-        electricChars:"dDpPtTfFeE ",
-        startState: function() {
-            return {
-              tokenize: tokenBase,
-              lastToken: null,
-              currentIndent: 0,
-              nextLineIndent: 0,
-              doInCurrentLine: false
-
-
-          };
-        },
-
-        token: function(stream, state) {
-            if (stream.sol()) {
-              state.currentIndent += state.nextLineIndent;
-              state.nextLineIndent = 0;
-              state.doInCurrentLine = 0;
-            }
-            var style = tokenLexer(stream, state);
-
-            state.lastToken = {style:style, content: stream.current()};
-
-
-
-            return style;
-        },
-
-        indent: function(state, textAfter) {
-            var trueText = textAfter.replace(/^\s+|\s+$/g, '') ;
-            if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1);
-            if(state.currentIndent < 0) return 0;
-            return state.currentIndent * conf.indentUnit;
-        }
-
-    };
-    return external;
-});
-
-CodeMirror.defineMIME("text/x-vb", "vb");
-
-});
--- a/kallithea/public/codemirror/mode/vbscript/vbscript.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,350 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/*
-For extra ASP classic objects, initialize CodeMirror instance with this option:
-    isASP: true
-
-E.G.:
-    var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
-        lineNumbers: true,
-        isASP: true
-      });
-*/
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("vbscript", function(conf, parserConf) {
-    var ERRORCLASS = 'error';
-
-    function wordRegexp(words) {
-        return new RegExp("^((" + words.join(")|(") + "))\\b", "i");
-    }
-
-    var singleOperators = new RegExp("^[\\+\\-\\*/&\\\\\\^<>=]");
-    var doubleOperators = new RegExp("^((<>)|(<=)|(>=))");
-    var singleDelimiters = new RegExp('^[\\.,]');
-    var brakets = new RegExp('^[\\(\\)]');
-    var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*");
-
-    var openingKeywords = ['class','sub','select','while','if','function', 'property', 'with', 'for'];
-    var middleKeywords = ['else','elseif','case'];
-    var endKeywords = ['next','loop','wend'];
-
-    var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']);
-    var commonkeywords = ['dim', 'redim', 'then',  'until', 'randomize',
-                          'byval','byref','new','property', 'exit', 'in',
-                          'const','private', 'public',
-                          'get','set','let', 'stop', 'on error resume next', 'on error goto 0', 'option explicit', 'call', 'me'];
-
-    //This list was from: http://msdn.microsoft.com/en-us/library/f8tbc79x(v=vs.84).aspx
-    var atomWords = ['true', 'false', 'nothing', 'empty', 'null'];
-    //This list was from: http://msdn.microsoft.com/en-us/library/3ca8tfek(v=vs.84).aspx
-    var builtinFuncsWords = ['abs', 'array', 'asc', 'atn', 'cbool', 'cbyte', 'ccur', 'cdate', 'cdbl', 'chr', 'cint', 'clng', 'cos', 'csng', 'cstr', 'date', 'dateadd', 'datediff', 'datepart',
-                        'dateserial', 'datevalue', 'day', 'escape', 'eval', 'execute', 'exp', 'filter', 'formatcurrency', 'formatdatetime', 'formatnumber', 'formatpercent', 'getlocale', 'getobject',
-                        'getref', 'hex', 'hour', 'inputbox', 'instr', 'instrrev', 'int', 'fix', 'isarray', 'isdate', 'isempty', 'isnull', 'isnumeric', 'isobject', 'join', 'lbound', 'lcase', 'left',
-                        'len', 'loadpicture', 'log', 'ltrim', 'rtrim', 'trim', 'maths', 'mid', 'minute', 'month', 'monthname', 'msgbox', 'now', 'oct', 'replace', 'rgb', 'right', 'rnd', 'round',
-                        'scriptengine', 'scriptenginebuildversion', 'scriptenginemajorversion', 'scriptengineminorversion', 'second', 'setlocale', 'sgn', 'sin', 'space', 'split', 'sqr', 'strcomp',
-                        'string', 'strreverse', 'tan', 'time', 'timer', 'timeserial', 'timevalue', 'typename', 'ubound', 'ucase', 'unescape', 'vartype', 'weekday', 'weekdayname', 'year'];
-
-    //This list was from: http://msdn.microsoft.com/en-us/library/ydz4cfk3(v=vs.84).aspx
-    var builtinConsts = ['vbBlack', 'vbRed', 'vbGreen', 'vbYellow', 'vbBlue', 'vbMagenta', 'vbCyan', 'vbWhite', 'vbBinaryCompare', 'vbTextCompare',
-                         'vbSunday', 'vbMonday', 'vbTuesday', 'vbWednesday', 'vbThursday', 'vbFriday', 'vbSaturday', 'vbUseSystemDayOfWeek', 'vbFirstJan1', 'vbFirstFourDays', 'vbFirstFullWeek',
-                         'vbGeneralDate', 'vbLongDate', 'vbShortDate', 'vbLongTime', 'vbShortTime', 'vbObjectError',
-                         'vbOKOnly', 'vbOKCancel', 'vbAbortRetryIgnore', 'vbYesNoCancel', 'vbYesNo', 'vbRetryCancel', 'vbCritical', 'vbQuestion', 'vbExclamation', 'vbInformation', 'vbDefaultButton1', 'vbDefaultButton2',
-                         'vbDefaultButton3', 'vbDefaultButton4', 'vbApplicationModal', 'vbSystemModal', 'vbOK', 'vbCancel', 'vbAbort', 'vbRetry', 'vbIgnore', 'vbYes', 'vbNo',
-                         'vbCr', 'VbCrLf', 'vbFormFeed', 'vbLf', 'vbNewLine', 'vbNullChar', 'vbNullString', 'vbTab', 'vbVerticalTab', 'vbUseDefault', 'vbTrue', 'vbFalse',
-                         'vbEmpty', 'vbNull', 'vbInteger', 'vbLong', 'vbSingle', 'vbDouble', 'vbCurrency', 'vbDate', 'vbString', 'vbObject', 'vbError', 'vbBoolean', 'vbVariant', 'vbDataObject', 'vbDecimal', 'vbByte', 'vbArray'];
-    //This list was from: http://msdn.microsoft.com/en-us/library/hkc375ea(v=vs.84).aspx
-    var builtinObjsWords = ['WScript', 'err', 'debug', 'RegExp'];
-    var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count'];
-    var knownMethods = ['clear', 'execute', 'raise', 'replace', 'test', 'write', 'writeline', 'close', 'open', 'state', 'eof', 'update', 'addnew', 'end', 'createobject', 'quit'];
-
-    var aspBuiltinObjsWords = ['server', 'response', 'request', 'session', 'application'];
-    var aspKnownProperties = ['buffer', 'cachecontrol', 'charset', 'contenttype', 'expires', 'expiresabsolute', 'isclientconnected', 'pics', 'status', //response
-                              'clientcertificate', 'cookies', 'form', 'querystring', 'servervariables', 'totalbytes', //request
-                              'contents', 'staticobjects', //application
-                              'codepage', 'lcid', 'sessionid', 'timeout', //session
-                              'scripttimeout']; //server
-    var aspKnownMethods = ['addheader', 'appendtolog', 'binarywrite', 'end', 'flush', 'redirect', //response
-                           'binaryread', //request
-                           'remove', 'removeall', 'lock', 'unlock', //application
-                           'abandon', //session
-                           'getlasterror', 'htmlencode', 'mappath', 'transfer', 'urlencode']; //server
-
-    var knownWords = knownMethods.concat(knownProperties);
-
-    builtinObjsWords = builtinObjsWords.concat(builtinConsts);
-
-    if (conf.isASP){
-        builtinObjsWords = builtinObjsWords.concat(aspBuiltinObjsWords);
-        knownWords = knownWords.concat(aspKnownMethods, aspKnownProperties);
-    };
-
-    var keywords = wordRegexp(commonkeywords);
-    var atoms = wordRegexp(atomWords);
-    var builtinFuncs = wordRegexp(builtinFuncsWords);
-    var builtinObjs = wordRegexp(builtinObjsWords);
-    var known = wordRegexp(knownWords);
-    var stringPrefixes = '"';
-
-    var opening = wordRegexp(openingKeywords);
-    var middle = wordRegexp(middleKeywords);
-    var closing = wordRegexp(endKeywords);
-    var doubleClosing = wordRegexp(['end']);
-    var doOpening = wordRegexp(['do']);
-    var noIndentWords = wordRegexp(['on error resume next', 'exit']);
-    var comment = wordRegexp(['rem']);
-
-
-    function indent(_stream, state) {
-      state.currentIndent++;
-    }
-
-    function dedent(_stream, state) {
-      state.currentIndent--;
-    }
-    // tokenizers
-    function tokenBase(stream, state) {
-        if (stream.eatSpace()) {
-            return 'space';
-            //return null;
-        }
-
-        var ch = stream.peek();
-
-        // Handle Comments
-        if (ch === "'") {
-            stream.skipToEnd();
-            return 'comment';
-        }
-        if (stream.match(comment)){
-            stream.skipToEnd();
-            return 'comment';
-        }
-
-
-        // Handle Number Literals
-        if (stream.match(/^((&H)|(&O))?[0-9\.]/i, false) && !stream.match(/^((&H)|(&O))?[0-9\.]+[a-z_]/i, false)) {
-            var floatLiteral = false;
-            // Floats
-            if (stream.match(/^\d*\.\d+/i)) { floatLiteral = true; }
-            else if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
-            else if (stream.match(/^\.\d+/)) { floatLiteral = true; }
-
-            if (floatLiteral) {
-                // Float literals may be "imaginary"
-                stream.eat(/J/i);
-                return 'number';
-            }
-            // Integers
-            var intLiteral = false;
-            // Hex
-            if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; }
-            // Octal
-            else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; }
-            // Decimal
-            else if (stream.match(/^[1-9]\d*F?/)) {
-                // Decimal literals may be "imaginary"
-                stream.eat(/J/i);
-                // TODO - Can you have imaginary longs?
-                intLiteral = true;
-            }
-            // Zero by itself with no other piece of number.
-            else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
-            if (intLiteral) {
-                // Integer literals may be "long"
-                stream.eat(/L/i);
-                return 'number';
-            }
-        }
-
-        // Handle Strings
-        if (stream.match(stringPrefixes)) {
-            state.tokenize = tokenStringFactory(stream.current());
-            return state.tokenize(stream, state);
-        }
-
-        // Handle operators and Delimiters
-        if (stream.match(doubleOperators)
-            || stream.match(singleOperators)
-            || stream.match(wordOperators)) {
-            return 'operator';
-        }
-        if (stream.match(singleDelimiters)) {
-            return null;
-        }
-
-        if (stream.match(brakets)) {
-            return "bracket";
-        }
-
-        if (stream.match(noIndentWords)) {
-            state.doInCurrentLine = true;
-
-            return 'keyword';
-        }
-
-        if (stream.match(doOpening)) {
-            indent(stream,state);
-            state.doInCurrentLine = true;
-
-            return 'keyword';
-        }
-        if (stream.match(opening)) {
-            if (! state.doInCurrentLine)
-              indent(stream,state);
-            else
-              state.doInCurrentLine = false;
-
-            return 'keyword';
-        }
-        if (stream.match(middle)) {
-            return 'keyword';
-        }
-
-
-        if (stream.match(doubleClosing)) {
-            dedent(stream,state);
-            dedent(stream,state);
-
-            return 'keyword';
-        }
-        if (stream.match(closing)) {
-            if (! state.doInCurrentLine)
-              dedent(stream,state);
-            else
-              state.doInCurrentLine = false;
-
-            return 'keyword';
-        }
-
-        if (stream.match(keywords)) {
-            return 'keyword';
-        }
-
-        if (stream.match(atoms)) {
-            return 'atom';
-        }
-
-        if (stream.match(known)) {
-            return 'variable-2';
-        }
-
-        if (stream.match(builtinFuncs)) {
-            return 'builtin';
-        }
-
-        if (stream.match(builtinObjs)){
-            return 'variable-2';
-        }
-
-        if (stream.match(identifiers)) {
-            return 'variable';
-        }
-
-        // Handle non-detected items
-        stream.next();
-        return ERRORCLASS;
-    }
-
-    function tokenStringFactory(delimiter) {
-        var singleline = delimiter.length == 1;
-        var OUTCLASS = 'string';
-
-        return function(stream, state) {
-            while (!stream.eol()) {
-                stream.eatWhile(/[^'"]/);
-                if (stream.match(delimiter)) {
-                    state.tokenize = tokenBase;
-                    return OUTCLASS;
-                } else {
-                    stream.eat(/['"]/);
-                }
-            }
-            if (singleline) {
-                if (parserConf.singleLineStringErrors) {
-                    return ERRORCLASS;
-                } else {
-                    state.tokenize = tokenBase;
-                }
-            }
-            return OUTCLASS;
-        };
-    }
-
-
-    function tokenLexer(stream, state) {
-        var style = state.tokenize(stream, state);
-        var current = stream.current();
-
-        // Handle '.' connected identifiers
-        if (current === '.') {
-            style = state.tokenize(stream, state);
-
-            current = stream.current();
-            if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) {
-                if (style === 'builtin' || style === 'keyword') style='variable';
-                if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2';
-
-                return style;
-            } else {
-                return ERRORCLASS;
-            }
-        }
-
-        return style;
-    }
-
-    var external = {
-        electricChars:"dDpPtTfFeE ",
-        startState: function() {
-            return {
-              tokenize: tokenBase,
-              lastToken: null,
-              currentIndent: 0,
-              nextLineIndent: 0,
-              doInCurrentLine: false,
-              ignoreKeyword: false
-
-
-          };
-        },
-
-        token: function(stream, state) {
-            if (stream.sol()) {
-              state.currentIndent += state.nextLineIndent;
-              state.nextLineIndent = 0;
-              state.doInCurrentLine = 0;
-            }
-            var style = tokenLexer(stream, state);
-
-            state.lastToken = {style:style, content: stream.current()};
-
-            if (style==='space') style=null;
-
-            return style;
-        },
-
-        indent: function(state, textAfter) {
-            var trueText = textAfter.replace(/^\s+|\s+$/g, '') ;
-            if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1);
-            if(state.currentIndent < 0) return 0;
-            return state.currentIndent * conf.indentUnit;
-        }
-
-    };
-    return external;
-});
-
-CodeMirror.defineMIME("text/vbscript", "vbscript");
-
-});
--- a/kallithea/public/codemirror/mode/velocity/velocity.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,201 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("velocity", function() {
-    function parseWords(str) {
-        var obj = {}, words = str.split(" ");
-        for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-        return obj;
-    }
-
-    var keywords = parseWords("#end #else #break #stop #[[ #]] " +
-                              "#{end} #{else} #{break} #{stop}");
-    var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " +
-                               "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}");
-    var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent.count $foreach.parent.hasNext $foreach.parent.first $foreach.parent.last $foreach.parent $velocityCount $!bodyContent $bodyContent");
-    var isOperatorChar = /[+\-*&%=<>!?:\/|]/;
-
-    function chain(stream, state, f) {
-        state.tokenize = f;
-        return f(stream, state);
-    }
-    function tokenBase(stream, state) {
-        var beforeParams = state.beforeParams;
-        state.beforeParams = false;
-        var ch = stream.next();
-        // start of unparsed string?
-        if ((ch == "'") && state.inParams) {
-            state.lastTokenWasBuiltin = false;
-            return chain(stream, state, tokenString(ch));
-        }
-        // start of parsed string?
-        else if ((ch == '"')) {
-            state.lastTokenWasBuiltin = false;
-            if (state.inString) {
-                state.inString = false;
-                return "string";
-            }
-            else if (state.inParams)
-                return chain(stream, state, tokenString(ch));
-        }
-        // is it one of the special signs []{}().,;? Seperator?
-        else if (/[\[\]{}\(\),;\.]/.test(ch)) {
-            if (ch == "(" && beforeParams)
-                state.inParams = true;
-            else if (ch == ")") {
-                state.inParams = false;
-                state.lastTokenWasBuiltin = true;
-            }
-            return null;
-        }
-        // start of a number value?
-        else if (/\d/.test(ch)) {
-            state.lastTokenWasBuiltin = false;
-            stream.eatWhile(/[\w\.]/);
-            return "number";
-        }
-        // multi line comment?
-        else if (ch == "#" && stream.eat("*")) {
-            state.lastTokenWasBuiltin = false;
-            return chain(stream, state, tokenComment);
-        }
-        // unparsed content?
-        else if (ch == "#" && stream.match(/ *\[ *\[/)) {
-            state.lastTokenWasBuiltin = false;
-            return chain(stream, state, tokenUnparsed);
-        }
-        // single line comment?
-        else if (ch == "#" && stream.eat("#")) {
-            state.lastTokenWasBuiltin = false;
-            stream.skipToEnd();
-            return "comment";
-        }
-        // variable?
-        else if (ch == "$") {
-            stream.eatWhile(/[\w\d\$_\.{}]/);
-            // is it one of the specials?
-            if (specials && specials.propertyIsEnumerable(stream.current())) {
-                return "keyword";
-            }
-            else {
-                state.lastTokenWasBuiltin = true;
-                state.beforeParams = true;
-                return "builtin";
-            }
-        }
-        // is it a operator?
-        else if (isOperatorChar.test(ch)) {
-            state.lastTokenWasBuiltin = false;
-            stream.eatWhile(isOperatorChar);
-            return "operator";
-        }
-        else {
-            // get the whole word
-            stream.eatWhile(/[\w\$_{}@]/);
-            var word = stream.current();
-            // is it one of the listed keywords?
-            if (keywords && keywords.propertyIsEnumerable(word))
-                return "keyword";
-            // is it one of the listed functions?
-            if (functions && functions.propertyIsEnumerable(word) ||
-                    (stream.current().match(/^#@?[a-z0-9_]+ *$/i) && stream.peek()=="(") &&
-                     !(functions && functions.propertyIsEnumerable(word.toLowerCase()))) {
-                state.beforeParams = true;
-                state.lastTokenWasBuiltin = false;
-                return "keyword";
-            }
-            if (state.inString) {
-                state.lastTokenWasBuiltin = false;
-                return "string";
-            }
-            if (stream.pos > word.length && stream.string.charAt(stream.pos-word.length-1)=="." && state.lastTokenWasBuiltin)
-                return "builtin";
-            // default: just a "word"
-            state.lastTokenWasBuiltin = false;
-            return null;
-        }
-    }
-
-    function tokenString(quote) {
-        return function(stream, state) {
-            var escaped = false, next, end = false;
-            while ((next = stream.next()) != null) {
-                if ((next == quote) && !escaped) {
-                    end = true;
-                    break;
-                }
-                if (quote=='"' && stream.peek() == '$' && !escaped) {
-                    state.inString = true;
-                    end = true;
-                    break;
-                }
-                escaped = !escaped && next == "\\";
-            }
-            if (end) state.tokenize = tokenBase;
-            return "string";
-        };
-    }
-
-    function tokenComment(stream, state) {
-        var maybeEnd = false, ch;
-        while (ch = stream.next()) {
-            if (ch == "#" && maybeEnd) {
-                state.tokenize = tokenBase;
-                break;
-            }
-            maybeEnd = (ch == "*");
-        }
-        return "comment";
-    }
-
-    function tokenUnparsed(stream, state) {
-        var maybeEnd = 0, ch;
-        while (ch = stream.next()) {
-            if (ch == "#" && maybeEnd == 2) {
-                state.tokenize = tokenBase;
-                break;
-            }
-            if (ch == "]")
-                maybeEnd++;
-            else if (ch != " ")
-                maybeEnd = 0;
-        }
-        return "meta";
-    }
-    // Interface
-
-    return {
-        startState: function() {
-            return {
-                tokenize: tokenBase,
-                beforeParams: false,
-                inParams: false,
-                inString: false,
-                lastTokenWasBuiltin: false
-            };
-        },
-
-        token: function(stream, state) {
-            if (stream.eatSpace()) return null;
-            return state.tokenize(stream, state);
-        },
-        blockCommentStart: "#*",
-        blockCommentEnd: "*#",
-        lineComment: "##",
-        fold: "velocity"
-    };
-});
-
-CodeMirror.defineMIME("text/velocity", "velocity");
-
-});
--- a/kallithea/public/codemirror/mode/verilog/verilog.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,364 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("verilog", function(config, parserConfig) {
-
-  var indentUnit = config.indentUnit,
-      statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
-      dontAlignCalls = parserConfig.dontAlignCalls,
-      noIndentKeywords = parserConfig.noIndentKeywords || [],
-      multiLineStrings = parserConfig.multiLineStrings;
-
-  function words(str) {
-    var obj = {}, words = str.split(" ");
-    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
-    return obj;
-  }
-
-  /**
-   * Keywords from IEEE 1800-2012
-   */
-  var keywords = words(
-    "accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind " +
-    "bins binsof bit break buf bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config " +
-    "const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable " +
-    "dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup " +
-    "endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask " +
-    "enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin " +
-    "function generate genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import " +
-    "incdir include initial inout input inside instance int integer interconnect interface intersect join join_any " +
-    "join_none large let liblist library local localparam logic longint macromodule matches medium modport module " +
-    "nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 null or output package packed " +
-    "parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup " +
-    "pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg " +
-    "reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime " +
-    "s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify " +
-    "specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on " +
-    "table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior " +
-    "trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void " +
-    "wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor");
-
-  /** Operators from IEEE 1800-2012
-     unary_operator ::=
-       + | - | ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~
-     binary_operator ::=
-       + | - | * | / | % | == | != | === | !== | ==? | !=? | && | || | **
-       | < | <= | > | >= | & | | | ^ | ^~ | ~^ | >> | << | >>> | <<<
-       | -> | <->
-     inc_or_dec_operator ::= ++ | --
-     unary_module_path_operator ::=
-       ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~
-     binary_module_path_operator ::=
-       == | != | && | || | & | | | ^ | ^~ | ~^
-  */
-  var isOperatorChar = /[\+\-\*\/!~&|^%=?:]/;
-  var isBracketChar = /[\[\]{}()]/;
-
-  var unsignedNumber = /\d[0-9_]*/;
-  var decimalLiteral = /\d*\s*'s?d\s*\d[0-9_]*/i;
-  var binaryLiteral = /\d*\s*'s?b\s*[xz01][xz01_]*/i;
-  var octLiteral = /\d*\s*'s?o\s*[xz0-7][xz0-7_]*/i;
-  var hexLiteral = /\d*\s*'s?h\s*[0-9a-fxz?][0-9a-fxz?_]*/i;
-  var realLiteral = /(\d[\d_]*(\.\d[\d_]*)?E-?[\d_]+)|(\d[\d_]*\.\d[\d_]*)/i;
-
-  var closingBracketOrWord = /^((\w+)|[)}\]])/;
-  var closingBracket = /[)}\]]/;
-
-  var curPunc;
-  var curKeyword;
-
-  // Block openings which are closed by a matching keyword in the form of ("end" + keyword)
-  // E.g. "task" => "endtask"
-  var blockKeywords = words(
-    "case checker class clocking config function generate interface module package" +
-    "primitive program property specify sequence table task"
-  );
-
-  // Opening/closing pairs
-  var openClose = {};
-  for (var keyword in blockKeywords) {
-    openClose[keyword] = "end" + keyword;
-  }
-  openClose["begin"] = "end";
-  openClose["casex"] = "endcase";
-  openClose["casez"] = "endcase";
-  openClose["do"   ] = "while";
-  openClose["fork" ] = "join;join_any;join_none";
-  openClose["covergroup"] = "endgroup";
-
-  for (var i in noIndentKeywords) {
-    var keyword = noIndentKeywords[i];
-    if (openClose[keyword]) {
-      openClose[keyword] = undefined;
-    }
-  }
-
-  // Keywords which open statements that are ended with a semi-colon
-  var statementKeywords = words("always always_comb always_ff always_latch assert assign assume else export for foreach forever if import initial repeat while");
-
-  function tokenBase(stream, state) {
-    var ch = stream.peek();
-    if (/[,;:\.]/.test(ch)) {
-      curPunc = stream.next();
-      return null;
-    }
-    if (isBracketChar.test(ch)) {
-      curPunc = stream.next();
-      return "bracket";
-    }
-    // Macros (tick-defines)
-    if (ch == '`') {
-      stream.next();
-      if (stream.eatWhile(/[\w\$_]/)) {
-        return "def";
-      } else {
-        return null;
-      }
-    }
-    // System calls
-    if (ch == '$') {
-      stream.next();
-      if (stream.eatWhile(/[\w\$_]/)) {
-        return "meta";
-      } else {
-        return null;
-      }
-    }
-    // Time literals
-    if (ch == '#') {
-      stream.next();
-      stream.eatWhile(/[\d_.]/);
-      return "def";
-    }
-    // Strings
-    if (ch == '"') {
-      stream.next();
-      state.tokenize = tokenString(ch);
-      return state.tokenize(stream, state);
-    }
-    // Comments
-    if (ch == "/") {
-      stream.next();
-      if (stream.eat("*")) {
-        state.tokenize = tokenComment;
-        return tokenComment(stream, state);
-      }
-      if (stream.eat("/")) {
-        stream.skipToEnd();
-        return "comment";
-      }
-      stream.backUp(1);
-    }
-
-    // Numeric literals
-    if (stream.match(realLiteral) ||
-        stream.match(decimalLiteral) ||
-        stream.match(binaryLiteral) ||
-        stream.match(octLiteral) ||
-        stream.match(hexLiteral) ||
-        stream.match(unsignedNumber) ||
-        stream.match(realLiteral)) {
-      return "number";
-    }
-
-    // Operators
-    if (stream.eatWhile(isOperatorChar)) {
-      return "meta";
-    }
-
-    // Keywords / plain variables
-    if (stream.eatWhile(/[\w\$_]/)) {
-      var cur = stream.current();
-      if (keywords[cur]) {
-        if (openClose[cur]) {
-          curPunc = "newblock";
-        }
-        if (statementKeywords[cur]) {
-          curPunc = "newstatement";
-        }
-        curKeyword = cur;
-        return "keyword";
-      }
-      return "variable";
-    }
-
-    stream.next();
-    return null;
-  }
-
-  function tokenString(quote) {
-    return function(stream, state) {
-      var escaped = false, next, end = false;
-      while ((next = stream.next()) != null) {
-        if (next == quote && !escaped) {end = true; break;}
-        escaped = !escaped && next == "\\";
-      }
-      if (end || !(escaped || multiLineStrings))
-        state.tokenize = tokenBase;
-      return "string";
-    };
-  }
-
-  function tokenComment(stream, state) {
-    var maybeEnd = false, ch;
-    while (ch = stream.next()) {
-      if (ch == "/" && maybeEnd) {
-        state.tokenize = tokenBase;
-        break;
-      }
-      maybeEnd = (ch == "*");
-    }
-    return "comment";
-  }
-
-  function Context(indented, column, type, align, prev) {
-    this.indented = indented;
-    this.column = column;
-    this.type = type;
-    this.align = align;
-    this.prev = prev;
-  }
-  function pushContext(state, col, type) {
-    var indent = state.indented;
-    var c = new Context(indent, col, type, null, state.context);
-    return state.context = c;
-  }
-  function popContext(state) {
-    var t = state.context.type;
-    if (t == ")" || t == "]" || t == "}") {
-      state.indented = state.context.indented;
-    }
-    return state.context = state.context.prev;
-  }
-
-  function isClosing(text, contextClosing) {
-    if (text == contextClosing) {
-      return true;
-    } else {
-      // contextClosing may be mulitple keywords separated by ;
-      var closingKeywords = contextClosing.split(";");
-      for (var i in closingKeywords) {
-        if (text == closingKeywords[i]) {
-          return true;
-        }
-      }
-      return false;
-    }
-  }
-
-  function buildElectricInputRegEx() {
-    // Reindentation should occur on any bracket char: {}()[]
-    // or on a match of any of the block closing keywords, at
-    // the end of a line
-    var allClosings = [];
-    for (var i in openClose) {
-      if (openClose[i]) {
-        var closings = openClose[i].split(";");
-        for (var j in closings) {
-          allClosings.push(closings[j]);
-        }
-      }
-    }
-    var re = new RegExp("[{}()\\[\\]]|(" + allClosings.join("|") + ")$");
-    return re;
-  }
-
-  // Interface
-  return {
-
-    // Regex to force current line to reindent
-    electricInput: buildElectricInputRegEx(),
-
-    startState: function(basecolumn) {
-      return {
-        tokenize: null,
-        context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
-        indented: 0,
-        startOfLine: true
-      };
-    },
-
-    token: function(stream, state) {
-      var ctx = state.context;
-      if (stream.sol()) {
-        if (ctx.align == null) ctx.align = false;
-        state.indented = stream.indentation();
-        state.startOfLine = true;
-      }
-      if (stream.eatSpace()) return null;
-      curPunc = null;
-      curKeyword = null;
-      var style = (state.tokenize || tokenBase)(stream, state);
-      if (style == "comment" || style == "meta" || style == "variable") return style;
-      if (ctx.align == null) ctx.align = true;
-
-      if (curPunc == ctx.type) {
-        popContext(state);
-      }
-      else if ((curPunc == ";" && ctx.type == "statement") ||
-               (ctx.type && isClosing(curKeyword, ctx.type))) {
-        ctx = popContext(state);
-        while (ctx && ctx.type == "statement") ctx = popContext(state);
-      }
-      else if (curPunc == "{") { pushContext(state, stream.column(), "}"); }
-      else if (curPunc == "[") { pushContext(state, stream.column(), "]"); }
-      else if (curPunc == "(") { pushContext(state, stream.column(), ")"); }
-      else if (ctx && ctx.type == "endcase" && curPunc == ":") { pushContext(state, stream.column(), "statement"); }
-      else if (curPunc == "newstatement") {
-        pushContext(state, stream.column(), "statement");
-      } else if (curPunc == "newblock") {
-        if (curKeyword == "function" && ctx && (ctx.type == "statement" || ctx.type == "endgroup")) {
-          // The 'function' keyword can appear in some other contexts where it actually does not
-          // indicate a function (import/export DPI and covergroup definitions).
-          // Do nothing in this case
-        } else if (curKeyword == "task" && ctx && ctx.type == "statement") {
-          // Same thing for task
-        } else {
-          var close = openClose[curKeyword];
-          pushContext(state, stream.column(), close);
-        }
-      }
-
-      state.startOfLine = false;
-      return style;
-    },
-
-    indent: function(state, textAfter) {
-      if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
-      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
-      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
-      var closing = false;
-      var possibleClosing = textAfter.match(closingBracketOrWord);
-      if (possibleClosing) {
-        closing = isClosing(possibleClosing[0], ctx.type);
-      }
-      if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
-      else if (closingBracket.test(ctx.type) && ctx.align && !dontAlignCalls) return ctx.column + (closing ? 0 : 1);
-      else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
-      else return ctx.indented + (closing ? 0 : indentUnit);
-    },
-
-    blockCommentStart: "/*",
-    blockCommentEnd: "*/",
-    lineComment: "//"
-  };
-});
-
-CodeMirror.defineMIME("text/x-verilog", {
-  name: "verilog"
-});
-CodeMirror.defineMIME("text/x-systemverilog", {
-  name: "systemverilog"
-});
-
-});
--- a/kallithea/public/codemirror/mode/xml/xml.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,384 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("xml", function(config, parserConfig) {
-  var indentUnit = config.indentUnit;
-  var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
-  var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag;
-  if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true;
-
-  var Kludges = parserConfig.htmlMode ? {
-    autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
-                      'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
-                      'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
-                      'track': true, 'wbr': true, 'menuitem': true},
-    implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
-                       'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
-                       'th': true, 'tr': true},
-    contextGrabbers: {
-      'dd': {'dd': true, 'dt': true},
-      'dt': {'dd': true, 'dt': true},
-      'li': {'li': true},
-      'option': {'option': true, 'optgroup': true},
-      'optgroup': {'optgroup': true},
-      'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
-            'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
-            'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
-            'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
-            'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
-      'rp': {'rp': true, 'rt': true},
-      'rt': {'rp': true, 'rt': true},
-      'tbody': {'tbody': true, 'tfoot': true},
-      'td': {'td': true, 'th': true},
-      'tfoot': {'tbody': true},
-      'th': {'td': true, 'th': true},
-      'thead': {'tbody': true, 'tfoot': true},
-      'tr': {'tr': true}
-    },
-    doNotIndent: {"pre": true},
-    allowUnquoted: true,
-    allowMissing: true,
-    caseFold: true
-  } : {
-    autoSelfClosers: {},
-    implicitlyClosed: {},
-    contextGrabbers: {},
-    doNotIndent: {},
-    allowUnquoted: false,
-    allowMissing: false,
-    caseFold: false
-  };
-  var alignCDATA = parserConfig.alignCDATA;
-
-  // Return variables for tokenizers
-  var type, setStyle;
-
-  function inText(stream, state) {
-    function chain(parser) {
-      state.tokenize = parser;
-      return parser(stream, state);
-    }
-
-    var ch = stream.next();
-    if (ch == "<") {
-      if (stream.eat("!")) {
-        if (stream.eat("[")) {
-          if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
-          else return null;
-        } else if (stream.match("--")) {
-          return chain(inBlock("comment", "-->"));
-        } else if (stream.match("DOCTYPE", true, true)) {
-          stream.eatWhile(/[\w\._\-]/);
-          return chain(doctype(1));
-        } else {
-          return null;
-        }
-      } else if (stream.eat("?")) {
-        stream.eatWhile(/[\w\._\-]/);
-        state.tokenize = inBlock("meta", "?>");
-        return "meta";
-      } else {
-        type = stream.eat("/") ? "closeTag" : "openTag";
-        state.tokenize = inTag;
-        return "tag bracket";
-      }
-    } else if (ch == "&") {
-      var ok;
-      if (stream.eat("#")) {
-        if (stream.eat("x")) {
-          ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
-        } else {
-          ok = stream.eatWhile(/[\d]/) && stream.eat(";");
-        }
-      } else {
-        ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
-      }
-      return ok ? "atom" : "error";
-    } else {
-      stream.eatWhile(/[^&<]/);
-      return null;
-    }
-  }
-
-  function inTag(stream, state) {
-    var ch = stream.next();
-    if (ch == ">" || (ch == "/" && stream.eat(">"))) {
-      state.tokenize = inText;
-      type = ch == ">" ? "endTag" : "selfcloseTag";
-      return "tag bracket";
-    } else if (ch == "=") {
-      type = "equals";
-      return null;
-    } else if (ch == "<") {
-      state.tokenize = inText;
-      state.state = baseState;
-      state.tagName = state.tagStart = null;
-      var next = state.tokenize(stream, state);
-      return next ? next + " tag error" : "tag error";
-    } else if (/[\'\"]/.test(ch)) {
-      state.tokenize = inAttribute(ch);
-      state.stringStartCol = stream.column();
-      return state.tokenize(stream, state);
-    } else {
-      stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
-      return "word";
-    }
-  }
-
-  function inAttribute(quote) {
-    var closure = function(stream, state) {
-      while (!stream.eol()) {
-        if (stream.next() == quote) {
-          state.tokenize = inTag;
-          break;
-        }
-      }
-      return "string";
-    };
-    closure.isInAttribute = true;
-    return closure;
-  }
-
-  function inBlock(style, terminator) {
-    return function(stream, state) {
-      while (!stream.eol()) {
-        if (stream.match(terminator)) {
-          state.tokenize = inText;
-          break;
-        }
-        stream.next();
-      }
-      return style;
-    };
-  }
-  function doctype(depth) {
-    return function(stream, state) {
-      var ch;
-      while ((ch = stream.next()) != null) {
-        if (ch == "<") {
-          state.tokenize = doctype(depth + 1);
-          return state.tokenize(stream, state);
-        } else if (ch == ">") {
-          if (depth == 1) {
-            state.tokenize = inText;
-            break;
-          } else {
-            state.tokenize = doctype(depth - 1);
-            return state.tokenize(stream, state);
-          }
-        }
-      }
-      return "meta";
-    };
-  }
-
-  function Context(state, tagName, startOfLine) {
-    this.prev = state.context;
-    this.tagName = tagName;
-    this.indent = state.indented;
-    this.startOfLine = startOfLine;
-    if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
-      this.noIndent = true;
-  }
-  function popContext(state) {
-    if (state.context) state.context = state.context.prev;
-  }
-  function maybePopContext(state, nextTagName) {
-    var parentTagName;
-    while (true) {
-      if (!state.context) {
-        return;
-      }
-      parentTagName = state.context.tagName;
-      if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
-          !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
-        return;
-      }
-      popContext(state);
-    }
-  }
-
-  function baseState(type, stream, state) {
-    if (type == "openTag") {
-      state.tagStart = stream.column();
-      return tagNameState;
-    } else if (type == "closeTag") {
-      return closeTagNameState;
-    } else {
-      return baseState;
-    }
-  }
-  function tagNameState(type, stream, state) {
-    if (type == "word") {
-      state.tagName = stream.current();
-      setStyle = "tag";
-      return attrState;
-    } else {
-      setStyle = "error";
-      return tagNameState;
-    }
-  }
-  function closeTagNameState(type, stream, state) {
-    if (type == "word") {
-      var tagName = stream.current();
-      if (state.context && state.context.tagName != tagName &&
-          Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName))
-        popContext(state);
-      if (state.context && state.context.tagName == tagName) {
-        setStyle = "tag";
-        return closeState;
-      } else {
-        setStyle = "tag error";
-        return closeStateErr;
-      }
-    } else {
-      setStyle = "error";
-      return closeStateErr;
-    }
-  }
-
-  function closeState(type, _stream, state) {
-    if (type != "endTag") {
-      setStyle = "error";
-      return closeState;
-    }
-    popContext(state);
-    return baseState;
-  }
-  function closeStateErr(type, stream, state) {
-    setStyle = "error";
-    return closeState(type, stream, state);
-  }
-
-  function attrState(type, _stream, state) {
-    if (type == "word") {
-      setStyle = "attribute";
-      return attrEqState;
-    } else if (type == "endTag" || type == "selfcloseTag") {
-      var tagName = state.tagName, tagStart = state.tagStart;
-      state.tagName = state.tagStart = null;
-      if (type == "selfcloseTag" ||
-          Kludges.autoSelfClosers.hasOwnProperty(tagName)) {
-        maybePopContext(state, tagName);
-      } else {
-        maybePopContext(state, tagName);
-        state.context = new Context(state, tagName, tagStart == state.indented);
-      }
-      return baseState;
-    }
-    setStyle = "error";
-    return attrState;
-  }
-  function attrEqState(type, stream, state) {
-    if (type == "equals") return attrValueState;
-    if (!Kludges.allowMissing) setStyle = "error";
-    return attrState(type, stream, state);
-  }
-  function attrValueState(type, stream, state) {
-    if (type == "string") return attrContinuedState;
-    if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
-    setStyle = "error";
-    return attrState(type, stream, state);
-  }
-  function attrContinuedState(type, stream, state) {
-    if (type == "string") return attrContinuedState;
-    return attrState(type, stream, state);
-  }
-
-  return {
-    startState: function() {
-      return {tokenize: inText,
-              state: baseState,
-              indented: 0,
-              tagName: null, tagStart: null,
-              context: null};
-    },
-
-    token: function(stream, state) {
-      if (!state.tagName && stream.sol())
-        state.indented = stream.indentation();
-
-      if (stream.eatSpace()) return null;
-      type = null;
-      var style = state.tokenize(stream, state);
-      if ((style || type) && style != "comment") {
-        setStyle = null;
-        state.state = state.state(type || style, stream, state);
-        if (setStyle)
-          style = setStyle == "error" ? style + " error" : setStyle;
-      }
-      return style;
-    },
-
-    indent: function(state, textAfter, fullLine) {
-      var context = state.context;
-      // Indent multi-line strings (e.g. css).
-      if (state.tokenize.isInAttribute) {
-        if (state.tagStart == state.indented)
-          return state.stringStartCol + 1;
-        else
-          return state.indented + indentUnit;
-      }
-      if (context && context.noIndent) return CodeMirror.Pass;
-      if (state.tokenize != inTag && state.tokenize != inText)
-        return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
-      // Indent the starts of attribute names.
-      if (state.tagName) {
-        if (multilineTagIndentPastTag)
-          return state.tagStart + state.tagName.length + 2;
-        else
-          return state.tagStart + indentUnit * multilineTagIndentFactor;
-      }
-      if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
-      var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
-      if (tagAfter && tagAfter[1]) { // Closing tag spotted
-        while (context) {
-          if (context.tagName == tagAfter[2]) {
-            context = context.prev;
-            break;
-          } else if (Kludges.implicitlyClosed.hasOwnProperty(context.tagName)) {
-            context = context.prev;
-          } else {
-            break;
-          }
-        }
-      } else if (tagAfter) { // Opening tag spotted
-        while (context) {
-          var grabbers = Kludges.contextGrabbers[context.tagName];
-          if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
-            context = context.prev;
-          else
-            break;
-        }
-      }
-      while (context && !context.startOfLine)
-        context = context.prev;
-      if (context) return context.indent + indentUnit;
-      else return 0;
-    },
-
-    electricInput: /<\/[\s\w:]+>$/,
-    blockCommentStart: "<!--",
-    blockCommentEnd: "-->",
-
-    configuration: parserConfig.htmlMode ? "html" : "xml",
-    helperType: parserConfig.htmlMode ? "html" : "xml"
-  };
-});
-
-CodeMirror.defineMIME("text/xml", "xml");
-CodeMirror.defineMIME("application/xml", "xml");
-if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
-  CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
-
-});
--- a/kallithea/public/codemirror/mode/xquery/xquery.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,447 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("xquery", function() {
-
-  // The keywords object is set to the result of this self executing
-  // function. Each keyword is a property of the keywords object whose
-  // value is {type: atype, style: astyle}
-  var keywords = function(){
-    // conveinence functions used to build keywords object
-    function kw(type) {return {type: type, style: "keyword"};}
-    var A = kw("keyword a")
-      , B = kw("keyword b")
-      , C = kw("keyword c")
-      , operator = kw("operator")
-      , atom = {type: "atom", style: "atom"}
-      , punctuation = {type: "punctuation", style: null}
-      , qualifier = {type: "axis_specifier", style: "qualifier"};
-
-    // kwObj is what is return from this function at the end
-    var kwObj = {
-      'if': A, 'switch': A, 'while': A, 'for': A,
-      'else': B, 'then': B, 'try': B, 'finally': B, 'catch': B,
-      'element': C, 'attribute': C, 'let': C, 'implements': C, 'import': C, 'module': C, 'namespace': C,
-      'return': C, 'super': C, 'this': C, 'throws': C, 'where': C, 'private': C,
-      ',': punctuation,
-      'null': atom, 'fn:false()': atom, 'fn:true()': atom
-    };
-
-    // a list of 'basic' keywords. For each add a property to kwObj with the value of
-    // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"}
-    var basic = ['after','ancestor','ancestor-or-self','and','as','ascending','assert','attribute','before',
-    'by','case','cast','child','comment','declare','default','define','descendant','descendant-or-self',
-    'descending','document','document-node','element','else','eq','every','except','external','following',
-    'following-sibling','follows','for','function','if','import','in','instance','intersect','item',
-    'let','module','namespace','node','node','of','only','or','order','parent','precedes','preceding',
-    'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element',
-    'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where',
-    'xquery', 'empty-sequence'];
-    for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);};
-
-    // a list of types. For each add a property to kwObj with the value of
-    // {type: "atom", style: "atom"}
-    var types = ['xs:string', 'xs:float', 'xs:decimal', 'xs:double', 'xs:integer', 'xs:boolean', 'xs:date', 'xs:dateTime',
-    'xs:time', 'xs:duration', 'xs:dayTimeDuration', 'xs:time', 'xs:yearMonthDuration', 'numeric', 'xs:hexBinary',
-    'xs:base64Binary', 'xs:anyURI', 'xs:QName', 'xs:byte','xs:boolean','xs:anyURI','xf:yearMonthDuration'];
-    for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;};
-
-    // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"}
-    var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-'];
-    for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;};
-
-    // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"}
-    var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::",
-    "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"];
-    for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; };
-
-    return kwObj;
-  }();
-
-  // Used as scratch variables to communicate multiple values without
-  // consing up tons of objects.
-  var type, content;
-
-  function ret(tp, style, cont) {
-    type = tp; content = cont;
-    return style;
-  }
-
-  function chain(stream, state, f) {
-    state.tokenize = f;
-    return f(stream, state);
-  }
-
-  // the primary mode tokenizer
-  function tokenBase(stream, state) {
-    var ch = stream.next(),
-        mightBeFunction = false,
-        isEQName = isEQNameAhead(stream);
-
-    // an XML tag (if not in some sub, chained tokenizer)
-    if (ch == "<") {
-      if(stream.match("!--", true))
-        return chain(stream, state, tokenXMLComment);
-
-      if(stream.match("![CDATA", false)) {
-        state.tokenize = tokenCDATA;
-        return ret("tag", "tag");
-      }
-
-      if(stream.match("?", false)) {
-        return chain(stream, state, tokenPreProcessing);
-      }
-
-      var isclose = stream.eat("/");
-      stream.eatSpace();
-      var tagName = "", c;
-      while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
-
-      return chain(stream, state, tokenTag(tagName, isclose));
-    }
-    // start code block
-    else if(ch == "{") {
-      pushStateStack(state,{ type: "codeblock"});
-      return ret("", null);
-    }
-    // end code block
-    else if(ch == "}") {
-      popStateStack(state);
-      return ret("", null);
-    }
-    // if we're in an XML block
-    else if(isInXmlBlock(state)) {
-      if(ch == ">")
-        return ret("tag", "tag");
-      else if(ch == "/" && stream.eat(">")) {
-        popStateStack(state);
-        return ret("tag", "tag");
-      }
-      else
-        return ret("word", "variable");
-    }
-    // if a number
-    else if (/\d/.test(ch)) {
-      stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/);
-      return ret("number", "atom");
-    }
-    // comment start
-    else if (ch === "(" && stream.eat(":")) {
-      pushStateStack(state, { type: "comment"});
-      return chain(stream, state, tokenComment);
-    }
-    // quoted string
-    else if (  !isEQName && (ch === '"' || ch === "'"))
-      return chain(stream, state, tokenString(ch));
-    // variable
-    else if(ch === "$") {
-      return chain(stream, state, tokenVariable);
-    }
-    // assignment
-    else if(ch ===":" && stream.eat("=")) {
-      return ret("operator", "keyword");
-    }
-    // open paren
-    else if(ch === "(") {
-      pushStateStack(state, { type: "paren"});
-      return ret("", null);
-    }
-    // close paren
-    else if(ch === ")") {
-      popStateStack(state);
-      return ret("", null);
-    }
-    // open paren
-    else if(ch === "[") {
-      pushStateStack(state, { type: "bracket"});
-      return ret("", null);
-    }
-    // close paren
-    else if(ch === "]") {
-      popStateStack(state);
-      return ret("", null);
-    }
-    else {
-      var known = keywords.propertyIsEnumerable(ch) && keywords[ch];
-
-      // if there's a EQName ahead, consume the rest of the string portion, it's likely a function
-      if(isEQName && ch === '\"') while(stream.next() !== '"'){}
-      if(isEQName && ch === '\'') while(stream.next() !== '\''){}
-
-      // gobble up a word if the character is not known
-      if(!known) stream.eatWhile(/[\w\$_-]/);
-
-      // gobble a colon in the case that is a lib func type call fn:doc
-      var foundColon = stream.eat(":");
-
-      // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier
-      // which should get matched as a keyword
-      if(!stream.eat(":") && foundColon) {
-        stream.eatWhile(/[\w\$_-]/);
-      }
-      // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort)
-      if(stream.match(/^[ \t]*\(/, false)) {
-        mightBeFunction = true;
-      }
-      // is the word a keyword?
-      var word = stream.current();
-      known = keywords.propertyIsEnumerable(word) && keywords[word];
-
-      // if we think it's a function call but not yet known,
-      // set style to variable for now for lack of something better
-      if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"};
-
-      // if the previous word was element, attribute, axis specifier, this word should be the name of that
-      if(isInXmlConstructor(state)) {
-        popStateStack(state);
-        return ret("word", "variable", word);
-      }
-      // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and
-      // push the stack so we know to look for it on the next word
-      if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"});
-
-      // if the word is known, return the details of that else just call this a generic 'word'
-      return known ? ret(known.type, known.style, word) :
-                     ret("word", "variable", word);
-    }
-  }
-
-  // handle comments, including nested
-  function tokenComment(stream, state) {
-    var maybeEnd = false, maybeNested = false, nestedCount = 0, ch;
-    while (ch = stream.next()) {
-      if (ch == ")" && maybeEnd) {
-        if(nestedCount > 0)
-          nestedCount--;
-        else {
-          popStateStack(state);
-          break;
-        }
-      }
-      else if(ch == ":" && maybeNested) {
-        nestedCount++;
-      }
-      maybeEnd = (ch == ":");
-      maybeNested = (ch == "(");
-    }
-
-    return ret("comment", "comment");
-  }
-
-  // tokenizer for string literals
-  // optionally pass a tokenizer function to set state.tokenize back to when finished
-  function tokenString(quote, f) {
-    return function(stream, state) {
-      var ch;
-
-      if(isInString(state) && stream.current() == quote) {
-        popStateStack(state);
-        if(f) state.tokenize = f;
-        return ret("string", "string");
-      }
-
-      pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) });
-
-      // if we're in a string and in an XML block, allow an embedded code block
-      if(stream.match("{", false) && isInXmlAttributeBlock(state)) {
-        state.tokenize = tokenBase;
-        return ret("string", "string");
-      }
-
-
-      while (ch = stream.next()) {
-        if (ch ==  quote) {
-          popStateStack(state);
-          if(f) state.tokenize = f;
-          break;
-        }
-        else {
-          // if we're in a string and in an XML block, allow an embedded code block in an attribute
-          if(stream.match("{", false) && isInXmlAttributeBlock(state)) {
-            state.tokenize = tokenBase;
-            return ret("string", "string");
-          }
-
-        }
-      }
-
-      return ret("string", "string");
-    };
-  }
-
-  // tokenizer for variables
-  function tokenVariable(stream, state) {
-    var isVariableChar = /[\w\$_-]/;
-
-    // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote
-    if(stream.eat("\"")) {
-      while(stream.next() !== '\"'){};
-      stream.eat(":");
-    } else {
-      stream.eatWhile(isVariableChar);
-      if(!stream.match(":=", false)) stream.eat(":");
-    }
-    stream.eatWhile(isVariableChar);
-    state.tokenize = tokenBase;
-    return ret("variable", "variable");
-  }
-
-  // tokenizer for XML tags
-  function tokenTag(name, isclose) {
-    return function(stream, state) {
-      stream.eatSpace();
-      if(isclose && stream.eat(">")) {
-        popStateStack(state);
-        state.tokenize = tokenBase;
-        return ret("tag", "tag");
-      }
-      // self closing tag without attributes?
-      if(!stream.eat("/"))
-        pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase});
-      if(!stream.eat(">")) {
-        state.tokenize = tokenAttribute;
-        return ret("tag", "tag");
-      }
-      else {
-        state.tokenize = tokenBase;
-      }
-      return ret("tag", "tag");
-    };
-  }
-
-  // tokenizer for XML attributes
-  function tokenAttribute(stream, state) {
-    var ch = stream.next();
-
-    if(ch == "/" && stream.eat(">")) {
-      if(isInXmlAttributeBlock(state)) popStateStack(state);
-      if(isInXmlBlock(state)) popStateStack(state);
-      return ret("tag", "tag");
-    }
-    if(ch == ">") {
-      if(isInXmlAttributeBlock(state)) popStateStack(state);
-      return ret("tag", "tag");
-    }
-    if(ch == "=")
-      return ret("", null);
-    // quoted string
-    if (ch == '"' || ch == "'")
-      return chain(stream, state, tokenString(ch, tokenAttribute));
-
-    if(!isInXmlAttributeBlock(state))
-      pushStateStack(state, { type: "attribute", tokenize: tokenAttribute});
-
-    stream.eat(/[a-zA-Z_:]/);
-    stream.eatWhile(/[-a-zA-Z0-9_:.]/);
-    stream.eatSpace();
-
-    // the case where the attribute has not value and the tag was closed
-    if(stream.match(">", false) || stream.match("/", false)) {
-      popStateStack(state);
-      state.tokenize = tokenBase;
-    }
-
-    return ret("attribute", "attribute");
-  }
-
-  // handle comments, including nested
-  function tokenXMLComment(stream, state) {
-    var ch;
-    while (ch = stream.next()) {
-      if (ch == "-" && stream.match("->", true)) {
-        state.tokenize = tokenBase;
-        return ret("comment", "comment");
-      }
-    }
-  }
-
-
-  // handle CDATA
-  function tokenCDATA(stream, state) {
-    var ch;
-    while (ch = stream.next()) {
-      if (ch == "]" && stream.match("]", true)) {
-        state.tokenize = tokenBase;
-        return ret("comment", "comment");
-      }
-    }
-  }
-
-  // handle preprocessing instructions
-  function tokenPreProcessing(stream, state) {
-    var ch;
-    while (ch = stream.next()) {
-      if (ch == "?" && stream.match(">", true)) {
-        state.tokenize = tokenBase;
-        return ret("comment", "comment meta");
-      }
-    }
-  }
-
-
-  // functions to test the current context of the state
-  function isInXmlBlock(state) { return isIn(state, "tag"); }
-  function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); }
-  function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); }
-  function isInString(state) { return isIn(state, "string"); }
-
-  function isEQNameAhead(stream) {
-    // assume we've already eaten a quote (")
-    if(stream.current() === '"')
-      return stream.match(/^[^\"]+\"\:/, false);
-    else if(stream.current() === '\'')
-      return stream.match(/^[^\"]+\'\:/, false);
-    else
-      return false;
-  }
-
-  function isIn(state, type) {
-    return (state.stack.length && state.stack[state.stack.length - 1].type == type);
-  }
-
-  function pushStateStack(state, newState) {
-    state.stack.push(newState);
-  }
-
-  function popStateStack(state) {
-    state.stack.pop();
-    var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize;
-    state.tokenize = reinstateTokenize || tokenBase;
-  }
-
-  // the interface for the mode API
-  return {
-    startState: function() {
-      return {
-        tokenize: tokenBase,
-        cc: [],
-        stack: []
-      };
-    },
-
-    token: function(stream, state) {
-      if (stream.eatSpace()) return null;
-      var style = state.tokenize(stream, state);
-      return style;
-    },
-
-    blockCommentStart: "(:",
-    blockCommentEnd: ":)"
-
-  };
-
-});
-
-CodeMirror.defineMIME("application/xquery", "xquery");
-
-});
--- a/kallithea/public/codemirror/mode/yaml/yaml.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode("yaml", function() {
-
-  var cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
-  var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i');
-
-  return {
-    token: function(stream, state) {
-      var ch = stream.peek();
-      var esc = state.escaped;
-      state.escaped = false;
-      /* comments */
-      if (ch == "#" && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) {
-        stream.skipToEnd(); return "comment";
-      }
-      if (state.literal && stream.indentation() > state.keyCol) {
-        stream.skipToEnd(); return "string";
-      } else if (state.literal) { state.literal = false; }
-      if (stream.sol()) {
-        state.keyCol = 0;
-        state.pair = false;
-        state.pairStart = false;
-        /* document start */
-        if(stream.match(/---/)) { return "def"; }
-        /* document end */
-        if (stream.match(/\.\.\./)) { return "def"; }
-        /* array list item */
-        if (stream.match(/\s*-\s+/)) { return 'meta'; }
-      }
-      /* inline pairs/lists */
-      if (stream.match(/^(\{|\}|\[|\])/)) {
-        if (ch == '{')
-          state.inlinePairs++;
-        else if (ch == '}')
-          state.inlinePairs--;
-        else if (ch == '[')
-          state.inlineList++;
-        else
-          state.inlineList--;
-        return 'meta';
-      }
-
-      /* list seperator */
-      if (state.inlineList > 0 && !esc && ch == ',') {
-        stream.next();
-        return 'meta';
-      }
-      /* pairs seperator */
-      if (state.inlinePairs > 0 && !esc && ch == ',') {
-        state.keyCol = 0;
-        state.pair = false;
-        state.pairStart = false;
-        stream.next();
-        return 'meta';
-      }
-
-      /* start of value of a pair */
-      if (state.pairStart) {
-        /* block literals */
-        if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; };
-        /* references */
-        if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; }
-        /* numbers */
-        if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; }
-        if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; }
-        /* keywords */
-        if (stream.match(keywordRegex)) { return 'keyword'; }
-      }
-
-      /* pairs (associative arrays) -> key */
-      if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) {
-        state.pair = true;
-        state.keyCol = stream.indentation();
-        return "atom";
-      }
-      if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; }
-
-      /* nothing found, continue */
-      state.pairStart = false;
-      state.escaped = (ch == '\\');
-      stream.next();
-      return null;
-    },
-    startState: function() {
-      return {
-        pair: false,
-        pairStart: false,
-        keyCol: 0,
-        inlinePairs: 0,
-        inlineList: 0,
-        literal: false,
-        escaped: false
-      };
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-yaml", "yaml");
-
-});
--- a/kallithea/public/codemirror/mode/z80/z80.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  if (typeof exports == "object" && typeof module == "object") // CommonJS
-    mod(require("../../lib/codemirror"));
-  else if (typeof define == "function" && define.amd) // AMD
-    define(["../../lib/codemirror"], mod);
-  else // Plain browser env
-    mod(CodeMirror);
-})(function(CodeMirror) {
-"use strict";
-
-CodeMirror.defineMode('z80', function() {
-  var keywords1 = /^(exx?|(ld|cp|in)([di]r?)?|pop|push|ad[cd]|cpl|daa|dec|inc|neg|sbc|sub|and|bit|[cs]cf|x?or|res|set|r[lr]c?a?|r[lr]d|s[lr]a|srl|djnz|nop|rst|[de]i|halt|im|ot[di]r|out[di]?)\b/i;
-  var keywords2 = /^(call|j[pr]|ret[in]?)\b/i;
-  var keywords3 = /^b_?(call|jump)\b/i;
-  var variables1 = /^(af?|bc?|c|de?|e|hl?|l|i[xy]?|r|sp)\b/i;
-  var variables2 = /^(n?[zc]|p[oe]?|m)\b/i;
-  var errors = /^([hl][xy]|i[xy][hl]|slia|sll)\b/i;
-  var numbers = /^([\da-f]+h|[0-7]+o|[01]+b|\d+)\b/i;
-
-  return {
-    startState: function() {
-      return {context: 0};
-    },
-    token: function(stream, state) {
-      if (!stream.column())
-        state.context = 0;
-
-      if (stream.eatSpace())
-        return null;
-
-      var w;
-
-      if (stream.eatWhile(/\w/)) {
-        w = stream.current();
-
-        if (stream.indentation()) {
-          if (state.context == 1 && variables1.test(w))
-            return 'variable-2';
-
-          if (state.context == 2 && variables2.test(w))
-            return 'variable-3';
-
-          if (keywords1.test(w)) {
-            state.context = 1;
-            return 'keyword';
-          } else if (keywords2.test(w)) {
-            state.context = 2;
-            return 'keyword';
-          } else if (keywords3.test(w)) {
-            state.context = 3;
-            return 'keyword';
-          }
-
-          if (errors.test(w))
-            return 'error';
-        } else if (numbers.test(w)) {
-          return 'number';
-        } else {
-          return null;
-        }
-      } else if (stream.eat(';')) {
-        stream.skipToEnd();
-        return 'comment';
-      } else if (stream.eat('"')) {
-        while (w = stream.next()) {
-          if (w == '"')
-            break;
-
-          if (w == '\\')
-            stream.next();
-        }
-        return 'string';
-      } else if (stream.eat('\'')) {
-        if (stream.match(/\\?.'/))
-          return 'number';
-      } else if (stream.eat('.') || stream.sol() && stream.eat('#')) {
-        state.context = 4;
-
-        if (stream.eatWhile(/\w/))
-          return 'def';
-      } else if (stream.eat('$')) {
-        if (stream.eatWhile(/[\da-f]/i))
-          return 'number';
-      } else if (stream.eat('%')) {
-        if (stream.eatWhile(/[01]/))
-          return 'number';
-      } else {
-        stream.next();
-      }
-      return null;
-    }
-  };
-});
-
-CodeMirror.defineMIME("text/x-z80", "z80");
-
-});
--- a/kallithea/public/css/bootstrap.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6805 +0,0 @@
-/*!
- * Bootstrap v3.0.0
- *
- * Copyright 2013 Twitter, Inc
- * Licensed under the Apache License v2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world by @mdo and @fat.
- */
-
-/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
-
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-main,
-nav,
-section,
-summary {
-  display: block;
-}
-
-audio,
-canvas,
-video {
-  display: inline-block;
-}
-
-audio:not([controls]) {
-  display: none;
-  height: 0;
-}
-
-[hidden] {
-  display: none;
-}
-
-html {
-  font-family: sans-serif;
-  -webkit-text-size-adjust: 100%;
-      -ms-text-size-adjust: 100%;
-}
-
-body {
-  margin: 0;
-}
-
-a:focus {
-  outline: thin dotted;
-}
-
-a:active,
-a:hover {
-  outline: 0;
-}
-
-h1 {
-  margin: 0.67em 0;
-  font-size: 2em;
-}
-
-abbr[title] {
-  border-bottom: 1px dotted;
-}
-
-b,
-strong {
-  font-weight: bold;
-}
-
-dfn {
-  font-style: italic;
-}
-
-hr {
-  height: 0;
-  -moz-box-sizing: content-box;
-       box-sizing: content-box;
-}
-
-mark {
-  color: #000;
-  background: #ff0;
-}
-
-code,
-kbd,
-pre,
-samp {
-  font-family: monospace, serif;
-  font-size: 1em;
-}
-
-pre {
-  white-space: pre-wrap;
-}
-
-q {
-  quotes: "\201C" "\201D" "\2018" "\2019";
-}
-
-small {
-  font-size: 80%;
-}
-
-sub,
-sup {
-  position: relative;
-  font-size: 75%;
-  line-height: 0;
-  vertical-align: baseline;
-}
-
-sup {
-  top: -0.5em;
-}
-
-sub {
-  bottom: -0.25em;
-}
-
-img {
-  border: 0;
-}
-
-svg:not(:root) {
-  overflow: hidden;
-}
-
-figure {
-  margin: 0;
-}
-
-fieldset {
-  padding: 0.35em 0.625em 0.75em;
-  margin: 0 2px;
-  border: 1px solid #c0c0c0;
-}
-
-legend {
-  padding: 0;
-  border: 0;
-}
-
-button,
-input,
-select,
-textarea {
-  margin: 0;
-  font-family: inherit;
-  font-size: 100%;
-}
-
-button,
-input {
-  line-height: normal;
-}
-
-button,
-select {
-  text-transform: none;
-}
-
-button,
-html input[type="button"],
-input[type="reset"],
-input[type="submit"] {
-  cursor: pointer;
-  -webkit-appearance: button;
-}
-
-button[disabled],
-html input[disabled] {
-  cursor: default;
-}
-
-input[type="checkbox"],
-input[type="radio"] {
-  padding: 0;
-  box-sizing: border-box;
-}
-
-input[type="search"] {
-  -webkit-box-sizing: content-box;
-     -moz-box-sizing: content-box;
-          box-sizing: content-box;
-  -webkit-appearance: textfield;
-}
-
-input[type="search"]::-webkit-search-cancel-button,
-input[type="search"]::-webkit-search-decoration {
-  -webkit-appearance: none;
-}
-
-button::-moz-focus-inner,
-input::-moz-focus-inner {
-  padding: 0;
-  border: 0;
-}
-
-textarea {
-  overflow: auto;
-  vertical-align: top;
-}
-
-table {
-  border-collapse: collapse;
-  border-spacing: 0;
-}
-
-@media print {
-  * {
-    color: #000 !important;
-    text-shadow: none !important;
-    background: transparent !important;
-    box-shadow: none !important;
-  }
-  a,
-  a:visited {
-    text-decoration: underline;
-  }
-  a[href]:after {
-    content: " (" attr(href) ")";
-  }
-  abbr[title]:after {
-    content: " (" attr(title) ")";
-  }
-  .ir a:after,
-  a[href^="javascript:"]:after,
-  a[href^="#"]:after {
-    content: "";
-  }
-  pre,
-  blockquote {
-    border: 1px solid #999;
-    page-break-inside: avoid;
-  }
-  thead {
-    display: table-header-group;
-  }
-  tr,
-  img {
-    page-break-inside: avoid;
-  }
-  img {
-    max-width: 100% !important;
-  }
-  @page  {
-    margin: 2cm .5cm;
-  }
-  p,
-  h2,
-  h3 {
-    orphans: 3;
-    widows: 3;
-  }
-  h2,
-  h3 {
-    page-break-after: avoid;
-  }
-  .navbar {
-    display: none;
-  }
-  .table td,
-  .table th {
-    background-color: #fff !important;
-  }
-  .btn > .caret,
-  .dropup > .btn > .caret {
-    border-top-color: #000 !important;
-  }
-  .label {
-    border: 1px solid #000;
-  }
-  .table {
-    border-collapse: collapse !important;
-  }
-  .table-bordered th,
-  .table-bordered td {
-    border: 1px solid #ddd !important;
-  }
-}
-
-*,
-*:before,
-*:after {
-  -webkit-box-sizing: border-box;
-     -moz-box-sizing: border-box;
-          box-sizing: border-box;
-}
-
-html {
-  font-size: 62.5%;
-  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-}
-
-body {
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-  font-size: 14px;
-  line-height: 1.428571429;
-  color: #333333;
-  background-color: #ffffff;
-}
-
-input,
-button,
-select,
-textarea {
-  font-family: inherit;
-  font-size: inherit;
-  line-height: inherit;
-}
-
-button,
-input,
-select[multiple],
-textarea {
-  background-image: none;
-}
-
-a {
-  color: #428bca;
-  text-decoration: none;
-}
-
-a:hover,
-a:focus {
-  color: #2a6496;
-  text-decoration: underline;
-}
-
-a:focus {
-  outline: thin dotted #333;
-  outline: 5px auto -webkit-focus-ring-color;
-  outline-offset: -2px;
-}
-
-img {
-  vertical-align: middle;
-}
-
-.img-responsive {
-  display: block;
-  height: auto;
-  max-width: 100%;
-}
-
-.img-rounded {
-  border-radius: 6px;
-}
-
-.img-thumbnail {
-  display: inline-block;
-  height: auto;
-  max-width: 100%;
-  padding: 4px;
-  line-height: 1.428571429;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-  border-radius: 4px;
-  -webkit-transition: all 0.2s ease-in-out;
-          transition: all 0.2s ease-in-out;
-}
-
-.img-circle {
-  border-radius: 50%;
-}
-
-hr {
-  margin-top: 20px;
-  margin-bottom: 20px;
-  border: 0;
-  border-top: 1px solid #eeeeee;
-}
-
-.sr-only {
-  position: absolute;
-  width: 1px;
-  height: 1px;
-  padding: 0;
-  margin: -1px;
-  overflow: hidden;
-  clip: rect(0 0 0 0);
-  border: 0;
-}
-
-p {
-  margin: 0 0 10px;
-}
-
-.lead {
-  margin-bottom: 20px;
-  font-size: 16.099999999999998px;
-  font-weight: 200;
-  line-height: 1.4;
-}
-
-@media (min-width: 768px) {
-  .lead {
-    font-size: 21px;
-  }
-}
-
-small {
-  font-size: 85%;
-}
-
-cite {
-  font-style: normal;
-}
-
-.text-muted {
-  color: #999999;
-}
-
-.text-primary {
-  color: #428bca;
-}
-
-.text-warning {
-  color: #c09853;
-}
-
-.text-danger {
-  color: #b94a48;
-}
-
-.text-success {
-  color: #468847;
-}
-
-.text-info {
-  color: #3a87ad;
-}
-
-.text-left {
-  text-align: left;
-}
-
-.text-right {
-  text-align: right;
-}
-
-.text-center {
-  text-align: center;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6,
-.h1,
-.h2,
-.h3,
-.h4,
-.h5,
-.h6 {
-  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-  font-weight: 500;
-  line-height: 1.1;
-}
-
-h1 small,
-h2 small,
-h3 small,
-h4 small,
-h5 small,
-h6 small,
-.h1 small,
-.h2 small,
-.h3 small,
-.h4 small,
-.h5 small,
-.h6 small {
-  font-weight: normal;
-  line-height: 1;
-  color: #999999;
-}
-
-h1,
-h2,
-h3 {
-  margin-top: 20px;
-  margin-bottom: 10px;
-}
-
-h4,
-h5,
-h6 {
-  margin-top: 10px;
-  margin-bottom: 10px;
-}
-
-h1,
-.h1 {
-  font-size: 36px;
-}
-
-h2,
-.h2 {
-  font-size: 30px;
-}
-
-h3,
-.h3 {
-  font-size: 24px;
-}
-
-h4,
-.h4 {
-  font-size: 18px;
-}
-
-h5,
-.h5 {
-  font-size: 14px;
-}
-
-h6,
-.h6 {
-  font-size: 12px;
-}
-
-h1 small,
-.h1 small {
-  font-size: 24px;
-}
-
-h2 small,
-.h2 small {
-  font-size: 18px;
-}
-
-h3 small,
-.h3 small,
-h4 small,
-.h4 small {
-  font-size: 14px;
-}
-
-.page-header {
-  padding-bottom: 9px;
-  margin: 40px 0 20px;
-  border-bottom: 1px solid #eeeeee;
-}
-
-ul,
-ol {
-  margin-top: 0;
-  margin-bottom: 10px;
-}
-
-ul ul,
-ol ul,
-ul ol,
-ol ol {
-  margin-bottom: 0;
-}
-
-.list-unstyled {
-  padding-left: 0;
-  list-style: none;
-}
-
-.list-inline {
-  padding-left: 0;
-  list-style: none;
-}
-
-.list-inline > li {
-  display: inline-block;
-  padding-right: 5px;
-  padding-left: 5px;
-}
-
-dl {
-  margin-bottom: 20px;
-}
-
-dt,
-dd {
-  line-height: 1.428571429;
-}
-
-dt {
-  font-weight: bold;
-}
-
-dd {
-  margin-left: 0;
-}
-
-@media (min-width: 768px) {
-  .dl-horizontal dt {
-    float: left;
-    width: 160px;
-    overflow: hidden;
-    clear: left;
-    text-align: right;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-  .dl-horizontal dd {
-    margin-left: 180px;
-  }
-  .dl-horizontal dd:before,
-  .dl-horizontal dd:after {
-    display: table;
-    content: " ";
-  }
-  .dl-horizontal dd:after {
-    clear: both;
-  }
-  .dl-horizontal dd:before,
-  .dl-horizontal dd:after {
-    display: table;
-    content: " ";
-  }
-  .dl-horizontal dd:after {
-    clear: both;
-  }
-}
-
-abbr[title],
-abbr[data-original-title] {
-  cursor: help;
-  border-bottom: 1px dotted #999999;
-}
-
-abbr.initialism {
-  font-size: 90%;
-  text-transform: uppercase;
-}
-
-blockquote {
-  padding: 10px 20px;
-  margin: 0 0 20px;
-  border-left: 5px solid #eeeeee;
-}
-
-blockquote p {
-  font-size: 17.5px;
-  font-weight: 300;
-  line-height: 1.25;
-}
-
-blockquote p:last-child {
-  margin-bottom: 0;
-}
-
-blockquote small {
-  display: block;
-  line-height: 1.428571429;
-  color: #999999;
-}
-
-blockquote small:before {
-  content: '\2014 \00A0';
-}
-
-blockquote.pull-right {
-  padding-right: 15px;
-  padding-left: 0;
-  border-right: 5px solid #eeeeee;
-  border-left: 0;
-}
-
-blockquote.pull-right p,
-blockquote.pull-right small {
-  text-align: right;
-}
-
-blockquote.pull-right small:before {
-  content: '';
-}
-
-blockquote.pull-right small:after {
-  content: '\00A0 \2014';
-}
-
-q:before,
-q:after,
-blockquote:before,
-blockquote:after {
-  content: "";
-}
-
-address {
-  display: block;
-  margin-bottom: 20px;
-  font-style: normal;
-  line-height: 1.428571429;
-}
-
-code,
-pre {
-  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
-}
-
-code {
-  padding: 2px 4px;
-  font-size: 90%;
-  color: #c7254e;
-  white-space: nowrap;
-  background-color: #f9f2f4;
-  border-radius: 4px;
-}
-
-pre {
-  display: block;
-  padding: 9.5px;
-  margin: 0 0 10px;
-  font-size: 13px;
-  line-height: 1.428571429;
-  color: #333333;
-  word-break: break-all;
-  word-wrap: break-word;
-  background-color: #f5f5f5;
-  border: 1px solid #cccccc;
-  border-radius: 4px;
-}
-
-pre.prettyprint {
-  margin-bottom: 20px;
-}
-
-pre code {
-  padding: 0;
-  font-size: inherit;
-  color: inherit;
-  white-space: pre-wrap;
-  background-color: transparent;
-  border: 0;
-}
-
-.pre-scrollable {
-  max-height: 340px;
-  overflow-y: scroll;
-}
-
-.container {
-  padding-right: 15px;
-  padding-left: 15px;
-  margin-right: auto;
-  margin-left: auto;
-}
-
-.container:before,
-.container:after {
-  display: table;
-  content: " ";
-}
-
-.container:after {
-  clear: both;
-}
-
-.container:before,
-.container:after {
-  display: table;
-  content: " ";
-}
-
-.container:after {
-  clear: both;
-}
-
-.row {
-  margin-right: -15px;
-  margin-left: -15px;
-}
-
-.row:before,
-.row:after {
-  display: table;
-  content: " ";
-}
-
-.row:after {
-  clear: both;
-}
-
-.row:before,
-.row:after {
-  display: table;
-  content: " ";
-}
-
-.row:after {
-  clear: both;
-}
-
-.col-xs-1,
-.col-xs-2,
-.col-xs-3,
-.col-xs-4,
-.col-xs-5,
-.col-xs-6,
-.col-xs-7,
-.col-xs-8,
-.col-xs-9,
-.col-xs-10,
-.col-xs-11,
-.col-xs-12,
-.col-sm-1,
-.col-sm-2,
-.col-sm-3,
-.col-sm-4,
-.col-sm-5,
-.col-sm-6,
-.col-sm-7,
-.col-sm-8,
-.col-sm-9,
-.col-sm-10,
-.col-sm-11,
-.col-sm-12,
-.col-md-1,
-.col-md-2,
-.col-md-3,
-.col-md-4,
-.col-md-5,
-.col-md-6,
-.col-md-7,
-.col-md-8,
-.col-md-9,
-.col-md-10,
-.col-md-11,
-.col-md-12,
-.col-lg-1,
-.col-lg-2,
-.col-lg-3,
-.col-lg-4,
-.col-lg-5,
-.col-lg-6,
-.col-lg-7,
-.col-lg-8,
-.col-lg-9,
-.col-lg-10,
-.col-lg-11,
-.col-lg-12 {
-  position: relative;
-  min-height: 1px;
-  padding-right: 15px;
-  padding-left: 15px;
-}
-
-.col-xs-1,
-.col-xs-2,
-.col-xs-3,
-.col-xs-4,
-.col-xs-5,
-.col-xs-6,
-.col-xs-7,
-.col-xs-8,
-.col-xs-9,
-.col-xs-10,
-.col-xs-11 {
-  float: left;
-}
-
-.col-xs-1 {
-  width: 8.333333333333332%;
-}
-
-.col-xs-2 {
-  width: 16.666666666666664%;
-}
-
-.col-xs-3 {
-  width: 25%;
-}
-
-.col-xs-4 {
-  width: 33.33333333333333%;
-}
-
-.col-xs-5 {
-  width: 41.66666666666667%;
-}
-
-.col-xs-6 {
-  width: 50%;
-}
-
-.col-xs-7 {
-  width: 58.333333333333336%;
-}
-
-.col-xs-8 {
-  width: 66.66666666666666%;
-}
-
-.col-xs-9 {
-  width: 75%;
-}
-
-.col-xs-10 {
-  width: 83.33333333333334%;
-}
-
-.col-xs-11 {
-  width: 91.66666666666666%;
-}
-
-.col-xs-12 {
-  width: 100%;
-}
-
-@media (min-width: 768px) {
-  .container {
-    max-width: 750px;
-  }
-  .col-sm-1,
-  .col-sm-2,
-  .col-sm-3,
-  .col-sm-4,
-  .col-sm-5,
-  .col-sm-6,
-  .col-sm-7,
-  .col-sm-8,
-  .col-sm-9,
-  .col-sm-10,
-  .col-sm-11 {
-    float: left;
-  }
-  .col-sm-1 {
-    width: 8.333333333333332%;
-  }
-  .col-sm-2 {
-    width: 16.666666666666664%;
-  }
-  .col-sm-3 {
-    width: 25%;
-  }
-  .col-sm-4 {
-    width: 33.33333333333333%;
-  }
-  .col-sm-5 {
-    width: 41.66666666666667%;
-  }
-  .col-sm-6 {
-    width: 50%;
-  }
-  .col-sm-7 {
-    width: 58.333333333333336%;
-  }
-  .col-sm-8 {
-    width: 66.66666666666666%;
-  }
-  .col-sm-9 {
-    width: 75%;
-  }
-  .col-sm-10 {
-    width: 83.33333333333334%;
-  }
-  .col-sm-11 {
-    width: 91.66666666666666%;
-  }
-  .col-sm-12 {
-    width: 100%;
-  }
-  .col-sm-push-1 {
-    left: 8.333333333333332%;
-  }
-  .col-sm-push-2 {
-    left: 16.666666666666664%;
-  }
-  .col-sm-push-3 {
-    left: 25%;
-  }
-  .col-sm-push-4 {
-    left: 33.33333333333333%;
-  }
-  .col-sm-push-5 {
-    left: 41.66666666666667%;
-  }
-  .col-sm-push-6 {
-    left: 50%;
-  }
-  .col-sm-push-7 {
-    left: 58.333333333333336%;
-  }
-  .col-sm-push-8 {
-    left: 66.66666666666666%;
-  }
-  .col-sm-push-9 {
-    left: 75%;
-  }
-  .col-sm-push-10 {
-    left: 83.33333333333334%;
-  }
-  .col-sm-push-11 {
-    left: 91.66666666666666%;
-  }
-  .col-sm-pull-1 {
-    right: 8.333333333333332%;
-  }
-  .col-sm-pull-2 {
-    right: 16.666666666666664%;
-  }
-  .col-sm-pull-3 {
-    right: 25%;
-  }
-  .col-sm-pull-4 {
-    right: 33.33333333333333%;
-  }
-  .col-sm-pull-5 {
-    right: 41.66666666666667%;
-  }
-  .col-sm-pull-6 {
-    right: 50%;
-  }
-  .col-sm-pull-7 {
-    right: 58.333333333333336%;
-  }
-  .col-sm-pull-8 {
-    right: 66.66666666666666%;
-  }
-  .col-sm-pull-9 {
-    right: 75%;
-  }
-  .col-sm-pull-10 {
-    right: 83.33333333333334%;
-  }
-  .col-sm-pull-11 {
-    right: 91.66666666666666%;
-  }
-  .col-sm-offset-1 {
-    margin-left: 8.333333333333332%;
-  }
-  .col-sm-offset-2 {
-    margin-left: 16.666666666666664%;
-  }
-  .col-sm-offset-3 {
-    margin-left: 25%;
-  }
-  .col-sm-offset-4 {
-    margin-left: 33.33333333333333%;
-  }
-  .col-sm-offset-5 {
-    margin-left: 41.66666666666667%;
-  }
-  .col-sm-offset-6 {
-    margin-left: 50%;
-  }
-  .col-sm-offset-7 {
-    margin-left: 58.333333333333336%;
-  }
-  .col-sm-offset-8 {
-    margin-left: 66.66666666666666%;
-  }
-  .col-sm-offset-9 {
-    margin-left: 75%;
-  }
-  .col-sm-offset-10 {
-    margin-left: 83.33333333333334%;
-  }
-  .col-sm-offset-11 {
-    margin-left: 91.66666666666666%;
-  }
-}
-
-@media (min-width: 992px) {
-  .container {
-    max-width: 970px;
-  }
-  .col-md-1,
-  .col-md-2,
-  .col-md-3,
-  .col-md-4,
-  .col-md-5,
-  .col-md-6,
-  .col-md-7,
-  .col-md-8,
-  .col-md-9,
-  .col-md-10,
-  .col-md-11 {
-    float: left;
-  }
-  .col-md-1 {
-    width: 8.333333333333332%;
-  }
-  .col-md-2 {
-    width: 16.666666666666664%;
-  }
-  .col-md-3 {
-    width: 25%;
-  }
-  .col-md-4 {
-    width: 33.33333333333333%;
-  }
-  .col-md-5 {
-    width: 41.66666666666667%;
-  }
-  .col-md-6 {
-    width: 50%;
-  }
-  .col-md-7 {
-    width: 58.333333333333336%;
-  }
-  .col-md-8 {
-    width: 66.66666666666666%;
-  }
-  .col-md-9 {
-    width: 75%;
-  }
-  .col-md-10 {
-    width: 83.33333333333334%;
-  }
-  .col-md-11 {
-    width: 91.66666666666666%;
-  }
-  .col-md-12 {
-    width: 100%;
-  }
-  .col-md-push-0 {
-    left: auto;
-  }
-  .col-md-push-1 {
-    left: 8.333333333333332%;
-  }
-  .col-md-push-2 {
-    left: 16.666666666666664%;
-  }
-  .col-md-push-3 {
-    left: 25%;
-  }
-  .col-md-push-4 {
-    left: 33.33333333333333%;
-  }
-  .col-md-push-5 {
-    left: 41.66666666666667%;
-  }
-  .col-md-push-6 {
-    left: 50%;
-  }
-  .col-md-push-7 {
-    left: 58.333333333333336%;
-  }
-  .col-md-push-8 {
-    left: 66.66666666666666%;
-  }
-  .col-md-push-9 {
-    left: 75%;
-  }
-  .col-md-push-10 {
-    left: 83.33333333333334%;
-  }
-  .col-md-push-11 {
-    left: 91.66666666666666%;
-  }
-  .col-md-pull-0 {
-    right: auto;
-  }
-  .col-md-pull-1 {
-    right: 8.333333333333332%;
-  }
-  .col-md-pull-2 {
-    right: 16.666666666666664%;
-  }
-  .col-md-pull-3 {
-    right: 25%;
-  }
-  .col-md-pull-4 {
-    right: 33.33333333333333%;
-  }
-  .col-md-pull-5 {
-    right: 41.66666666666667%;
-  }
-  .col-md-pull-6 {
-    right: 50%;
-  }
-  .col-md-pull-7 {
-    right: 58.333333333333336%;
-  }
-  .col-md-pull-8 {
-    right: 66.66666666666666%;
-  }
-  .col-md-pull-9 {
-    right: 75%;
-  }
-  .col-md-pull-10 {
-    right: 83.33333333333334%;
-  }
-  .col-md-pull-11 {
-    right: 91.66666666666666%;
-  }
-  .col-md-offset-0 {
-    margin-left: 0;
-  }
-  .col-md-offset-1 {
-    margin-left: 8.333333333333332%;
-  }
-  .col-md-offset-2 {
-    margin-left: 16.666666666666664%;
-  }
-  .col-md-offset-3 {
-    margin-left: 25%;
-  }
-  .col-md-offset-4 {
-    margin-left: 33.33333333333333%;
-  }
-  .col-md-offset-5 {
-    margin-left: 41.66666666666667%;
-  }
-  .col-md-offset-6 {
-    margin-left: 50%;
-  }
-  .col-md-offset-7 {
-    margin-left: 58.333333333333336%;
-  }
-  .col-md-offset-8 {
-    margin-left: 66.66666666666666%;
-  }
-  .col-md-offset-9 {
-    margin-left: 75%;
-  }
-  .col-md-offset-10 {
-    margin-left: 83.33333333333334%;
-  }
-  .col-md-offset-11 {
-    margin-left: 91.66666666666666%;
-  }
-}
-
-@media (min-width: 1200px) {
-  .container {
-    max-width: 1170px;
-  }
-  .col-lg-1,
-  .col-lg-2,
-  .col-lg-3,
-  .col-lg-4,
-  .col-lg-5,
-  .col-lg-6,
-  .col-lg-7,
-  .col-lg-8,
-  .col-lg-9,
-  .col-lg-10,
-  .col-lg-11 {
-    float: left;
-  }
-  .col-lg-1 {
-    width: 8.333333333333332%;
-  }
-  .col-lg-2 {
-    width: 16.666666666666664%;
-  }
-  .col-lg-3 {
-    width: 25%;
-  }
-  .col-lg-4 {
-    width: 33.33333333333333%;
-  }
-  .col-lg-5 {
-    width: 41.66666666666667%;
-  }
-  .col-lg-6 {
-    width: 50%;
-  }
-  .col-lg-7 {
-    width: 58.333333333333336%;
-  }
-  .col-lg-8 {
-    width: 66.66666666666666%;
-  }
-  .col-lg-9 {
-    width: 75%;
-  }
-  .col-lg-10 {
-    width: 83.33333333333334%;
-  }
-  .col-lg-11 {
-    width: 91.66666666666666%;
-  }
-  .col-lg-12 {
-    width: 100%;
-  }
-  .col-lg-push-0 {
-    left: auto;
-  }
-  .col-lg-push-1 {
-    left: 8.333333333333332%;
-  }
-  .col-lg-push-2 {
-    left: 16.666666666666664%;
-  }
-  .col-lg-push-3 {
-    left: 25%;
-  }
-  .col-lg-push-4 {
-    left: 33.33333333333333%;
-  }
-  .col-lg-push-5 {
-    left: 41.66666666666667%;
-  }
-  .col-lg-push-6 {
-    left: 50%;
-  }
-  .col-lg-push-7 {
-    left: 58.333333333333336%;
-  }
-  .col-lg-push-8 {
-    left: 66.66666666666666%;
-  }
-  .col-lg-push-9 {
-    left: 75%;
-  }
-  .col-lg-push-10 {
-    left: 83.33333333333334%;
-  }
-  .col-lg-push-11 {
-    left: 91.66666666666666%;
-  }
-  .col-lg-pull-0 {
-    right: auto;
-  }
-  .col-lg-pull-1 {
-    right: 8.333333333333332%;
-  }
-  .col-lg-pull-2 {
-    right: 16.666666666666664%;
-  }
-  .col-lg-pull-3 {
-    right: 25%;
-  }
-  .col-lg-pull-4 {
-    right: 33.33333333333333%;
-  }
-  .col-lg-pull-5 {
-    right: 41.66666666666667%;
-  }
-  .col-lg-pull-6 {
-    right: 50%;
-  }
-  .col-lg-pull-7 {
-    right: 58.333333333333336%;
-  }
-  .col-lg-pull-8 {
-    right: 66.66666666666666%;
-  }
-  .col-lg-pull-9 {
-    right: 75%;
-  }
-  .col-lg-pull-10 {
-    right: 83.33333333333334%;
-  }
-  .col-lg-pull-11 {
-    right: 91.66666666666666%;
-  }
-  .col-lg-offset-0 {
-    margin-left: 0;
-  }
-  .col-lg-offset-1 {
-    margin-left: 8.333333333333332%;
-  }
-  .col-lg-offset-2 {
-    margin-left: 16.666666666666664%;
-  }
-  .col-lg-offset-3 {
-    margin-left: 25%;
-  }
-  .col-lg-offset-4 {
-    margin-left: 33.33333333333333%;
-  }
-  .col-lg-offset-5 {
-    margin-left: 41.66666666666667%;
-  }
-  .col-lg-offset-6 {
-    margin-left: 50%;
-  }
-  .col-lg-offset-7 {
-    margin-left: 58.333333333333336%;
-  }
-  .col-lg-offset-8 {
-    margin-left: 66.66666666666666%;
-  }
-  .col-lg-offset-9 {
-    margin-left: 75%;
-  }
-  .col-lg-offset-10 {
-    margin-left: 83.33333333333334%;
-  }
-  .col-lg-offset-11 {
-    margin-left: 91.66666666666666%;
-  }
-}
-
-table {
-  max-width: 100%;
-  background-color: transparent;
-}
-
-th {
-  text-align: left;
-}
-
-.table {
-  width: 100%;
-  margin-bottom: 20px;
-}
-
-.table thead > tr > th,
-.table tbody > tr > th,
-.table tfoot > tr > th,
-.table thead > tr > td,
-.table tbody > tr > td,
-.table tfoot > tr > td {
-  padding: 8px;
-  line-height: 1.428571429;
-  vertical-align: top;
-  border-top: 1px solid #dddddd;
-}
-
-.table thead > tr > th {
-  vertical-align: bottom;
-  border-bottom: 2px solid #dddddd;
-}
-
-.table caption + thead tr:first-child th,
-.table colgroup + thead tr:first-child th,
-.table thead:first-child tr:first-child th,
-.table caption + thead tr:first-child td,
-.table colgroup + thead tr:first-child td,
-.table thead:first-child tr:first-child td {
-  border-top: 0;
-}
-
-.table tbody + tbody {
-  border-top: 2px solid #dddddd;
-}
-
-.table .table {
-  background-color: #ffffff;
-}
-
-.table-condensed thead > tr > th,
-.table-condensed tbody > tr > th,
-.table-condensed tfoot > tr > th,
-.table-condensed thead > tr > td,
-.table-condensed tbody > tr > td,
-.table-condensed tfoot > tr > td {
-  padding: 5px;
-}
-
-.table-bordered {
-  border: 1px solid #dddddd;
-}
-
-.table-bordered > thead > tr > th,
-.table-bordered > tbody > tr > th,
-.table-bordered > tfoot > tr > th,
-.table-bordered > thead > tr > td,
-.table-bordered > tbody > tr > td,
-.table-bordered > tfoot > tr > td {
-  border: 1px solid #dddddd;
-}
-
-.table-bordered > thead > tr > th,
-.table-bordered > thead > tr > td {
-  border-bottom-width: 2px;
-}
-
-.table-striped > tbody > tr:nth-child(odd) > td,
-.table-striped > tbody > tr:nth-child(odd) > th {
-  background-color: #f9f9f9;
-}
-
-.table-hover > tbody > tr:hover > td,
-.table-hover > tbody > tr:hover > th {
-  background-color: #f5f5f5;
-}
-
-table col[class*="col-"] {
-  display: table-column;
-  float: none;
-}
-
-table td[class*="col-"],
-table th[class*="col-"] {
-  display: table-cell;
-  float: none;
-}
-
-.table > thead > tr > td.active,
-.table > tbody > tr > td.active,
-.table > tfoot > tr > td.active,
-.table > thead > tr > th.active,
-.table > tbody > tr > th.active,
-.table > tfoot > tr > th.active,
-.table > thead > tr.active > td,
-.table > tbody > tr.active > td,
-.table > tfoot > tr.active > td,
-.table > thead > tr.active > th,
-.table > tbody > tr.active > th,
-.table > tfoot > tr.active > th {
-  background-color: #f5f5f5;
-}
-
-.table > thead > tr > td.success,
-.table > tbody > tr > td.success,
-.table > tfoot > tr > td.success,
-.table > thead > tr > th.success,
-.table > tbody > tr > th.success,
-.table > tfoot > tr > th.success,
-.table > thead > tr.success > td,
-.table > tbody > tr.success > td,
-.table > tfoot > tr.success > td,
-.table > thead > tr.success > th,
-.table > tbody > tr.success > th,
-.table > tfoot > tr.success > th {
-  background-color: #dff0d8;
-  border-color: #d6e9c6;
-}
-
-.table-hover > tbody > tr > td.success:hover,
-.table-hover > tbody > tr > th.success:hover,
-.table-hover > tbody > tr.success:hover > td {
-  background-color: #d0e9c6;
-  border-color: #c9e2b3;
-}
-
-.table > thead > tr > td.danger,
-.table > tbody > tr > td.danger,
-.table > tfoot > tr > td.danger,
-.table > thead > tr > th.danger,
-.table > tbody > tr > th.danger,
-.table > tfoot > tr > th.danger,
-.table > thead > tr.danger > td,
-.table > tbody > tr.danger > td,
-.table > tfoot > tr.danger > td,
-.table > thead > tr.danger > th,
-.table > tbody > tr.danger > th,
-.table > tfoot > tr.danger > th {
-  background-color: #f2dede;
-  border-color: #eed3d7;
-}
-
-.table-hover > tbody > tr > td.danger:hover,
-.table-hover > tbody > tr > th.danger:hover,
-.table-hover > tbody > tr.danger:hover > td {
-  background-color: #ebcccc;
-  border-color: #e6c1c7;
-}
-
-.table > thead > tr > td.warning,
-.table > tbody > tr > td.warning,
-.table > tfoot > tr > td.warning,
-.table > thead > tr > th.warning,
-.table > tbody > tr > th.warning,
-.table > tfoot > tr > th.warning,
-.table > thead > tr.warning > td,
-.table > tbody > tr.warning > td,
-.table > tfoot > tr.warning > td,
-.table > thead > tr.warning > th,
-.table > tbody > tr.warning > th,
-.table > tfoot > tr.warning > th {
-  background-color: #fcf8e3;
-  border-color: #fbeed5;
-}
-
-.table-hover > tbody > tr > td.warning:hover,
-.table-hover > tbody > tr > th.warning:hover,
-.table-hover > tbody > tr.warning:hover > td {
-  background-color: #faf2cc;
-  border-color: #f8e5be;
-}
-
-@media (max-width: 768px) {
-  .table-responsive {
-    width: 100%;
-    margin-bottom: 15px;
-    overflow-x: scroll;
-    overflow-y: hidden;
-    border: 1px solid #dddddd;
-  }
-  .table-responsive > .table {
-    margin-bottom: 0;
-    background-color: #fff;
-  }
-  .table-responsive > .table > thead > tr > th,
-  .table-responsive > .table > tbody > tr > th,
-  .table-responsive > .table > tfoot > tr > th,
-  .table-responsive > .table > thead > tr > td,
-  .table-responsive > .table > tbody > tr > td,
-  .table-responsive > .table > tfoot > tr > td {
-    white-space: nowrap;
-  }
-  .table-responsive > .table-bordered {
-    border: 0;
-  }
-  .table-responsive > .table-bordered > thead > tr > th:first-child,
-  .table-responsive > .table-bordered > tbody > tr > th:first-child,
-  .table-responsive > .table-bordered > tfoot > tr > th:first-child,
-  .table-responsive > .table-bordered > thead > tr > td:first-child,
-  .table-responsive > .table-bordered > tbody > tr > td:first-child,
-  .table-responsive > .table-bordered > tfoot > tr > td:first-child {
-    border-left: 0;
-  }
-  .table-responsive > .table-bordered > thead > tr > th:last-child,
-  .table-responsive > .table-bordered > tbody > tr > th:last-child,
-  .table-responsive > .table-bordered > tfoot > tr > th:last-child,
-  .table-responsive > .table-bordered > thead > tr > td:last-child,
-  .table-responsive > .table-bordered > tbody > tr > td:last-child,
-  .table-responsive > .table-bordered > tfoot > tr > td:last-child {
-    border-right: 0;
-  }
-  .table-responsive > .table-bordered > thead > tr:last-child > th,
-  .table-responsive > .table-bordered > tbody > tr:last-child > th,
-  .table-responsive > .table-bordered > tfoot > tr:last-child > th,
-  .table-responsive > .table-bordered > thead > tr:last-child > td,
-  .table-responsive > .table-bordered > tbody > tr:last-child > td,
-  .table-responsive > .table-bordered > tfoot > tr:last-child > td {
-    border-bottom: 0;
-  }
-}
-
-fieldset {
-  padding: 0;
-  margin: 0;
-  border: 0;
-}
-
-legend {
-  display: block;
-  width: 100%;
-  padding: 0;
-  margin-bottom: 20px;
-  font-size: 21px;
-  line-height: inherit;
-  color: #333333;
-  border: 0;
-  border-bottom: 1px solid #e5e5e5;
-}
-
-label {
-  display: inline-block;
-  margin-bottom: 5px;
-  font-weight: bold;
-}
-
-input[type="search"] {
-  -webkit-box-sizing: border-box;
-     -moz-box-sizing: border-box;
-          box-sizing: border-box;
-}
-
-input[type="radio"],
-input[type="checkbox"] {
-  margin: 4px 0 0;
-  margin-top: 1px \9;
-  /* IE8-9 */
-
-  line-height: normal;
-}
-
-input[type="file"] {
-  display: block;
-}
-
-select[multiple],
-select[size] {
-  height: auto;
-}
-
-select optgroup {
-  font-family: inherit;
-  font-size: inherit;
-  font-style: inherit;
-}
-
-input[type="file"]:focus,
-input[type="radio"]:focus,
-input[type="checkbox"]:focus {
-  outline: thin dotted #333;
-  outline: 5px auto -webkit-focus-ring-color;
-  outline-offset: -2px;
-}
-
-input[type="number"]::-webkit-outer-spin-button,
-input[type="number"]::-webkit-inner-spin-button {
-  height: auto;
-}
-
-.form-control:-moz-placeholder {
-  color: #999999;
-}
-
-.form-control::-moz-placeholder {
-  color: #999999;
-}
-
-.form-control:-ms-input-placeholder {
-  color: #999999;
-}
-
-.form-control::-webkit-input-placeholder {
-  color: #999999;
-}
-
-.form-control {
-  display: block;
-  width: 100%;
-  height: 34px;
-  padding: 6px 12px;
-  font-size: 14px;
-  line-height: 1.428571429;
-  color: #555555;
-  vertical-align: middle;
-  background-color: #ffffff;
-  border: 1px solid #cccccc;
-  border-radius: 4px;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-}
-
-.form-control:focus {
-  border-color: #66afe9;
-  outline: 0;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-}
-
-.form-control[disabled],
-.form-control[readonly],
-fieldset[disabled] .form-control {
-  cursor: not-allowed;
-  background-color: #eeeeee;
-}
-
-textarea.form-control {
-  height: auto;
-}
-
-.form-group {
-  margin-bottom: 15px;
-}
-
-.radio,
-.checkbox {
-  display: block;
-  min-height: 20px;
-  padding-left: 20px;
-  margin-top: 10px;
-  margin-bottom: 10px;
-  vertical-align: middle;
-}
-
-.radio label,
-.checkbox label {
-  display: inline;
-  margin-bottom: 0;
-  font-weight: normal;
-  cursor: pointer;
-}
-
-.radio input[type="radio"],
-.radio-inline input[type="radio"],
-.checkbox input[type="checkbox"],
-.checkbox-inline input[type="checkbox"] {
-  float: left;
-  margin-left: -20px;
-}
-
-.radio + .radio,
-.checkbox + .checkbox {
-  margin-top: -5px;
-}
-
-.radio-inline,
-.checkbox-inline {
-  display: inline-block;
-  padding-left: 20px;
-  margin-bottom: 0;
-  font-weight: normal;
-  vertical-align: middle;
-  cursor: pointer;
-}
-
-.radio-inline + .radio-inline,
-.checkbox-inline + .checkbox-inline {
-  margin-top: 0;
-  margin-left: 10px;
-}
-
-input[type="radio"][disabled],
-input[type="checkbox"][disabled],
-.radio[disabled],
-.radio-inline[disabled],
-.checkbox[disabled],
-.checkbox-inline[disabled],
-fieldset[disabled] input[type="radio"],
-fieldset[disabled] input[type="checkbox"],
-fieldset[disabled] .radio,
-fieldset[disabled] .radio-inline,
-fieldset[disabled] .checkbox,
-fieldset[disabled] .checkbox-inline {
-  cursor: not-allowed;
-}
-
-.input-sm {
-  height: 30px;
-  padding: 5px 10px;
-  font-size: 12px;
-  line-height: 1.5;
-  border-radius: 3px;
-}
-
-select.input-sm {
-  height: 30px;
-  line-height: 30px;
-}
-
-textarea.input-sm {
-  height: auto;
-}
-
-.input-lg {
-  height: 45px;
-  padding: 10px 16px;
-  font-size: 18px;
-  line-height: 1.33;
-  border-radius: 6px;
-}
-
-select.input-lg {
-  height: 45px;
-  line-height: 45px;
-}
-
-textarea.input-lg {
-  height: auto;
-}
-
-.has-warning .help-block,
-.has-warning .control-label {
-  color: #c09853;
-}
-
-.has-warning .form-control {
-  border-color: #c09853;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.has-warning .form-control:focus {
-  border-color: #a47e3c;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
-}
-
-.has-warning .input-group-addon {
-  color: #c09853;
-  background-color: #fcf8e3;
-  border-color: #c09853;
-}
-
-.has-error .help-block,
-.has-error .control-label {
-  color: #b94a48;
-}
-
-.has-error .form-control {
-  border-color: #b94a48;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.has-error .form-control:focus {
-  border-color: #953b39;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
-}
-
-.has-error .input-group-addon {
-  color: #b94a48;
-  background-color: #f2dede;
-  border-color: #b94a48;
-}
-
-.has-success .help-block,
-.has-success .control-label {
-  color: #468847;
-}
-
-.has-success .form-control {
-  border-color: #468847;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.has-success .form-control:focus {
-  border-color: #356635;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
-}
-
-.has-success .input-group-addon {
-  color: #468847;
-  background-color: #dff0d8;
-  border-color: #468847;
-}
-
-.form-control-static {
-  padding-top: 7px;
-  margin-bottom: 0;
-}
-
-.help-block {
-  display: block;
-  margin-top: 5px;
-  margin-bottom: 10px;
-  color: #737373;
-}
-
-@media (min-width: 768px) {
-  .form-inline .form-group {
-    display: inline-block;
-    margin-bottom: 0;
-    vertical-align: middle;
-  }
-  .form-inline .form-control {
-    display: inline-block;
-  }
-  .form-inline .radio,
-  .form-inline .checkbox {
-    display: inline-block;
-    padding-left: 0;
-    margin-top: 0;
-    margin-bottom: 0;
-  }
-  .form-inline .radio input[type="radio"],
-  .form-inline .checkbox input[type="checkbox"] {
-    float: none;
-    margin-left: 0;
-  }
-}
-
-.form-horizontal .control-label,
-.form-horizontal .radio,
-.form-horizontal .checkbox,
-.form-horizontal .radio-inline,
-.form-horizontal .checkbox-inline {
-  padding-top: 7px;
-  margin-top: 0;
-  margin-bottom: 0;
-}
-
-.form-horizontal .form-group {
-  margin-right: -15px;
-  margin-left: -15px;
-}
-
-.form-horizontal .form-group:before,
-.form-horizontal .form-group:after {
-  display: table;
-  content: " ";
-}
-
-.form-horizontal .form-group:after {
-  clear: both;
-}
-
-.form-horizontal .form-group:before,
-.form-horizontal .form-group:after {
-  display: table;
-  content: " ";
-}
-
-.form-horizontal .form-group:after {
-  clear: both;
-}
-
-@media (min-width: 768px) {
-  .form-horizontal .control-label {
-    text-align: right;
-  }
-}
-
-.btn {
-  display: inline-block;
-  padding: 6px 12px;
-  margin-bottom: 0;
-  font-size: 14px;
-  font-weight: normal;
-  line-height: 1.428571429;
-  text-align: center;
-  white-space: nowrap;
-  vertical-align: middle;
-  cursor: pointer;
-  border: 1px solid transparent;
-  border-radius: 4px;
-  -webkit-user-select: none;
-     -moz-user-select: none;
-      -ms-user-select: none;
-       -o-user-select: none;
-          user-select: none;
-}
-
-.btn:focus {
-  outline: thin dotted #333;
-  outline: 5px auto -webkit-focus-ring-color;
-  outline-offset: -2px;
-}
-
-.btn:hover,
-.btn:focus {
-  color: #333333;
-  text-decoration: none;
-}
-
-.btn:active,
-.btn.active {
-  background-image: none;
-  outline: 0;
-  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-}
-
-.btn.disabled,
-.btn[disabled],
-fieldset[disabled] .btn {
-  pointer-events: none;
-  cursor: not-allowed;
-  opacity: 0.65;
-  filter: alpha(opacity=65);
-  -webkit-box-shadow: none;
-          box-shadow: none;
-}
-
-.btn-default {
-  color: #333333;
-  background-color: #ffffff;
-  border-color: #cccccc;
-}
-
-.btn-default:hover,
-.btn-default:focus,
-.btn-default:active,
-.btn-default.active,
-.open .dropdown-toggle.btn-default {
-  color: #333333;
-  background-color: #ebebeb;
-  border-color: #adadad;
-}
-
-.btn-default:active,
-.btn-default.active,
-.open .dropdown-toggle.btn-default {
-  background-image: none;
-}
-
-.btn-default.disabled,
-.btn-default[disabled],
-fieldset[disabled] .btn-default,
-.btn-default.disabled:hover,
-.btn-default[disabled]:hover,
-fieldset[disabled] .btn-default:hover,
-.btn-default.disabled:focus,
-.btn-default[disabled]:focus,
-fieldset[disabled] .btn-default:focus,
-.btn-default.disabled:active,
-.btn-default[disabled]:active,
-fieldset[disabled] .btn-default:active,
-.btn-default.disabled.active,
-.btn-default[disabled].active,
-fieldset[disabled] .btn-default.active {
-  background-color: #ffffff;
-  border-color: #cccccc;
-}
-
-.btn-primary {
-  color: #ffffff;
-  background-color: #428bca;
-  border-color: #357ebd;
-}
-
-.btn-primary:hover,
-.btn-primary:focus,
-.btn-primary:active,
-.btn-primary.active,
-.open .dropdown-toggle.btn-primary {
-  color: #ffffff;
-  background-color: #3276b1;
-  border-color: #285e8e;
-}
-
-.btn-primary:active,
-.btn-primary.active,
-.open .dropdown-toggle.btn-primary {
-  background-image: none;
-}
-
-.btn-primary.disabled,
-.btn-primary[disabled],
-fieldset[disabled] .btn-primary,
-.btn-primary.disabled:hover,
-.btn-primary[disabled]:hover,
-fieldset[disabled] .btn-primary:hover,
-.btn-primary.disabled:focus,
-.btn-primary[disabled]:focus,
-fieldset[disabled] .btn-primary:focus,
-.btn-primary.disabled:active,
-.btn-primary[disabled]:active,
-fieldset[disabled] .btn-primary:active,
-.btn-primary.disabled.active,
-.btn-primary[disabled].active,
-fieldset[disabled] .btn-primary.active {
-  background-color: #428bca;
-  border-color: #357ebd;
-}
-
-.btn-warning {
-  color: #ffffff;
-  background-color: #f0ad4e;
-  border-color: #eea236;
-}
-
-.btn-warning:hover,
-.btn-warning:focus,
-.btn-warning:active,
-.btn-warning.active,
-.open .dropdown-toggle.btn-warning {
-  color: #ffffff;
-  background-color: #ed9c28;
-  border-color: #d58512;
-}
-
-.btn-warning:active,
-.btn-warning.active,
-.open .dropdown-toggle.btn-warning {
-  background-image: none;
-}
-
-.btn-warning.disabled,
-.btn-warning[disabled],
-fieldset[disabled] .btn-warning,
-.btn-warning.disabled:hover,
-.btn-warning[disabled]:hover,
-fieldset[disabled] .btn-warning:hover,
-.btn-warning.disabled:focus,
-.btn-warning[disabled]:focus,
-fieldset[disabled] .btn-warning:focus,
-.btn-warning.disabled:active,
-.btn-warning[disabled]:active,
-fieldset[disabled] .btn-warning:active,
-.btn-warning.disabled.active,
-.btn-warning[disabled].active,
-fieldset[disabled] .btn-warning.active {
-  background-color: #f0ad4e;
-  border-color: #eea236;
-}
-
-.btn-danger {
-  color: #ffffff;
-  background-color: #d9534f;
-  border-color: #d43f3a;
-}
-
-.btn-danger:hover,
-.btn-danger:focus,
-.btn-danger:active,
-.btn-danger.active,
-.open .dropdown-toggle.btn-danger {
-  color: #ffffff;
-  background-color: #d2322d;
-  border-color: #ac2925;
-}
-
-.btn-danger:active,
-.btn-danger.active,
-.open .dropdown-toggle.btn-danger {
-  background-image: none;
-}
-
-.btn-danger.disabled,
-.btn-danger[disabled],
-fieldset[disabled] .btn-danger,
-.btn-danger.disabled:hover,
-.btn-danger[disabled]:hover,
-fieldset[disabled] .btn-danger:hover,
-.btn-danger.disabled:focus,
-.btn-danger[disabled]:focus,
-fieldset[disabled] .btn-danger:focus,
-.btn-danger.disabled:active,
-.btn-danger[disabled]:active,
-fieldset[disabled] .btn-danger:active,
-.btn-danger.disabled.active,
-.btn-danger[disabled].active,
-fieldset[disabled] .btn-danger.active {
-  background-color: #d9534f;
-  border-color: #d43f3a;
-}
-
-.btn-success {
-  color: #ffffff;
-  background-color: #5cb85c;
-  border-color: #4cae4c;
-}
-
-.btn-success:hover,
-.btn-success:focus,
-.btn-success:active,
-.btn-success.active,
-.open .dropdown-toggle.btn-success {
-  color: #ffffff;
-  background-color: #47a447;
-  border-color: #398439;
-}
-
-.btn-success:active,
-.btn-success.active,
-.open .dropdown-toggle.btn-success {
-  background-image: none;
-}
-
-.btn-success.disabled,
-.btn-success[disabled],
-fieldset[disabled] .btn-success,
-.btn-success.disabled:hover,
-.btn-success[disabled]:hover,
-fieldset[disabled] .btn-success:hover,
-.btn-success.disabled:focus,
-.btn-success[disabled]:focus,
-fieldset[disabled] .btn-success:focus,
-.btn-success.disabled:active,
-.btn-success[disabled]:active,
-fieldset[disabled] .btn-success:active,
-.btn-success.disabled.active,
-.btn-success[disabled].active,
-fieldset[disabled] .btn-success.active {
-  background-color: #5cb85c;
-  border-color: #4cae4c;
-}
-
-.btn-info {
-  color: #ffffff;
-  background-color: #5bc0de;
-  border-color: #46b8da;
-}
-
-.btn-info:hover,
-.btn-info:focus,
-.btn-info:active,
-.btn-info.active,
-.open .dropdown-toggle.btn-info {
-  color: #ffffff;
-  background-color: #39b3d7;
-  border-color: #269abc;
-}
-
-.btn-info:active,
-.btn-info.active,
-.open .dropdown-toggle.btn-info {
-  background-image: none;
-}
-
-.btn-info.disabled,
-.btn-info[disabled],
-fieldset[disabled] .btn-info,
-.btn-info.disabled:hover,
-.btn-info[disabled]:hover,
-fieldset[disabled] .btn-info:hover,
-.btn-info.disabled:focus,
-.btn-info[disabled]:focus,
-fieldset[disabled] .btn-info:focus,
-.btn-info.disabled:active,
-.btn-info[disabled]:active,
-fieldset[disabled] .btn-info:active,
-.btn-info.disabled.active,
-.btn-info[disabled].active,
-fieldset[disabled] .btn-info.active {
-  background-color: #5bc0de;
-  border-color: #46b8da;
-}
-
-.btn-link {
-  font-weight: normal;
-  color: #428bca;
-  cursor: pointer;
-  border-radius: 0;
-}
-
-.btn-link,
-.btn-link:active,
-.btn-link[disabled],
-fieldset[disabled] .btn-link {
-  background-color: transparent;
-  -webkit-box-shadow: none;
-          box-shadow: none;
-}
-
-.btn-link,
-.btn-link:hover,
-.btn-link:focus,
-.btn-link:active {
-  border-color: transparent;
-}
-
-.btn-link:hover,
-.btn-link:focus {
-  color: #2a6496;
-  text-decoration: underline;
-  background-color: transparent;
-}
-
-.btn-link[disabled]:hover,
-fieldset[disabled] .btn-link:hover,
-.btn-link[disabled]:focus,
-fieldset[disabled] .btn-link:focus {
-  color: #999999;
-  text-decoration: none;
-}
-
-.btn-lg {
-  padding: 10px 16px;
-  font-size: 18px;
-  line-height: 1.33;
-  border-radius: 6px;
-}
-
-.btn-sm,
-.btn-xs {
-  padding: 5px 10px;
-  font-size: 12px;
-  line-height: 1.5;
-  border-radius: 3px;
-}
-
-.btn-xs {
-  padding: 1px 5px;
-}
-
-.btn-block {
-  display: block;
-  width: 100%;
-  padding-right: 0;
-  padding-left: 0;
-}
-
-.btn-block + .btn-block {
-  margin-top: 5px;
-}
-
-input[type="submit"].btn-block,
-input[type="reset"].btn-block,
-input[type="button"].btn-block {
-  width: 100%;
-}
-
-.fade {
-  opacity: 0;
-  -webkit-transition: opacity 0.15s linear;
-          transition: opacity 0.15s linear;
-}
-
-.fade.in {
-  opacity: 1;
-}
-
-.collapse {
-  display: none;
-}
-
-.collapse.in {
-  display: block;
-}
-
-.collapsing {
-  position: relative;
-  height: 0;
-  overflow: hidden;
-  -webkit-transition: height 0.35s ease;
-          transition: height 0.35s ease;
-}
-
-@font-face {
-  font-family: 'Glyphicons Halflings';
-  src: url('../fonts/glyphicons-halflings-regular.eot');
-  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
-}
-
-.glyphicon {
-  position: relative;
-  top: 1px;
-  display: inline-block;
-  font-family: 'Glyphicons Halflings';
-  -webkit-font-smoothing: antialiased;
-  font-style: normal;
-  font-weight: normal;
-  line-height: 1;
-}
-
-.glyphicon-asterisk:before {
-  content: "\2a";
-}
-
-.glyphicon-plus:before {
-  content: "\2b";
-}
-
-.glyphicon-euro:before {
-  content: "\20ac";
-}
-
-.glyphicon-minus:before {
-  content: "\2212";
-}
-
-.glyphicon-cloud:before {
-  content: "\2601";
-}
-
-.glyphicon-envelope:before {
-  content: "\2709";
-}
-
-.glyphicon-pencil:before {
-  content: "\270f";
-}
-
-.glyphicon-glass:before {
-  content: "\e001";
-}
-
-.glyphicon-music:before {
-  content: "\e002";
-}
-
-.glyphicon-search:before {
-  content: "\e003";
-}
-
-.glyphicon-heart:before {
-  content: "\e005";
-}
-
-.glyphicon-star:before {
-  content: "\e006";
-}
-
-.glyphicon-star-empty:before {
-  content: "\e007";
-}
-
-.glyphicon-user:before {
-  content: "\e008";
-}
-
-.glyphicon-film:before {
-  content: "\e009";
-}
-
-.glyphicon-th-large:before {
-  content: "\e010";
-}
-
-.glyphicon-th:before {
-  content: "\e011";
-}
-
-.glyphicon-th-list:before {
-  content: "\e012";
-}
-
-.glyphicon-ok:before {
-  content: "\e013";
-}
-
-.glyphicon-remove:before {
-  content: "\e014";
-}
-
-.glyphicon-zoom-in:before {
-  content: "\e015";
-}
-
-.glyphicon-zoom-out:before {
-  content: "\e016";
-}
-
-.glyphicon-off:before {
-  content: "\e017";
-}
-
-.glyphicon-signal:before {
-  content: "\e018";
-}
-
-.glyphicon-cog:before {
-  content: "\e019";
-}
-
-.glyphicon-trash:before {
-  content: "\e020";
-}
-
-.glyphicon-home:before {
-  content: "\e021";
-}
-
-.glyphicon-file:before {
-  content: "\e022";
-}
-
-.glyphicon-time:before {
-  content: "\e023";
-}
-
-.glyphicon-road:before {
-  content: "\e024";
-}
-
-.glyphicon-download-alt:before {
-  content: "\e025";
-}
-
-.glyphicon-download:before {
-  content: "\e026";
-}
-
-.glyphicon-upload:before {
-  content: "\e027";
-}
-
-.glyphicon-inbox:before {
-  content: "\e028";
-}
-
-.glyphicon-play-circle:before {
-  content: "\e029";
-}
-
-.glyphicon-repeat:before {
-  content: "\e030";
-}
-
-.glyphicon-arrows-cw:before {
-  content: "\e031";
-}
-
-.glyphicon-list-alt:before {
-  content: "\e032";
-}
-
-.glyphicon-flag:before {
-  content: "\e034";
-}
-
-.glyphicon-headphones:before {
-  content: "\e035";
-}
-
-.glyphicon-volume-off:before {
-  content: "\e036";
-}
-
-.glyphicon-volume-down:before {
-  content: "\e037";
-}
-
-.glyphicon-volume-up:before {
-  content: "\e038";
-}
-
-.glyphicon-qrcode:before {
-  content: "\e039";
-}
-
-.glyphicon-barcode:before {
-  content: "\e040";
-}
-
-.glyphicon-tag:before {
-  content: "\e041";
-}
-
-.glyphicon-tags:before {
-  content: "\e042";
-}
-
-.glyphicon-book:before {
-  content: "\e043";
-}
-
-.glyphicon-print:before {
-  content: "\e045";
-}
-
-.glyphicon-font:before {
-  content: "\e047";
-}
-
-.glyphicon-bold:before {
-  content: "\e048";
-}
-
-.glyphicon-italic:before {
-  content: "\e049";
-}
-
-.glyphicon-text-height:before {
-  content: "\e050";
-}
-
-.glyphicon-text-width:before {
-  content: "\e051";
-}
-
-.glyphicon-align-left:before {
-  content: "\e052";
-}
-
-.glyphicon-align-center:before {
-  content: "\e053";
-}
-
-.glyphicon-align-right:before {
-  content: "\e054";
-}
-
-.glyphicon-align-justify:before {
-  content: "\e055";
-}
-
-.glyphicon-list:before {
-  content: "\e056";
-}
-
-.glyphicon-indent-left:before {
-  content: "\e057";
-}
-
-.glyphicon-indent-right:before {
-  content: "\e058";
-}
-
-.glyphicon-facetime-video:before {
-  content: "\e059";
-}
-
-.glyphicon-picture:before {
-  content: "\e060";
-}
-
-.glyphicon-map-marker:before {
-  content: "\e062";
-}
-
-.glyphicon-adjust:before {
-  content: "\e063";
-}
-
-.glyphicon-tint:before {
-  content: "\e064";
-}
-
-.glyphicon-edit:before {
-  content: "\e065";
-}
-
-.glyphicon-share:before {
-  content: "\e066";
-}
-
-.glyphicon-check:before {
-  content: "\e067";
-}
-
-.glyphicon-move:before {
-  content: "\e068";
-}
-
-.glyphicon-step-backward:before {
-  content: "\e069";
-}
-
-.glyphicon-fast-backward:before {
-  content: "\e070";
-}
-
-.glyphicon-backward:before {
-  content: "\e071";
-}
-
-.glyphicon-play:before {
-  content: "\e072";
-}
-
-.glyphicon-pause:before {
-  content: "\e073";
-}
-
-.glyphicon-stop:before {
-  content: "\e074";
-}
-
-.glyphicon-forward:before {
-  content: "\e075";
-}
-
-.glyphicon-fast-forward:before {
-  content: "\e076";
-}
-
-.glyphicon-step-forward:before {
-  content: "\e077";
-}
-
-.glyphicon-eject:before {
-  content: "\e078";
-}
-
-.glyphicon-chevron-left:before {
-  content: "\e079";
-}
-
-.glyphicon-chevron-right:before {
-  content: "\e080";
-}
-
-.glyphicon-plus-sign:before {
-  content: "\e081";
-}
-
-.glyphicon-minus-sign:before {
-  content: "\e082";
-}
-
-.glyphicon-remove-sign:before {
-  content: "\e083";
-}
-
-.glyphicon-ok-sign:before {
-  content: "\e084";
-}
-
-.glyphicon-question-sign:before {
-  content: "\e085";
-}
-
-.glyphicon-info-sign:before {
-  content: "\e086";
-}
-
-.glyphicon-screenshot:before {
-  content: "\e087";
-}
-
-.glyphicon-remove-circle:before {
-  content: "\e088";
-}
-
-.glyphicon-ok-circle:before {
-  content: "\e089";
-}
-
-.glyphicon-ban-circle:before {
-  content: "\e090";
-}
-
-.glyphicon-arrow-left:before {
-  content: "\e091";
-}
-
-.glyphicon-arrow-right:before {
-  content: "\e092";
-}
-
-.glyphicon-arrow-up:before {
-  content: "\e093";
-}
-
-.glyphicon-arrow-down:before {
-  content: "\e094";
-}
-
-.glyphicon-share-alt:before {
-  content: "\e095";
-}
-
-.glyphicon-resize-full:before {
-  content: "\e096";
-}
-
-.glyphicon-resize-small:before {
-  content: "\e097";
-}
-
-.glyphicon-exclamation-sign:before {
-  content: "\e101";
-}
-
-.glyphicon-gift:before {
-  content: "\e102";
-}
-
-.glyphicon-leaf:before {
-  content: "\e103";
-}
-
-.glyphicon-eye-open:before {
-  content: "\e105";
-}
-
-.glyphicon-eye-close:before {
-  content: "\e106";
-}
-
-.glyphicon-warning-sign:before {
-  content: "\e107";
-}
-
-.glyphicon-plane:before {
-  content: "\e108";
-}
-
-.glyphicon-random:before {
-  content: "\e110";
-}
-
-.glyphicon-comment:before {
-  content: "\e111";
-}
-
-.glyphicon-magnet:before {
-  content: "\e112";
-}
-
-.glyphicon-chevron-up:before {
-  content: "\e113";
-}
-
-.glyphicon-chevron-down:before {
-  content: "\e114";
-}
-
-.glyphicon-retweet:before {
-  content: "\e115";
-}
-
-.glyphicon-shopping-cart:before {
-  content: "\e116";
-}
-
-.glyphicon-folder-close:before {
-  content: "\e117";
-}
-
-.glyphicon-folder-open:before {
-  content: "\e118";
-}
-
-.glyphicon-resize-vertical:before {
-  content: "\e119";
-}
-
-.glyphicon-resize-horizontal:before {
-  content: "\e120";
-}
-
-.glyphicon-hdd:before {
-  content: "\e121";
-}
-
-.glyphicon-bullhorn:before {
-  content: "\e122";
-}
-
-.glyphicon-certificate:before {
-  content: "\e124";
-}
-
-.glyphicon-thumbs-up:before {
-  content: "\e125";
-}
-
-.glyphicon-thumbs-down:before {
-  content: "\e126";
-}
-
-.glyphicon-hand-right:before {
-  content: "\e127";
-}
-
-.glyphicon-hand-left:before {
-  content: "\e128";
-}
-
-.glyphicon-hand-up:before {
-  content: "\e129";
-}
-
-.glyphicon-hand-down:before {
-  content: "\e130";
-}
-
-.glyphicon-circle-arrow-right:before {
-  content: "\e131";
-}
-
-.glyphicon-circle-arrow-left:before {
-  content: "\e132";
-}
-
-.glyphicon-circle-arrow-up:before {
-  content: "\e133";
-}
-
-.glyphicon-circle-arrow-down:before {
-  content: "\e134";
-}
-
-.glyphicon-globe:before {
-  content: "\e135";
-}
-
-.glyphicon-tasks:before {
-  content: "\e137";
-}
-
-.glyphicon-filter:before {
-  content: "\e138";
-}
-
-.glyphicon-fullscreen:before {
-  content: "\e140";
-}
-
-.glyphicon-dashboard:before {
-  content: "\e141";
-}
-
-.glyphicon-heart-empty:before {
-  content: "\e143";
-}
-
-.glyphicon-link:before {
-  content: "\e144";
-}
-
-.glyphicon-phone:before {
-  content: "\e145";
-}
-
-.glyphicon-usd:before {
-  content: "\e148";
-}
-
-.glyphicon-gbp:before {
-  content: "\e149";
-}
-
-.glyphicon-sort:before {
-  content: "\e150";
-}
-
-.glyphicon-sort-by-alphabet:before {
-  content: "\e151";
-}
-
-.glyphicon-sort-by-alphabet-alt:before {
-  content: "\e152";
-}
-
-.glyphicon-sort-by-order:before {
-  content: "\e153";
-}
-
-.glyphicon-sort-by-order-alt:before {
-  content: "\e154";
-}
-
-.glyphicon-sort-by-attributes:before {
-  content: "\e155";
-}
-
-.glyphicon-sort-by-attributes-alt:before {
-  content: "\e156";
-}
-
-.glyphicon-unchecked:before {
-  content: "\e157";
-}
-
-.glyphicon-expand:before {
-  content: "\e158";
-}
-
-.glyphicon-collapse-down:before {
-  content: "\e159";
-}
-
-.glyphicon-collapse-up:before {
-  content: "\e160";
-}
-
-.glyphicon-log-in:before {
-  content: "\e161";
-}
-
-.glyphicon-flash:before {
-  content: "\e162";
-}
-
-.glyphicon-log-out:before {
-  content: "\e163";
-}
-
-.glyphicon-new-window:before {
-  content: "\e164";
-}
-
-.glyphicon-record:before {
-  content: "\e165";
-}
-
-.glyphicon-save:before {
-  content: "\e166";
-}
-
-.glyphicon-open:before {
-  content: "\e167";
-}
-
-.glyphicon-saved:before {
-  content: "\e168";
-}
-
-.glyphicon-import:before {
-  content: "\e169";
-}
-
-.glyphicon-export:before {
-  content: "\e170";
-}
-
-.glyphicon-send:before {
-  content: "\e171";
-}
-
-.glyphicon-floppy-disk:before {
-  content: "\e172";
-}
-
-.glyphicon-floppy-saved:before {
-  content: "\e173";
-}
-
-.glyphicon-floppy-remove:before {
-  content: "\e174";
-}
-
-.glyphicon-floppy-save:before {
-  content: "\e175";
-}
-
-.glyphicon-floppy-open:before {
-  content: "\e176";
-}
-
-.glyphicon-credit-card:before {
-  content: "\e177";
-}
-
-.glyphicon-transfer:before {
-  content: "\e178";
-}
-
-.glyphicon-cutlery:before {
-  content: "\e179";
-}
-
-.glyphicon-header:before {
-  content: "\e180";
-}
-
-.glyphicon-compressed:before {
-  content: "\e181";
-}
-
-.glyphicon-earphone:before {
-  content: "\e182";
-}
-
-.glyphicon-phone-alt:before {
-  content: "\e183";
-}
-
-.glyphicon-tower:before {
-  content: "\e184";
-}
-
-.glyphicon-stats:before {
-  content: "\e185";
-}
-
-.glyphicon-sd-video:before {
-  content: "\e186";
-}
-
-.glyphicon-hd-video:before {
-  content: "\e187";
-}
-
-.glyphicon-subtitles:before {
-  content: "\e188";
-}
-
-.glyphicon-sound-stereo:before {
-  content: "\e189";
-}
-
-.glyphicon-sound-dolby:before {
-  content: "\e190";
-}
-
-.glyphicon-sound-5-1:before {
-  content: "\e191";
-}
-
-.glyphicon-sound-6-1:before {
-  content: "\e192";
-}
-
-.glyphicon-sound-7-1:before {
-  content: "\e193";
-}
-
-.glyphicon-copyright-mark:before {
-  content: "\e194";
-}
-
-.glyphicon-registration-mark:before {
-  content: "\e195";
-}
-
-.glyphicon-cloud-download:before {
-  content: "\e197";
-}
-
-.glyphicon-cloud-upload:before {
-  content: "\e198";
-}
-
-.glyphicon-tree-conifer:before {
-  content: "\e199";
-}
-
-.glyphicon-tree-deciduous:before {
-  content: "\e200";
-}
-
-.glyphicon-briefcase:before {
-  content: "\1f4bc";
-}
-
-.glyphicon-calendar:before {
-  content: "\1f4c5";
-}
-
-.glyphicon-pushpin:before {
-  content: "\1f4cc";
-}
-
-.glyphicon-paperclip:before {
-  content: "\1f4ce";
-}
-
-.glyphicon-camera:before {
-  content: "\1f4f7";
-}
-
-.glyphicon-lock:before {
-  content: "\1f512";
-}
-
-.glyphicon-bell:before {
-  content: "\1f514";
-}
-
-.glyphicon-bookmark:before {
-  content: "\1f516";
-}
-
-.glyphicon-fire:before {
-  content: "\1f525";
-}
-
-.glyphicon-wrench:before {
-  content: "\1f527";
-}
-
-.caret {
-  display: inline-block;
-  width: 0;
-  height: 0;
-  margin-left: 2px;
-  vertical-align: middle;
-  border-top: 4px solid #000000;
-  border-right: 4px solid transparent;
-  border-bottom: 0 dotted;
-  border-left: 4px solid transparent;
-  content: "";
-}
-
-.dropdown {
-  position: relative;
-}
-
-.dropdown-toggle:focus {
-  outline: 0;
-}
-
-.dropdown-menu {
-  position: absolute;
-  top: 100%;
-  left: 0;
-  z-index: 1000;
-  display: none;
-  float: left;
-  min-width: 160px;
-  padding: 5px 0;
-  margin: 2px 0 0;
-  font-size: 14px;
-  list-style: none;
-  background-color: #ffffff;
-  border: 1px solid #cccccc;
-  border: 1px solid rgba(0, 0, 0, 0.15);
-  border-radius: 4px;
-  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
-          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
-  background-clip: padding-box;
-}
-
-.dropdown-menu.pull-right {
-  right: 0;
-  left: auto;
-}
-
-.dropdown-menu .divider {
-  height: 1px;
-  margin: 9px 0;
-  overflow: hidden;
-  background-color: #e5e5e5;
-}
-
-.dropdown-menu > li > a {
-  display: block;
-  padding: 3px 20px;
-  clear: both;
-  font-weight: normal;
-  line-height: 1.428571429;
-  color: #333333;
-  white-space: nowrap;
-}
-
-.dropdown-menu > li > a:hover,
-.dropdown-menu > li > a:focus {
-  color: #ffffff;
-  text-decoration: none;
-  background-color: #428bca;
-}
-
-.dropdown-menu > .active > a,
-.dropdown-menu > .active > a:hover,
-.dropdown-menu > .active > a:focus {
-  color: #ffffff;
-  text-decoration: none;
-  background-color: #428bca;
-  outline: 0;
-}
-
-.dropdown-menu > .disabled > a,
-.dropdown-menu > .disabled > a:hover,
-.dropdown-menu > .disabled > a:focus {
-  color: #999999;
-}
-
-.dropdown-menu > .disabled > a:hover,
-.dropdown-menu > .disabled > a:focus {
-  text-decoration: none;
-  cursor: not-allowed;
-  background-color: transparent;
-  background-image: none;
-  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.open > .dropdown-menu {
-  display: block;
-}
-
-.open > a {
-  outline: 0;
-}
-
-.dropdown-header {
-  display: block;
-  padding: 3px 20px;
-  font-size: 12px;
-  line-height: 1.428571429;
-  color: #999999;
-}
-
-.dropdown-backdrop {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 990;
-}
-
-.pull-right > .dropdown-menu {
-  right: 0;
-  left: auto;
-}
-
-.dropup .caret,
-.navbar-fixed-bottom .dropdown .caret {
-  border-top: 0 dotted;
-  border-bottom: 4px solid #000000;
-  content: "";
-}
-
-.dropup .dropdown-menu,
-.navbar-fixed-bottom .dropdown .dropdown-menu {
-  top: auto;
-  bottom: 100%;
-  margin-bottom: 1px;
-}
-
-@media (min-width: 768px) {
-  .navbar-right .dropdown-menu {
-    right: 0;
-    left: auto;
-  }
-}
-
-.btn-default .caret {
-  border-top-color: #333333;
-}
-
-.btn-primary .caret,
-.btn-success .caret,
-.btn-warning .caret,
-.btn-danger .caret,
-.btn-info .caret {
-  border-top-color: #fff;
-}
-
-.dropup .btn-default .caret {
-  border-bottom-color: #333333;
-}
-
-.dropup .btn-primary .caret,
-.dropup .btn-success .caret,
-.dropup .btn-warning .caret,
-.dropup .btn-danger .caret,
-.dropup .btn-info .caret {
-  border-bottom-color: #fff;
-}
-
-.btn-group,
-.btn-group-vertical {
-  position: relative;
-  display: inline-block;
-  vertical-align: middle;
-}
-
-.btn-group > .btn,
-.btn-group-vertical > .btn {
-  position: relative;
-  float: left;
-}
-
-.btn-group > .btn:hover,
-.btn-group-vertical > .btn:hover,
-.btn-group > .btn:focus,
-.btn-group-vertical > .btn:focus,
-.btn-group > .btn:active,
-.btn-group-vertical > .btn:active,
-.btn-group > .btn.active,
-.btn-group-vertical > .btn.active {
-  z-index: 2;
-}
-
-.btn-group > .btn:focus,
-.btn-group-vertical > .btn:focus {
-  outline: none;
-}
-
-.btn-group .btn + .btn,
-.btn-group .btn + .btn-group,
-.btn-group .btn-group + .btn,
-.btn-group .btn-group + .btn-group {
-  margin-left: -1px;
-}
-
-.btn-toolbar:before,
-.btn-toolbar:after {
-  display: table;
-  content: " ";
-}
-
-.btn-toolbar:after {
-  clear: both;
-}
-
-.btn-toolbar:before,
-.btn-toolbar:after {
-  display: table;
-  content: " ";
-}
-
-.btn-toolbar:after {
-  clear: both;
-}
-
-.btn-toolbar .btn-group {
-  float: left;
-}
-
-.btn-toolbar > .btn + .btn,
-.btn-toolbar > .btn-group + .btn,
-.btn-toolbar > .btn + .btn-group,
-.btn-toolbar > .btn-group + .btn-group {
-  margin-left: 5px;
-}
-
-.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
-  border-radius: 0;
-}
-
-.btn-group > .btn:first-child {
-  margin-left: 0;
-}
-
-.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.btn-group > .btn:last-child:not(:first-child),
-.btn-group > .dropdown-toggle:not(:first-child) {
-  border-bottom-left-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.btn-group > .btn-group {
-  float: left;
-}
-
-.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
-  border-radius: 0;
-}
-
-.btn-group > .btn-group:first-child > .btn:last-child,
-.btn-group > .btn-group:first-child > .dropdown-toggle {
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.btn-group > .btn-group:last-child > .btn:first-child {
-  border-bottom-left-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.btn-group .dropdown-toggle:active,
-.btn-group.open .dropdown-toggle {
-  outline: 0;
-}
-
-.btn-group-xs > .btn {
-  padding: 5px 10px;
-  padding: 1px 5px;
-  font-size: 12px;
-  line-height: 1.5;
-  border-radius: 3px;
-}
-
-.btn-group-sm > .btn {
-  padding: 5px 10px;
-  font-size: 12px;
-  line-height: 1.5;
-  border-radius: 3px;
-}
-
-.btn-group-lg > .btn {
-  padding: 10px 16px;
-  font-size: 18px;
-  line-height: 1.33;
-  border-radius: 6px;
-}
-
-.btn-group > .btn + .dropdown-toggle {
-  padding-right: 8px;
-  padding-left: 8px;
-}
-
-.btn-group > .btn-lg + .dropdown-toggle {
-  padding-right: 12px;
-  padding-left: 12px;
-}
-
-.btn-group.open .dropdown-toggle {
-  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-          box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
-}
-
-.btn .caret {
-  margin-left: 0;
-}
-
-.btn-lg .caret {
-  border-width: 5px 5px 0;
-  border-bottom-width: 0;
-}
-
-.dropup .btn-lg .caret {
-  border-width: 0 5px 5px;
-}
-
-.btn-group-vertical > .btn,
-.btn-group-vertical > .btn-group {
-  display: block;
-  float: none;
-  width: 100%;
-  max-width: 100%;
-}
-
-.btn-group-vertical > .btn-group:before,
-.btn-group-vertical > .btn-group:after {
-  display: table;
-  content: " ";
-}
-
-.btn-group-vertical > .btn-group:after {
-  clear: both;
-}
-
-.btn-group-vertical > .btn-group:before,
-.btn-group-vertical > .btn-group:after {
-  display: table;
-  content: " ";
-}
-
-.btn-group-vertical > .btn-group:after {
-  clear: both;
-}
-
-.btn-group-vertical > .btn-group > .btn {
-  float: none;
-}
-
-.btn-group-vertical > .btn + .btn,
-.btn-group-vertical > .btn + .btn-group,
-.btn-group-vertical > .btn-group + .btn,
-.btn-group-vertical > .btn-group + .btn-group {
-  margin-top: -1px;
-  margin-left: 0;
-}
-
-.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
-  border-radius: 0;
-}
-
-.btn-group-vertical > .btn:first-child:not(:last-child) {
-  border-top-right-radius: 4px;
-  border-bottom-right-radius: 0;
-  border-bottom-left-radius: 0;
-}
-
-.btn-group-vertical > .btn:last-child:not(:first-child) {
-  border-top-right-radius: 0;
-  border-bottom-left-radius: 4px;
-  border-top-left-radius: 0;
-}
-
-.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
-  border-radius: 0;
-}
-
-.btn-group-vertical > .btn-group:first-child > .btn:last-child,
-.btn-group-vertical > .btn-group:first-child > .dropdown-toggle {
-  border-bottom-right-radius: 0;
-  border-bottom-left-radius: 0;
-}
-
-.btn-group-vertical > .btn-group:last-child > .btn:first-child {
-  border-top-right-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.btn-group-justified {
-  display: table;
-  width: 100%;
-  border-collapse: separate;
-  table-layout: fixed;
-}
-
-.btn-group-justified .btn {
-  display: table-cell;
-  float: none;
-  width: 1%;
-}
-
-[data-toggle="buttons"] > .btn > input[type="radio"],
-[data-toggle="buttons"] > .btn > input[type="checkbox"] {
-  display: none;
-}
-
-.input-group {
-  position: relative;
-  display: table;
-  border-collapse: separate;
-}
-
-.input-group.col {
-  float: none;
-  padding-right: 0;
-  padding-left: 0;
-}
-
-.input-group .form-control {
-  width: 100%;
-  margin-bottom: 0;
-}
-
-.input-group-lg > .form-control,
-.input-group-lg > .input-group-addon,
-.input-group-lg > .input-group-btn > .btn {
-  height: 45px;
-  padding: 10px 16px;
-  font-size: 18px;
-  line-height: 1.33;
-  border-radius: 6px;
-}
-
-select.input-group-lg > .form-control,
-select.input-group-lg > .input-group-addon,
-select.input-group-lg > .input-group-btn > .btn {
-  height: 45px;
-  line-height: 45px;
-}
-
-textarea.input-group-lg > .form-control,
-textarea.input-group-lg > .input-group-addon,
-textarea.input-group-lg > .input-group-btn > .btn {
-  height: auto;
-}
-
-.input-group-sm > .form-control,
-.input-group-sm > .input-group-addon,
-.input-group-sm > .input-group-btn > .btn {
-  height: 30px;
-  padding: 5px 10px;
-  font-size: 12px;
-  line-height: 1.5;
-  border-radius: 3px;
-}
-
-select.input-group-sm > .form-control,
-select.input-group-sm > .input-group-addon,
-select.input-group-sm > .input-group-btn > .btn {
-  height: 30px;
-  line-height: 30px;
-}
-
-textarea.input-group-sm > .form-control,
-textarea.input-group-sm > .input-group-addon,
-textarea.input-group-sm > .input-group-btn > .btn {
-  height: auto;
-}
-
-.input-group-addon,
-.input-group-btn,
-.input-group .form-control {
-  display: table-cell;
-}
-
-.input-group-addon:not(:first-child):not(:last-child),
-.input-group-btn:not(:first-child):not(:last-child),
-.input-group .form-control:not(:first-child):not(:last-child) {
-  border-radius: 0;
-}
-
-.input-group-addon,
-.input-group-btn {
-  width: 1%;
-  white-space: nowrap;
-  vertical-align: middle;
-}
-
-.input-group-addon {
-  padding: 6px 12px;
-  font-size: 14px;
-  font-weight: normal;
-  line-height: 1;
-  text-align: center;
-  background-color: #eeeeee;
-  border: 1px solid #cccccc;
-  border-radius: 4px;
-}
-
-.input-group-addon.input-sm {
-  padding: 5px 10px;
-  font-size: 12px;
-  border-radius: 3px;
-}
-
-.input-group-addon.input-lg {
-  padding: 10px 16px;
-  font-size: 18px;
-  border-radius: 6px;
-}
-
-.input-group-addon input[type="radio"],
-.input-group-addon input[type="checkbox"] {
-  margin-top: 0;
-}
-
-.input-group .form-control:first-child,
-.input-group-addon:first-child,
-.input-group-btn:first-child > .btn,
-.input-group-btn:first-child > .dropdown-toggle,
-.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.input-group-addon:first-child {
-  border-right: 0;
-}
-
-.input-group .form-control:last-child,
-.input-group-addon:last-child,
-.input-group-btn:last-child > .btn,
-.input-group-btn:last-child > .dropdown-toggle,
-.input-group-btn:first-child > .btn:not(:first-child) {
-  border-bottom-left-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.input-group-addon:last-child {
-  border-left: 0;
-}
-
-.input-group-btn {
-  position: relative;
-  white-space: nowrap;
-}
-
-.input-group-btn > .btn {
-  position: relative;
-}
-
-.input-group-btn > .btn + .btn {
-  margin-left: -4px;
-}
-
-.input-group-btn > .btn:hover,
-.input-group-btn > .btn:active {
-  z-index: 2;
-}
-
-.nav {
-  padding-left: 0;
-  margin-bottom: 0;
-  list-style: none;
-}
-
-.nav:before,
-.nav:after {
-  display: table;
-  content: " ";
-}
-
-.nav:after {
-  clear: both;
-}
-
-.nav:before,
-.nav:after {
-  display: table;
-  content: " ";
-}
-
-.nav:after {
-  clear: both;
-}
-
-.nav > li {
-  position: relative;
-  display: block;
-}
-
-.nav > li > a {
-  position: relative;
-  display: block;
-  padding: 10px 15px;
-}
-
-.nav > li > a:hover,
-.nav > li > a:focus {
-  text-decoration: none;
-  background-color: #eeeeee;
-}
-
-.nav > li.disabled > a {
-  color: #999999;
-}
-
-.nav > li.disabled > a:hover,
-.nav > li.disabled > a:focus {
-  color: #999999;
-  text-decoration: none;
-  cursor: not-allowed;
-  background-color: transparent;
-}
-
-.nav .open > a,
-.nav .open > a:hover,
-.nav .open > a:focus {
-  background-color: #eeeeee;
-  border-color: #428bca;
-}
-
-.nav .nav-divider {
-  height: 1px;
-  margin: 9px 0;
-  overflow: hidden;
-  background-color: #e5e5e5;
-}
-
-.nav > li > a > img {
-  max-width: none;
-}
-
-.nav-tabs {
-  border-bottom: 1px solid #dddddd;
-}
-
-.nav-tabs > li {
-  float: left;
-  margin-bottom: -1px;
-}
-
-.nav-tabs > li > a {
-  margin-right: 2px;
-  line-height: 1.428571429;
-  border: 1px solid transparent;
-  border-radius: 4px 4px 0 0;
-}
-
-.nav-tabs > li > a:hover {
-  border-color: #eeeeee #eeeeee #dddddd;
-}
-
-.nav-tabs > li.active > a,
-.nav-tabs > li.active > a:hover,
-.nav-tabs > li.active > a:focus {
-  color: #555555;
-  cursor: default;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-  border-bottom-color: transparent;
-}
-
-.nav-tabs.nav-justified {
-  width: 100%;
-  border-bottom: 0;
-}
-
-.nav-tabs.nav-justified > li {
-  float: none;
-}
-
-.nav-tabs.nav-justified > li > a {
-  text-align: center;
-}
-
-@media (min-width: 768px) {
-  .nav-tabs.nav-justified > li {
-    display: table-cell;
-    width: 1%;
-  }
-}
-
-.nav-tabs.nav-justified > li > a {
-  margin-right: 0;
-  border-bottom: 1px solid #dddddd;
-}
-
-.nav-tabs.nav-justified > .active > a {
-  border-bottom-color: #ffffff;
-}
-
-.nav-pills > li {
-  float: left;
-}
-
-.nav-pills > li > a {
-  border-radius: 5px;
-}
-
-.nav-pills > li + li {
-  margin-left: 2px;
-}
-
-.nav-pills > li.active > a,
-.nav-pills > li.active > a:hover,
-.nav-pills > li.active > a:focus {
-  color: #ffffff;
-  background-color: #428bca;
-}
-
-.nav-stacked > li {
-  float: none;
-}
-
-.nav-stacked > li + li {
-  margin-top: 2px;
-  margin-left: 0;
-}
-
-.nav-justified {
-  width: 100%;
-}
-
-.nav-justified > li {
-  float: none;
-}
-
-.nav-justified > li > a {
-  text-align: center;
-}
-
-@media (min-width: 768px) {
-  .nav-justified > li {
-    display: table-cell;
-    width: 1%;
-  }
-}
-
-.nav-tabs-justified {
-  border-bottom: 0;
-}
-
-.nav-tabs-justified > li > a {
-  margin-right: 0;
-  border-bottom: 1px solid #dddddd;
-}
-
-.nav-tabs-justified > .active > a {
-  border-bottom-color: #ffffff;
-}
-
-.tabbable:before,
-.tabbable:after {
-  display: table;
-  content: " ";
-}
-
-.tabbable:after {
-  clear: both;
-}
-
-.tabbable:before,
-.tabbable:after {
-  display: table;
-  content: " ";
-}
-
-.tabbable:after {
-  clear: both;
-}
-
-.tab-content > .tab-pane,
-.pill-content > .pill-pane {
-  display: none;
-}
-
-.tab-content > .active,
-.pill-content > .active {
-  display: block;
-}
-
-.nav .caret {
-  border-top-color: #428bca;
-  border-bottom-color: #428bca;
-}
-
-.nav a:hover .caret {
-  border-top-color: #2a6496;
-  border-bottom-color: #2a6496;
-}
-
-.nav-tabs .dropdown-menu {
-  margin-top: -1px;
-  border-top-right-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.navbar {
-  position: relative;
-  z-index: 1000;
-  min-height: 50px;
-  margin-bottom: 20px;
-  border: 1px solid transparent;
-}
-
-.navbar:before,
-.navbar:after {
-  display: table;
-  content: " ";
-}
-
-.navbar:after {
-  clear: both;
-}
-
-.navbar:before,
-.navbar:after {
-  display: table;
-  content: " ";
-}
-
-.navbar:after {
-  clear: both;
-}
-
-@media (min-width: 768px) {
-  .navbar {
-    border-radius: 4px;
-  }
-}
-
-.navbar-header:before,
-.navbar-header:after {
-  display: table;
-  content: " ";
-}
-
-.navbar-header:after {
-  clear: both;
-}
-
-.navbar-header:before,
-.navbar-header:after {
-  display: table;
-  content: " ";
-}
-
-.navbar-header:after {
-  clear: both;
-}
-
-@media (min-width: 768px) {
-  .navbar-header {
-    float: left;
-  }
-}
-
-.navbar-collapse {
-  max-height: 340px;
-  padding-right: 15px;
-  padding-left: 15px;
-  overflow-x: visible;
-  border-top: 1px solid transparent;
-  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
-  -webkit-overflow-scrolling: touch;
-}
-
-.navbar-collapse:before,
-.navbar-collapse:after {
-  display: table;
-  content: " ";
-}
-
-.navbar-collapse:after {
-  clear: both;
-}
-
-.navbar-collapse:before,
-.navbar-collapse:after {
-  display: table;
-  content: " ";
-}
-
-.navbar-collapse:after {
-  clear: both;
-}
-
-.navbar-collapse.in {
-  overflow-y: auto;
-}
-
-@media (min-width: 768px) {
-  .navbar-collapse {
-    width: auto;
-    border-top: 0;
-    box-shadow: none;
-  }
-  .navbar-collapse.collapse {
-    display: block !important;
-    height: auto !important;
-    padding-bottom: 0;
-    overflow: visible !important;
-  }
-  .navbar-collapse.in {
-    overflow-y: visible;
-  }
-  .navbar-collapse .navbar-nav.navbar-left:first-child {
-    margin-left: -15px;
-  }
-  .navbar-collapse .navbar-nav.navbar-right:last-child {
-    margin-right: -15px;
-  }
-  .navbar-collapse .navbar-text:last-child {
-    margin-right: 0;
-  }
-}
-
-.container > .navbar-header,
-.container > .navbar-collapse {
-  margin-right: -15px;
-  margin-left: -15px;
-}
-
-@media (min-width: 768px) {
-  .container > .navbar-header,
-  .container > .navbar-collapse {
-    margin-right: 0;
-    margin-left: 0;
-  }
-}
-
-.navbar-static-top {
-  border-width: 0 0 1px;
-}
-
-@media (min-width: 768px) {
-  .navbar-static-top {
-    border-radius: 0;
-  }
-}
-
-.navbar-fixed-top,
-.navbar-fixed-bottom {
-  position: fixed;
-  right: 0;
-  left: 0;
-  border-width: 0 0 1px;
-}
-
-@media (min-width: 768px) {
-  .navbar-fixed-top,
-  .navbar-fixed-bottom {
-    border-radius: 0;
-  }
-}
-
-.navbar-fixed-top {
-  top: 0;
-  z-index: 1030;
-}
-
-.navbar-fixed-bottom {
-  bottom: 0;
-  margin-bottom: 0;
-}
-
-.navbar-brand {
-  float: left;
-  padding: 15px 15px;
-  font-size: 18px;
-  line-height: 20px;
-}
-
-.navbar-brand:hover,
-.navbar-brand:focus {
-  text-decoration: none;
-}
-
-@media (min-width: 768px) {
-  .navbar > .container .navbar-brand {
-    margin-left: -15px;
-  }
-}
-
-.navbar-toggle {
-  position: relative;
-  float: right;
-  padding: 9px 10px;
-  margin-top: 8px;
-  margin-right: 15px;
-  margin-bottom: 8px;
-  background-color: transparent;
-  border: 1px solid transparent;
-  border-radius: 4px;
-}
-
-.navbar-toggle .icon-bar {
-  display: block;
-  width: 22px;
-  height: 2px;
-  border-radius: 1px;
-}
-
-.navbar-toggle .icon-bar + .icon-bar {
-  margin-top: 4px;
-}
-
-@media (min-width: 768px) {
-  .navbar-toggle {
-    display: none;
-  }
-}
-
-.navbar-nav {
-  margin: 7.5px -15px;
-}
-
-.navbar-nav > li > a {
-  padding-top: 10px;
-  padding-bottom: 10px;
-  line-height: 20px;
-}
-
-@media (max-width: 767px) {
-  .navbar-nav .open .dropdown-menu {
-    position: static;
-    float: none;
-    width: auto;
-    margin-top: 0;
-    background-color: transparent;
-    border: 0;
-    box-shadow: none;
-  }
-  .navbar-nav .open .dropdown-menu > li > a,
-  .navbar-nav .open .dropdown-menu .dropdown-header {
-    padding: 5px 15px 5px 25px;
-  }
-  .navbar-nav .open .dropdown-menu > li > a {
-    line-height: 20px;
-  }
-  .navbar-nav .open .dropdown-menu > li > a:hover,
-  .navbar-nav .open .dropdown-menu > li > a:focus {
-    background-image: none;
-  }
-}
-
-@media (min-width: 768px) {
-  .navbar-nav {
-    float: left;
-    margin: 0;
-  }
-  .navbar-nav > li {
-    float: left;
-  }
-  .navbar-nav > li > a {
-    padding-top: 15px;
-    padding-bottom: 15px;
-  }
-}
-
-@media (min-width: 768px) {
-  .navbar-left {
-    float: left !important;
-  }
-  .navbar-right {
-    float: right !important;
-  }
-}
-
-.navbar-form {
-  padding: 10px 15px;
-  margin-top: 8px;
-  margin-right: -15px;
-  margin-bottom: 8px;
-  margin-left: -15px;
-  border-top: 1px solid transparent;
-  border-bottom: 1px solid transparent;
-  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-}
-
-@media (min-width: 768px) {
-  .navbar-form .form-group {
-    display: inline-block;
-    margin-bottom: 0;
-    vertical-align: middle;
-  }
-  .navbar-form .form-control {
-    display: inline-block;
-  }
-  .navbar-form .radio,
-  .navbar-form .checkbox {
-    display: inline-block;
-    padding-left: 0;
-    margin-top: 0;
-    margin-bottom: 0;
-  }
-  .navbar-form .radio input[type="radio"],
-  .navbar-form .checkbox input[type="checkbox"] {
-    float: none;
-    margin-left: 0;
-  }
-}
-
-@media (max-width: 767px) {
-  .navbar-form .form-group {
-    margin-bottom: 5px;
-  }
-}
-
-@media (min-width: 768px) {
-  .navbar-form {
-    width: auto;
-    padding-top: 0;
-    padding-bottom: 0;
-    margin-right: 0;
-    margin-left: 0;
-    border: 0;
-    -webkit-box-shadow: none;
-            box-shadow: none;
-  }
-}
-
-.navbar-nav > li > .dropdown-menu {
-  margin-top: 0;
-  border-top-right-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
-  border-bottom-right-radius: 0;
-  border-bottom-left-radius: 0;
-}
-
-.navbar-nav.pull-right > li > .dropdown-menu,
-.navbar-nav > li > .dropdown-menu.pull-right {
-  right: 0;
-  left: auto;
-}
-
-.navbar-btn {
-  margin-top: 8px;
-  margin-bottom: 8px;
-}
-
-.navbar-text {
-  float: left;
-  margin-top: 15px;
-  margin-bottom: 15px;
-}
-
-@media (min-width: 768px) {
-  .navbar-text {
-    margin-right: 15px;
-    margin-left: 15px;
-  }
-}
-
-.navbar-default {
-  background-color: #f8f8f8;
-  border-color: #e7e7e7;
-}
-
-.navbar-default .navbar-brand {
-  color: #777777;
-}
-
-.navbar-default .navbar-brand:hover,
-.navbar-default .navbar-brand:focus {
-  color: #5e5e5e;
-  background-color: transparent;
-}
-
-.navbar-default .navbar-text {
-  color: #777777;
-}
-
-.navbar-default .navbar-nav > li > a {
-  color: #777777;
-}
-
-.navbar-default .navbar-nav > li > a:hover,
-.navbar-default .navbar-nav > li > a:focus {
-  color: #333333;
-  background-color: transparent;
-}
-
-.navbar-default .navbar-nav > .active > a,
-.navbar-default .navbar-nav > .active > a:hover,
-.navbar-default .navbar-nav > .active > a:focus {
-  color: #555555;
-  background-color: #e7e7e7;
-}
-
-.navbar-default .navbar-nav > .disabled > a,
-.navbar-default .navbar-nav > .disabled > a:hover,
-.navbar-default .navbar-nav > .disabled > a:focus {
-  color: #cccccc;
-  background-color: transparent;
-}
-
-.navbar-default .navbar-toggle {
-  border-color: #dddddd;
-}
-
-.navbar-default .navbar-toggle:hover,
-.navbar-default .navbar-toggle:focus {
-  background-color: #dddddd;
-}
-
-.navbar-default .navbar-toggle .icon-bar {
-  background-color: #cccccc;
-}
-
-.navbar-default .navbar-collapse,
-.navbar-default .navbar-form {
-  border-color: #e6e6e6;
-}
-
-.navbar-default .navbar-nav > .dropdown > a:hover .caret,
-.navbar-default .navbar-nav > .dropdown > a:focus .caret {
-  border-top-color: #333333;
-  border-bottom-color: #333333;
-}
-
-.navbar-default .navbar-nav > .open > a,
-.navbar-default .navbar-nav > .open > a:hover,
-.navbar-default .navbar-nav > .open > a:focus {
-  color: #555555;
-  background-color: #e7e7e7;
-}
-
-.navbar-default .navbar-nav > .open > a .caret,
-.navbar-default .navbar-nav > .open > a:hover .caret,
-.navbar-default .navbar-nav > .open > a:focus .caret {
-  border-top-color: #555555;
-  border-bottom-color: #555555;
-}
-
-.navbar-default .navbar-nav > .dropdown > a .caret {
-  border-top-color: #777777;
-  border-bottom-color: #777777;
-}
-
-@media (max-width: 767px) {
-  .navbar-default .navbar-nav .open .dropdown-menu > li > a {
-    color: #777777;
-  }
-  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
-  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
-    color: #333333;
-    background-color: transparent;
-  }
-  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
-  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
-  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
-    color: #555555;
-    background-color: #e7e7e7;
-  }
-  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
-  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
-  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
-    color: #cccccc;
-    background-color: transparent;
-  }
-}
-
-.navbar-default .navbar-link {
-  color: #777777;
-}
-
-.navbar-default .navbar-link:hover {
-  color: #333333;
-}
-
-.navbar-inverse {
-  background-color: #222222;
-  border-color: #080808;
-}
-
-.navbar-inverse .navbar-brand {
-  color: #999999;
-}
-
-.navbar-inverse .navbar-brand:hover,
-.navbar-inverse .navbar-brand:focus {
-  color: #ffffff;
-  background-color: transparent;
-}
-
-.navbar-inverse .navbar-text {
-  color: #999999;
-}
-
-.navbar-inverse .navbar-nav > li > a {
-  color: #999999;
-}
-
-.navbar-inverse .navbar-nav > li > a:hover,
-.navbar-inverse .navbar-nav > li > a:focus {
-  color: #ffffff;
-  background-color: transparent;
-}
-
-.navbar-inverse .navbar-nav > .active > a,
-.navbar-inverse .navbar-nav > .active > a:hover,
-.navbar-inverse .navbar-nav > .active > a:focus {
-  color: #ffffff;
-  background-color: #080808;
-}
-
-.navbar-inverse .navbar-nav > .disabled > a,
-.navbar-inverse .navbar-nav > .disabled > a:hover,
-.navbar-inverse .navbar-nav > .disabled > a:focus {
-  color: #444444;
-  background-color: transparent;
-}
-
-.navbar-inverse .navbar-toggle {
-  border-color: #333333;
-}
-
-.navbar-inverse .navbar-toggle:hover,
-.navbar-inverse .navbar-toggle:focus {
-  background-color: #333333;
-}
-
-.navbar-inverse .navbar-toggle .icon-bar {
-  background-color: #ffffff;
-}
-
-.navbar-inverse .navbar-collapse,
-.navbar-inverse .navbar-form {
-  border-color: #101010;
-}
-
-.navbar-inverse .navbar-nav > .open > a,
-.navbar-inverse .navbar-nav > .open > a:hover,
-.navbar-inverse .navbar-nav > .open > a:focus {
-  color: #ffffff;
-  background-color: #080808;
-}
-
-.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
-  border-top-color: #ffffff;
-  border-bottom-color: #ffffff;
-}
-
-.navbar-inverse .navbar-nav > .dropdown > a .caret {
-  border-top-color: #999999;
-  border-bottom-color: #999999;
-}
-
-.navbar-inverse .navbar-nav > .open > a .caret,
-.navbar-inverse .navbar-nav > .open > a:hover .caret,
-.navbar-inverse .navbar-nav > .open > a:focus .caret {
-  border-top-color: #ffffff;
-  border-bottom-color: #ffffff;
-}
-
-@media (max-width: 767px) {
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
-    border-color: #080808;
-  }
-  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
-    color: #999999;
-  }
-  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
-  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
-    color: #ffffff;
-    background-color: transparent;
-  }
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
-    color: #ffffff;
-    background-color: #080808;
-  }
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
-  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
-    color: #444444;
-    background-color: transparent;
-  }
-}
-
-.navbar-inverse .navbar-link {
-  color: #999999;
-}
-
-.navbar-inverse .navbar-link:hover {
-  color: #ffffff;
-}
-
-.breadcrumb {
-  padding: 8px 15px;
-  margin-bottom: 20px;
-  list-style: none;
-  background-color: #f5f5f5;
-  border-radius: 4px;
-}
-
-.breadcrumb > li {
-  display: inline-block;
-}
-
-.breadcrumb > li + li:before {
-  padding: 0 5px;
-  color: #cccccc;
-  content: "/\00a0";
-}
-
-.breadcrumb > .active {
-  color: #999999;
-}
-
-.pagination {
-  display: inline-block;
-  padding-left: 0;
-  margin: 20px 0;
-  border-radius: 4px;
-}
-
-.pagination > li {
-  display: inline;
-}
-
-.pagination > li > a,
-.pagination > li > span {
-  position: relative;
-  float: left;
-  padding: 6px 12px;
-  margin-left: -1px;
-  line-height: 1.428571429;
-  text-decoration: none;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-}
-
-.pagination > li:first-child > a,
-.pagination > li:first-child > span {
-  margin-left: 0;
-  border-bottom-left-radius: 4px;
-  border-top-left-radius: 4px;
-}
-
-.pagination > li:last-child > a,
-.pagination > li:last-child > span {
-  border-top-right-radius: 4px;
-  border-bottom-right-radius: 4px;
-}
-
-.pagination > li > a:hover,
-.pagination > li > span:hover,
-.pagination > li > a:focus,
-.pagination > li > span:focus {
-  background-color: #eeeeee;
-}
-
-.pagination > .active > a,
-.pagination > .active > span,
-.pagination > .active > a:hover,
-.pagination > .active > span:hover,
-.pagination > .active > a:focus,
-.pagination > .active > span:focus {
-  z-index: 2;
-  color: #ffffff;
-  cursor: default;
-  background-color: #428bca;
-  border-color: #428bca;
-}
-
-.pagination > .disabled > span,
-.pagination > .disabled > a,
-.pagination > .disabled > a:hover,
-.pagination > .disabled > a:focus {
-  color: #999999;
-  cursor: not-allowed;
-  background-color: #ffffff;
-  border-color: #dddddd;
-}
-
-.pagination-lg > li > a,
-.pagination-lg > li > span {
-  padding: 10px 16px;
-  font-size: 18px;
-}
-
-.pagination-lg > li:first-child > a,
-.pagination-lg > li:first-child > span {
-  border-bottom-left-radius: 6px;
-  border-top-left-radius: 6px;
-}
-
-.pagination-lg > li:last-child > a,
-.pagination-lg > li:last-child > span {
-  border-top-right-radius: 6px;
-  border-bottom-right-radius: 6px;
-}
-
-.pagination-sm > li > a,
-.pagination-sm > li > span {
-  padding: 5px 10px;
-  font-size: 12px;
-}
-
-.pagination-sm > li:first-child > a,
-.pagination-sm > li:first-child > span {
-  border-bottom-left-radius: 3px;
-  border-top-left-radius: 3px;
-}
-
-.pagination-sm > li:last-child > a,
-.pagination-sm > li:last-child > span {
-  border-top-right-radius: 3px;
-  border-bottom-right-radius: 3px;
-}
-
-.pager {
-  padding-left: 0;
-  margin: 20px 0;
-  text-align: center;
-  list-style: none;
-}
-
-.pager:before,
-.pager:after {
-  display: table;
-  content: " ";
-}
-
-.pager:after {
-  clear: both;
-}
-
-.pager:before,
-.pager:after {
-  display: table;
-  content: " ";
-}
-
-.pager:after {
-  clear: both;
-}
-
-.pager li {
-  display: inline;
-}
-
-.pager li > a,
-.pager li > span {
-  display: inline-block;
-  padding: 5px 14px;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-  border-radius: 15px;
-}
-
-.pager li > a:hover,
-.pager li > a:focus {
-  text-decoration: none;
-  background-color: #eeeeee;
-}
-
-.pager .next > a,
-.pager .next > span {
-  float: right;
-}
-
-.pager .previous > a,
-.pager .previous > span {
-  float: left;
-}
-
-.pager .disabled > a,
-.pager .disabled > a:hover,
-.pager .disabled > a:focus,
-.pager .disabled > span {
-  color: #999999;
-  cursor: not-allowed;
-  background-color: #ffffff;
-}
-
-.label {
-  display: inline;
-  padding: .2em .6em .3em;
-  font-size: 75%;
-  font-weight: bold;
-  line-height: 1;
-  color: #ffffff;
-  text-align: center;
-  white-space: nowrap;
-  vertical-align: baseline;
-  border-radius: .25em;
-}
-
-.label[href]:hover,
-.label[href]:focus {
-  color: #ffffff;
-  text-decoration: none;
-  cursor: pointer;
-}
-
-.label:empty {
-  display: none;
-}
-
-.label-default {
-  background-color: #999999;
-}
-
-.label-default[href]:hover,
-.label-default[href]:focus {
-  background-color: #808080;
-}
-
-.label-primary {
-  background-color: #428bca;
-}
-
-.label-primary[href]:hover,
-.label-primary[href]:focus {
-  background-color: #3071a9;
-}
-
-.label-success {
-  background-color: #5cb85c;
-}
-
-.label-success[href]:hover,
-.label-success[href]:focus {
-  background-color: #449d44;
-}
-
-.label-info {
-  background-color: #5bc0de;
-}
-
-.label-info[href]:hover,
-.label-info[href]:focus {
-  background-color: #31b0d5;
-}
-
-.label-warning {
-  background-color: #f0ad4e;
-}
-
-.label-warning[href]:hover,
-.label-warning[href]:focus {
-  background-color: #ec971f;
-}
-
-.label-danger {
-  background-color: #d9534f;
-}
-
-.label-danger[href]:hover,
-.label-danger[href]:focus {
-  background-color: #c9302c;
-}
-
-.badge {
-  display: inline-block;
-  min-width: 10px;
-  padding: 3px 7px;
-  font-size: 12px;
-  font-weight: bold;
-  line-height: 1;
-  color: #ffffff;
-  text-align: center;
-  white-space: nowrap;
-  vertical-align: baseline;
-  background-color: #999999;
-  border-radius: 10px;
-}
-
-.badge:empty {
-  display: none;
-}
-
-a.badge:hover,
-a.badge:focus {
-  color: #ffffff;
-  text-decoration: none;
-  cursor: pointer;
-}
-
-.btn .badge {
-  position: relative;
-  top: -1px;
-}
-
-a.list-group-item.active > .badge,
-.nav-pills > .active > a > .badge {
-  color: #428bca;
-  background-color: #ffffff;
-}
-
-.nav-pills > li > a > .badge {
-  margin-left: 3px;
-}
-
-.jumbotron {
-  padding: 30px;
-  margin-bottom: 30px;
-  font-size: 21px;
-  font-weight: 200;
-  line-height: 2.1428571435;
-  color: inherit;
-  background-color: #eeeeee;
-}
-
-.jumbotron h1 {
-  line-height: 1;
-  color: inherit;
-}
-
-.jumbotron p {
-  line-height: 1.4;
-}
-
-.container .jumbotron {
-  border-radius: 6px;
-}
-
-@media screen and (min-width: 768px) {
-  .jumbotron {
-    padding-top: 48px;
-    padding-bottom: 48px;
-  }
-  .container .jumbotron {
-    padding-right: 60px;
-    padding-left: 60px;
-  }
-  .jumbotron h1 {
-    font-size: 63px;
-  }
-}
-
-.thumbnail {
-  display: inline-block;
-  display: block;
-  height: auto;
-  max-width: 100%;
-  padding: 4px;
-  line-height: 1.428571429;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-  border-radius: 4px;
-  -webkit-transition: all 0.2s ease-in-out;
-          transition: all 0.2s ease-in-out;
-}
-
-.thumbnail > img {
-  display: block;
-  height: auto;
-  max-width: 100%;
-}
-
-a.thumbnail:hover,
-a.thumbnail:focus {
-  border-color: #428bca;
-}
-
-.thumbnail > img {
-  margin-right: auto;
-  margin-left: auto;
-}
-
-.thumbnail .caption {
-  padding: 9px;
-  color: #333333;
-}
-
-.alert {
-  padding: 15px;
-  margin-bottom: 20px;
-  border: 1px solid transparent;
-  border-radius: 4px;
-}
-
-.alert h4 {
-  margin-top: 0;
-  color: inherit;
-}
-
-.alert .alert-link {
-  font-weight: bold;
-}
-
-.alert > p,
-.alert > ul {
-  margin-bottom: 0;
-}
-
-.alert > p + p {
-  margin-top: 5px;
-}
-
-.alert-dismissable {
-  padding-right: 35px;
-}
-
-.alert-dismissable .close {
-  position: relative;
-  top: -2px;
-  right: -21px;
-  color: inherit;
-}
-
-.alert-success {
-  color: #468847;
-  background-color: #dff0d8;
-  border-color: #d6e9c6;
-}
-
-.alert-success hr {
-  border-top-color: #c9e2b3;
-}
-
-.alert-success .alert-link {
-  color: #356635;
-}
-
-.alert-info {
-  color: #3a87ad;
-  background-color: #d9edf7;
-  border-color: #bce8f1;
-}
-
-.alert-info hr {
-  border-top-color: #a6e1ec;
-}
-
-.alert-info .alert-link {
-  color: #2d6987;
-}
-
-.alert-warning {
-  color: #c09853;
-  background-color: #fcf8e3;
-  border-color: #fbeed5;
-}
-
-.alert-warning hr {
-  border-top-color: #f8e5be;
-}
-
-.alert-warning .alert-link {
-  color: #a47e3c;
-}
-
-.alert-danger {
-  color: #b94a48;
-  background-color: #f2dede;
-  border-color: #eed3d7;
-}
-
-.alert-danger hr {
-  border-top-color: #e6c1c7;
-}
-
-.alert-danger .alert-link {
-  color: #953b39;
-}
-
-@-webkit-keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-
-@-moz-keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-
-@-o-keyframes progress-bar-stripes {
-  from {
-    background-position: 0 0;
-  }
-  to {
-    background-position: 40px 0;
-  }
-}
-
-@keyframes progress-bar-stripes {
-  from {
-    background-position: 40px 0;
-  }
-  to {
-    background-position: 0 0;
-  }
-}
-
-.progress {
-  height: 20px;
-  margin-bottom: 20px;
-  overflow: hidden;
-  background-color: #f5f5f5;
-  border-radius: 4px;
-  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-}
-
-.progress-bar {
-  float: left;
-  width: 0;
-  height: 100%;
-  font-size: 12px;
-  color: #ffffff;
-  text-align: center;
-  background-color: #428bca;
-  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-  -webkit-transition: width 0.6s ease;
-          transition: width 0.6s ease;
-}
-
-.progress-striped .progress-bar {
-  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-size: 40px 40px;
-}
-
-.progress.active .progress-bar {
-  -webkit-animation: progress-bar-stripes 2s linear infinite;
-     -moz-animation: progress-bar-stripes 2s linear infinite;
-      -ms-animation: progress-bar-stripes 2s linear infinite;
-       -o-animation: progress-bar-stripes 2s linear infinite;
-          animation: progress-bar-stripes 2s linear infinite;
-}
-
-.progress-bar-success {
-  background-color: #5cb85c;
-}
-
-.progress-striped .progress-bar-success {
-  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-bar-info {
-  background-color: #5bc0de;
-}
-
-.progress-striped .progress-bar-info {
-  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-bar-warning {
-  background-color: #f0ad4e;
-}
-
-.progress-striped .progress-bar-warning {
-  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-bar-danger {
-  background-color: #d9534f;
-}
-
-.progress-striped .progress-bar-danger {
-  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.media,
-.media-body {
-  overflow: hidden;
-  zoom: 1;
-}
-
-.media,
-.media .media {
-  margin-top: 15px;
-}
-
-.media:first-child {
-  margin-top: 0;
-}
-
-.media-object {
-  display: block;
-}
-
-.media-heading {
-  margin: 0 0 5px;
-}
-
-.media > .pull-left {
-  margin-right: 10px;
-}
-
-.media > .pull-right {
-  margin-left: 10px;
-}
-
-.media-list {
-  padding-left: 0;
-  list-style: none;
-}
-
-.list-group {
-  padding-left: 0;
-  margin-bottom: 20px;
-}
-
-.list-group-item {
-  position: relative;
-  display: block;
-  padding: 10px 15px;
-  margin-bottom: -1px;
-  background-color: #ffffff;
-  border: 1px solid #dddddd;
-}
-
-.list-group-item:first-child {
-  border-top-right-radius: 4px;
-  border-top-left-radius: 4px;
-}
-
-.list-group-item:last-child {
-  margin-bottom: 0;
-  border-bottom-right-radius: 4px;
-  border-bottom-left-radius: 4px;
-}
-
-.list-group-item > .badge {
-  float: right;
-}
-
-.list-group-item > .badge + .badge {
-  margin-right: 5px;
-}
-
-a.list-group-item {
-  color: #555555;
-}
-
-a.list-group-item .list-group-item-heading {
-  color: #333333;
-}
-
-a.list-group-item:hover,
-a.list-group-item:focus {
-  text-decoration: none;
-  background-color: #f5f5f5;
-}
-
-.list-group-item.active,
-.list-group-item.active:hover,
-.list-group-item.active:focus {
-  z-index: 2;
-  color: #ffffff;
-  background-color: #428bca;
-  border-color: #428bca;
-}
-
-.list-group-item.active .list-group-item-heading,
-.list-group-item.active:hover .list-group-item-heading,
-.list-group-item.active:focus .list-group-item-heading {
-  color: inherit;
-}
-
-.list-group-item.active .list-group-item-text,
-.list-group-item.active:hover .list-group-item-text,
-.list-group-item.active:focus .list-group-item-text {
-  color: #e1edf7;
-}
-
-.list-group-item-heading {
-  margin-top: 0;
-  margin-bottom: 5px;
-}
-
-.list-group-item-text {
-  margin-bottom: 0;
-  line-height: 1.3;
-}
-
-.panel {
-  margin-bottom: 20px;
-  background-color: #ffffff;
-  border: 1px solid transparent;
-  border-radius: 4px;
-  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
-          box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
-}
-
-.panel-body {
-  padding: 15px;
-}
-
-.panel-body:before,
-.panel-body:after {
-  display: table;
-  content: " ";
-}
-
-.panel-body:after {
-  clear: both;
-}
-
-.panel-body:before,
-.panel-body:after {
-  display: table;
-  content: " ";
-}
-
-.panel-body:after {
-  clear: both;
-}
-
-.panel > .list-group {
-  margin-bottom: 0;
-}
-
-.panel > .list-group .list-group-item {
-  border-width: 1px 0;
-}
-
-.panel > .list-group .list-group-item:first-child {
-  border-top-right-radius: 0;
-  border-top-left-radius: 0;
-}
-
-.panel > .list-group .list-group-item:last-child {
-  border-bottom: 0;
-}
-
-.panel-heading + .list-group .list-group-item:first-child {
-  border-top-width: 0;
-}
-
-.panel > .table {
-  margin-bottom: 0;
-}
-
-.panel > .panel-body + .table {
-  border-top: 1px solid #dddddd;
-}
-
-.panel-heading {
-  padding: 10px 15px;
-  border-bottom: 1px solid transparent;
-  border-top-right-radius: 3px;
-  border-top-left-radius: 3px;
-}
-
-.panel-title {
-  margin-top: 0;
-  margin-bottom: 0;
-  font-size: 16px;
-}
-
-.panel-title > a {
-  color: inherit;
-}
-
-.panel-footer {
-  padding: 10px 15px;
-  background-color: #f5f5f5;
-  border-top: 1px solid #dddddd;
-  border-bottom-right-radius: 3px;
-  border-bottom-left-radius: 3px;
-}
-
-.panel-group .panel {
-  margin-bottom: 0;
-  overflow: hidden;
-  border-radius: 4px;
-}
-
-.panel-group .panel + .panel {
-  margin-top: 5px;
-}
-
-.panel-group .panel-heading {
-  border-bottom: 0;
-}
-
-.panel-group .panel-heading + .panel-collapse .panel-body {
-  border-top: 1px solid #dddddd;
-}
-
-.panel-group .panel-footer {
-  border-top: 0;
-}
-
-.panel-group .panel-footer + .panel-collapse .panel-body {
-  border-bottom: 1px solid #dddddd;
-}
-
-.panel-default {
-  border-color: #dddddd;
-}
-
-.panel-default > .panel-heading {
-  color: #333333;
-  background-color: #f5f5f5;
-  border-color: #dddddd;
-}
-
-.panel-default > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #dddddd;
-}
-
-.panel-default > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #dddddd;
-}
-
-.panel-primary {
-  border-color: #428bca;
-}
-
-.panel-primary > .panel-heading {
-  color: #ffffff;
-  background-color: #428bca;
-  border-color: #428bca;
-}
-
-.panel-primary > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #428bca;
-}
-
-.panel-primary > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #428bca;
-}
-
-.panel-success {
-  border-color: #d6e9c6;
-}
-
-.panel-success > .panel-heading {
-  color: #468847;
-  background-color: #dff0d8;
-  border-color: #d6e9c6;
-}
-
-.panel-success > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #d6e9c6;
-}
-
-.panel-success > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #d6e9c6;
-}
-
-.panel-warning {
-  border-color: #fbeed5;
-}
-
-.panel-warning > .panel-heading {
-  color: #c09853;
-  background-color: #fcf8e3;
-  border-color: #fbeed5;
-}
-
-.panel-warning > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #fbeed5;
-}
-
-.panel-warning > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #fbeed5;
-}
-
-.panel-danger {
-  border-color: #eed3d7;
-}
-
-.panel-danger > .panel-heading {
-  color: #b94a48;
-  background-color: #f2dede;
-  border-color: #eed3d7;
-}
-
-.panel-danger > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #eed3d7;
-}
-
-.panel-danger > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #eed3d7;
-}
-
-.panel-info {
-  border-color: #bce8f1;
-}
-
-.panel-info > .panel-heading {
-  color: #3a87ad;
-  background-color: #d9edf7;
-  border-color: #bce8f1;
-}
-
-.panel-info > .panel-heading + .panel-collapse .panel-body {
-  border-top-color: #bce8f1;
-}
-
-.panel-info > .panel-footer + .panel-collapse .panel-body {
-  border-bottom-color: #bce8f1;
-}
-
-.well {
-  min-height: 20px;
-  padding: 19px;
-  margin-bottom: 20px;
-  background-color: #f5f5f5;
-  border: 1px solid #e3e3e3;
-  border-radius: 4px;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-}
-
-.well blockquote {
-  border-color: #ddd;
-  border-color: rgba(0, 0, 0, 0.15);
-}
-
-.well-lg {
-  padding: 24px;
-  border-radius: 6px;
-}
-
-.well-sm {
-  padding: 9px;
-  border-radius: 3px;
-}
-
-.close {
-  float: right;
-  font-size: 21px;
-  font-weight: bold;
-  line-height: 1;
-  color: #000000;
-  text-shadow: 0 1px 0 #ffffff;
-  opacity: 0.2;
-  filter: alpha(opacity=20);
-}
-
-.close:hover,
-.close:focus {
-  color: #000000;
-  text-decoration: none;
-  cursor: pointer;
-  opacity: 0.5;
-  filter: alpha(opacity=50);
-}
-
-button.close {
-  padding: 0;
-  cursor: pointer;
-  background: transparent;
-  border: 0;
-  -webkit-appearance: none;
-}
-
-.modal-open {
-  overflow: hidden;
-}
-
-body.modal-open,
-.modal-open .navbar-fixed-top,
-.modal-open .navbar-fixed-bottom {
-  margin-right: 15px;
-}
-
-.modal {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 1040;
-  display: none;
-  overflow: auto;
-  overflow-y: scroll;
-}
-
-.modal.fade .modal-dialog {
-  -webkit-transform: translate(0, -25%);
-      -ms-transform: translate(0, -25%);
-          transform: translate(0, -25%);
-  -webkit-transition: -webkit-transform 0.3s ease-out;
-     -moz-transition: -moz-transform 0.3s ease-out;
-       -o-transition: -o-transform 0.3s ease-out;
-          transition: transform 0.3s ease-out;
-}
-
-.modal.in .modal-dialog {
-  -webkit-transform: translate(0, 0);
-      -ms-transform: translate(0, 0);
-          transform: translate(0, 0);
-}
-
-.modal-dialog {
-  z-index: 1050;
-  width: auto;
-  padding: 10px;
-  margin-right: auto;
-  margin-left: auto;
-}
-
-.modal-content {
-  position: relative;
-  background-color: #ffffff;
-  border: 1px solid #999999;
-  border: 1px solid rgba(0, 0, 0, 0.2);
-  border-radius: 6px;
-  outline: none;
-  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
-          box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
-  background-clip: padding-box;
-}
-
-.modal-backdrop {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 1030;
-  background-color: #000000;
-}
-
-.modal-backdrop.fade {
-  opacity: 0;
-  filter: alpha(opacity=0);
-}
-
-.modal-backdrop.in {
-  opacity: 0.5;
-  filter: alpha(opacity=50);
-}
-
-.modal-header {
-  min-height: 16.428571429px;
-  padding: 15px;
-  border-bottom: 1px solid #e5e5e5;
-}
-
-.modal-header .close {
-  margin-top: -2px;
-}
-
-.modal-title {
-  margin: 0;
-  line-height: 1.428571429;
-}
-
-.modal-body {
-  position: relative;
-  padding: 20px;
-}
-
-.modal-footer {
-  padding: 19px 20px 20px;
-  margin-top: 15px;
-  text-align: right;
-  border-top: 1px solid #e5e5e5;
-}
-
-.modal-footer:before,
-.modal-footer:after {
-  display: table;
-  content: " ";
-}
-
-.modal-footer:after {
-  clear: both;
-}
-
-.modal-footer:before,
-.modal-footer:after {
-  display: table;
-  content: " ";
-}
-
-.modal-footer:after {
-  clear: both;
-}
-
-.modal-footer .btn + .btn {
-  margin-bottom: 0;
-  margin-left: 5px;
-}
-
-.modal-footer .btn-group .btn + .btn {
-  margin-left: -1px;
-}
-
-.modal-footer .btn-block + .btn-block {
-  margin-left: 0;
-}
-
-@media screen and (min-width: 768px) {
-  .modal-dialog {
-    right: auto;
-    left: 50%;
-    width: 600px;
-    padding-top: 30px;
-    padding-bottom: 30px;
-  }
-  .modal-content {
-    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
-            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
-  }
-}
-
-.tooltip {
-  position: absolute;
-  z-index: 1030;
-  display: block;
-  font-size: 12px;
-  line-height: 1.4;
-  opacity: 0;
-  filter: alpha(opacity=0);
-  visibility: visible;
-}
-
-.tooltip.in {
-  opacity: 0.9;
-  filter: alpha(opacity=90);
-}
-
-.tooltip.top {
-  padding: 5px 0;
-  margin-top: -3px;
-}
-
-.tooltip.right {
-  padding: 0 5px;
-  margin-left: 3px;
-}
-
-.tooltip.bottom {
-  padding: 5px 0;
-  margin-top: 3px;
-}
-
-.tooltip.left {
-  padding: 0 5px;
-  margin-left: -3px;
-}
-
-.tooltip-inner {
-  max-width: 200px;
-  padding: 3px 8px;
-  color: #ffffff;
-  text-align: center;
-  text-decoration: none;
-  background-color: #000000;
-  border-radius: 4px;
-}
-
-.tooltip-arrow {
-  position: absolute;
-  width: 0;
-  height: 0;
-  border-color: transparent;
-  border-style: solid;
-}
-
-.tooltip.top .tooltip-arrow {
-  bottom: 0;
-  left: 50%;
-  margin-left: -5px;
-  border-top-color: #000000;
-  border-width: 5px 5px 0;
-}
-
-.tooltip.top-left .tooltip-arrow {
-  bottom: 0;
-  left: 5px;
-  border-top-color: #000000;
-  border-width: 5px 5px 0;
-}
-
-.tooltip.top-right .tooltip-arrow {
-  right: 5px;
-  bottom: 0;
-  border-top-color: #000000;
-  border-width: 5px 5px 0;
-}
-
-.tooltip.right .tooltip-arrow {
-  top: 50%;
-  left: 0;
-  margin-top: -5px;
-  border-right-color: #000000;
-  border-width: 5px 5px 5px 0;
-}
-
-.tooltip.left .tooltip-arrow {
-  top: 50%;
-  right: 0;
-  margin-top: -5px;
-  border-left-color: #000000;
-  border-width: 5px 0 5px 5px;
-}
-
-.tooltip.bottom .tooltip-arrow {
-  top: 0;
-  left: 50%;
-  margin-left: -5px;
-  border-bottom-color: #000000;
-  border-width: 0 5px 5px;
-}
-
-.tooltip.bottom-left .tooltip-arrow {
-  top: 0;
-  left: 5px;
-  border-bottom-color: #000000;
-  border-width: 0 5px 5px;
-}
-
-.tooltip.bottom-right .tooltip-arrow {
-  top: 0;
-  right: 5px;
-  border-bottom-color: #000000;
-  border-width: 0 5px 5px;
-}
-
-.popover {
-  position: absolute;
-  top: 0;
-  left: 0;
-  z-index: 1010;
-  display: none;
-  max-width: 276px;
-  padding: 1px;
-  text-align: left;
-  white-space: normal;
-  background-color: #ffffff;
-  border: 1px solid #cccccc;
-  border: 1px solid rgba(0, 0, 0, 0.2);
-  border-radius: 6px;
-  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-  background-clip: padding-box;
-}
-
-.popover.top {
-  margin-top: -10px;
-}
-
-.popover.right {
-  margin-left: 10px;
-}
-
-.popover.bottom {
-  margin-top: 10px;
-}
-
-.popover.left {
-  margin-left: -10px;
-}
-
-.popover-title {
-  padding: 8px 14px;
-  margin: 0;
-  font-size: 14px;
-  font-weight: normal;
-  line-height: 18px;
-  background-color: #f7f7f7;
-  border-bottom: 1px solid #ebebeb;
-  border-radius: 5px 5px 0 0;
-}
-
-.popover-content {
-  padding: 9px 14px;
-}
-
-.popover .arrow,
-.popover .arrow:after {
-  position: absolute;
-  display: block;
-  width: 0;
-  height: 0;
-  border-color: transparent;
-  border-style: solid;
-}
-
-.popover .arrow {
-  border-width: 11px;
-}
-
-.popover .arrow:after {
-  border-width: 10px;
-  content: "";
-}
-
-.popover.top .arrow {
-  bottom: -11px;
-  left: 50%;
-  margin-left: -11px;
-  border-top-color: #999999;
-  border-top-color: rgba(0, 0, 0, 0.25);
-  border-bottom-width: 0;
-}
-
-.popover.top .arrow:after {
-  bottom: 1px;
-  margin-left: -10px;
-  border-top-color: #ffffff;
-  border-bottom-width: 0;
-  content: " ";
-}
-
-.popover.right .arrow {
-  top: 50%;
-  left: -11px;
-  margin-top: -11px;
-  border-right-color: #999999;
-  border-right-color: rgba(0, 0, 0, 0.25);
-  border-left-width: 0;
-}
-
-.popover.right .arrow:after {
-  bottom: -10px;
-  left: 1px;
-  border-right-color: #ffffff;
-  border-left-width: 0;
-  content: " ";
-}
-
-.popover.bottom .arrow {
-  top: -11px;
-  left: 50%;
-  margin-left: -11px;
-  border-bottom-color: #999999;
-  border-bottom-color: rgba(0, 0, 0, 0.25);
-  border-top-width: 0;
-}
-
-.popover.bottom .arrow:after {
-  top: 1px;
-  margin-left: -10px;
-  border-bottom-color: #ffffff;
-  border-top-width: 0;
-  content: " ";
-}
-
-.popover.left .arrow {
-  top: 50%;
-  right: -11px;
-  margin-top: -11px;
-  border-left-color: #999999;
-  border-left-color: rgba(0, 0, 0, 0.25);
-  border-right-width: 0;
-}
-
-.popover.left .arrow:after {
-  right: 1px;
-  bottom: -10px;
-  border-left-color: #ffffff;
-  border-right-width: 0;
-  content: " ";
-}
-
-.carousel {
-  position: relative;
-}
-
-.carousel-inner {
-  position: relative;
-  width: 100%;
-  overflow: hidden;
-}
-
-.carousel-inner > .item {
-  position: relative;
-  display: none;
-  -webkit-transition: 0.6s ease-in-out left;
-          transition: 0.6s ease-in-out left;
-}
-
-.carousel-inner > .item > img,
-.carousel-inner > .item > a > img {
-  display: block;
-  height: auto;
-  max-width: 100%;
-  line-height: 1;
-}
-
-.carousel-inner > .active,
-.carousel-inner > .next,
-.carousel-inner > .prev {
-  display: block;
-}
-
-.carousel-inner > .active {
-  left: 0;
-}
-
-.carousel-inner > .next,
-.carousel-inner > .prev {
-  position: absolute;
-  top: 0;
-  width: 100%;
-}
-
-.carousel-inner > .next {
-  left: 100%;
-}
-
-.carousel-inner > .prev {
-  left: -100%;
-}
-
-.carousel-inner > .next.left,
-.carousel-inner > .prev.right {
-  left: 0;
-}
-
-.carousel-inner > .active.left {
-  left: -100%;
-}
-
-.carousel-inner > .active.right {
-  left: 100%;
-}
-
-.carousel-control {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  left: 0;
-  width: 15%;
-  font-size: 20px;
-  color: #ffffff;
-  text-align: center;
-  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-  opacity: 0.5;
-  filter: alpha(opacity=50);
-}
-
-.carousel-control.left {
-  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
-  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
-  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
-  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
-}
-
-.carousel-control.right {
-  right: 0;
-  left: auto;
-  background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
-  background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
-  background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
-  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
-  background-repeat: repeat-x;
-  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
-}
-
-.carousel-control:hover,
-.carousel-control:focus {
-  color: #ffffff;
-  text-decoration: none;
-  opacity: 0.9;
-  filter: alpha(opacity=90);
-}
-
-.carousel-control .icon-prev,
-.carousel-control .icon-next,
-.carousel-control .glyphicon-chevron-left,
-.carousel-control .glyphicon-chevron-right {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  z-index: 5;
-  display: inline-block;
-}
-
-.carousel-control .icon-prev,
-.carousel-control .icon-next {
-  width: 20px;
-  height: 20px;
-  margin-top: -10px;
-  margin-left: -10px;
-  font-family: serif;
-}
-
-.carousel-control .icon-prev:before {
-  content: '\2039';
-}
-
-.carousel-control .icon-next:before {
-  content: '\203a';
-}
-
-.carousel-indicators {
-  position: absolute;
-  bottom: 10px;
-  left: 50%;
-  z-index: 15;
-  width: 60%;
-  padding-left: 0;
-  margin-left: -30%;
-  text-align: center;
-  list-style: none;
-}
-
-.carousel-indicators li {
-  display: inline-block;
-  width: 10px;
-  height: 10px;
-  margin: 1px;
-  text-indent: -999px;
-  cursor: pointer;
-  border: 1px solid #ffffff;
-  border-radius: 10px;
-}
-
-.carousel-indicators .active {
-  width: 12px;
-  height: 12px;
-  margin: 0;
-  background-color: #ffffff;
-}
-
-.carousel-caption {
-  position: absolute;
-  right: 15%;
-  bottom: 20px;
-  left: 15%;
-  z-index: 10;
-  padding-top: 20px;
-  padding-bottom: 20px;
-  color: #ffffff;
-  text-align: center;
-  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-.carousel-caption .btn {
-  text-shadow: none;
-}
-
-@media screen and (min-width: 768px) {
-  .carousel-control .icon-prev,
-  .carousel-control .icon-next {
-    width: 30px;
-    height: 30px;
-    margin-top: -15px;
-    margin-left: -15px;
-    font-size: 30px;
-  }
-  .carousel-caption {
-    right: 20%;
-    left: 20%;
-    padding-bottom: 30px;
-  }
-  .carousel-indicators {
-    bottom: 20px;
-  }
-}
-
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: " ";
-}
-
-.clearfix:after {
-  clear: both;
-}
-
-.pull-right {
-  float: right !important;
-}
-
-.pull-left {
-  float: left !important;
-}
-
-.hide {
-  display: none !important;
-}
-
-.show {
-  display: block !important;
-}
-
-.invisible {
-  visibility: hidden;
-}
-
-.text-hide {
-  font: 0/0 a;
-  color: transparent;
-  text-shadow: none;
-  background-color: transparent;
-  border: 0;
-}
-
-.affix {
-  position: fixed;
-}
-
-@-ms-viewport {
-  width: device-width;
-}
-
-@media screen and (max-width: 400px) {
-  @-ms-viewport {
-    width: 320px;
-  }
-}
-
-.hidden {
-  display: none !important;
-  visibility: hidden !important;
-}
-
-.visible-xs {
-  display: none !important;
-}
-
-tr.visible-xs {
-  display: none !important;
-}
-
-th.visible-xs,
-td.visible-xs {
-  display: none !important;
-}
-
-@media (max-width: 767px) {
-  .visible-xs {
-    display: block !important;
-  }
-  tr.visible-xs {
-    display: table-row !important;
-  }
-  th.visible-xs,
-  td.visible-xs {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .visible-xs.visible-sm {
-    display: block !important;
-  }
-  tr.visible-xs.visible-sm {
-    display: table-row !important;
-  }
-  th.visible-xs.visible-sm,
-  td.visible-xs.visible-sm {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .visible-xs.visible-md {
-    display: block !important;
-  }
-  tr.visible-xs.visible-md {
-    display: table-row !important;
-  }
-  th.visible-xs.visible-md,
-  td.visible-xs.visible-md {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .visible-xs.visible-lg {
-    display: block !important;
-  }
-  tr.visible-xs.visible-lg {
-    display: table-row !important;
-  }
-  th.visible-xs.visible-lg,
-  td.visible-xs.visible-lg {
-    display: table-cell !important;
-  }
-}
-
-.visible-sm {
-  display: none !important;
-}
-
-tr.visible-sm {
-  display: none !important;
-}
-
-th.visible-sm,
-td.visible-sm {
-  display: none !important;
-}
-
-@media (max-width: 767px) {
-  .visible-sm.visible-xs {
-    display: block !important;
-  }
-  tr.visible-sm.visible-xs {
-    display: table-row !important;
-  }
-  th.visible-sm.visible-xs,
-  td.visible-sm.visible-xs {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .visible-sm {
-    display: block !important;
-  }
-  tr.visible-sm {
-    display: table-row !important;
-  }
-  th.visible-sm,
-  td.visible-sm {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .visible-sm.visible-md {
-    display: block !important;
-  }
-  tr.visible-sm.visible-md {
-    display: table-row !important;
-  }
-  th.visible-sm.visible-md,
-  td.visible-sm.visible-md {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .visible-sm.visible-lg {
-    display: block !important;
-  }
-  tr.visible-sm.visible-lg {
-    display: table-row !important;
-  }
-  th.visible-sm.visible-lg,
-  td.visible-sm.visible-lg {
-    display: table-cell !important;
-  }
-}
-
-.visible-md {
-  display: none !important;
-}
-
-tr.visible-md {
-  display: none !important;
-}
-
-th.visible-md,
-td.visible-md {
-  display: none !important;
-}
-
-@media (max-width: 767px) {
-  .visible-md.visible-xs {
-    display: block !important;
-  }
-  tr.visible-md.visible-xs {
-    display: table-row !important;
-  }
-  th.visible-md.visible-xs,
-  td.visible-md.visible-xs {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .visible-md.visible-sm {
-    display: block !important;
-  }
-  tr.visible-md.visible-sm {
-    display: table-row !important;
-  }
-  th.visible-md.visible-sm,
-  td.visible-md.visible-sm {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .visible-md {
-    display: block !important;
-  }
-  tr.visible-md {
-    display: table-row !important;
-  }
-  th.visible-md,
-  td.visible-md {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .visible-md.visible-lg {
-    display: block !important;
-  }
-  tr.visible-md.visible-lg {
-    display: table-row !important;
-  }
-  th.visible-md.visible-lg,
-  td.visible-md.visible-lg {
-    display: table-cell !important;
-  }
-}
-
-.visible-lg {
-  display: none !important;
-}
-
-tr.visible-lg {
-  display: none !important;
-}
-
-th.visible-lg,
-td.visible-lg {
-  display: none !important;
-}
-
-@media (max-width: 767px) {
-  .visible-lg.visible-xs {
-    display: block !important;
-  }
-  tr.visible-lg.visible-xs {
-    display: table-row !important;
-  }
-  th.visible-lg.visible-xs,
-  td.visible-lg.visible-xs {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .visible-lg.visible-sm {
-    display: block !important;
-  }
-  tr.visible-lg.visible-sm {
-    display: table-row !important;
-  }
-  th.visible-lg.visible-sm,
-  td.visible-lg.visible-sm {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .visible-lg.visible-md {
-    display: block !important;
-  }
-  tr.visible-lg.visible-md {
-    display: table-row !important;
-  }
-  th.visible-lg.visible-md,
-  td.visible-lg.visible-md {
-    display: table-cell !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .visible-lg {
-    display: block !important;
-  }
-  tr.visible-lg {
-    display: table-row !important;
-  }
-  th.visible-lg,
-  td.visible-lg {
-    display: table-cell !important;
-  }
-}
-
-.hidden-xs {
-  display: block !important;
-}
-
-tr.hidden-xs {
-  display: table-row !important;
-}
-
-th.hidden-xs,
-td.hidden-xs {
-  display: table-cell !important;
-}
-
-@media (max-width: 767px) {
-  .hidden-xs {
-    display: none !important;
-  }
-  tr.hidden-xs {
-    display: none !important;
-  }
-  th.hidden-xs,
-  td.hidden-xs {
-    display: none !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .hidden-xs.hidden-sm {
-    display: none !important;
-  }
-  tr.hidden-xs.hidden-sm {
-    display: none !important;
-  }
-  th.hidden-xs.hidden-sm,
-  td.hidden-xs.hidden-sm {
-    display: none !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .hidden-xs.hidden-md {
-    display: none !important;
-  }
-  tr.hidden-xs.hidden-md {
-    display: none !important;
-  }
-  th.hidden-xs.hidden-md,
-  td.hidden-xs.hidden-md {
-    display: none !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .hidden-xs.hidden-lg {
-    display: none !important;
-  }
-  tr.hidden-xs.hidden-lg {
-    display: none !important;
-  }
-  th.hidden-xs.hidden-lg,
-  td.hidden-xs.hidden-lg {
-    display: none !important;
-  }
-}
-
-.hidden-sm {
-  display: block !important;
-}
-
-tr.hidden-sm {
-  display: table-row !important;
-}
-
-th.hidden-sm,
-td.hidden-sm {
-  display: table-cell !important;
-}
-
-@media (max-width: 767px) {
-  .hidden-sm.hidden-xs {
-    display: none !important;
-  }
-  tr.hidden-sm.hidden-xs {
-    display: none !important;
-  }
-  th.hidden-sm.hidden-xs,
-  td.hidden-sm.hidden-xs {
-    display: none !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .hidden-sm {
-    display: none !important;
-  }
-  tr.hidden-sm {
-    display: none !important;
-  }
-  th.hidden-sm,
-  td.hidden-sm {
-    display: none !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .hidden-sm.hidden-md {
-    display: none !important;
-  }
-  tr.hidden-sm.hidden-md {
-    display: none !important;
-  }
-  th.hidden-sm.hidden-md,
-  td.hidden-sm.hidden-md {
-    display: none !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .hidden-sm.hidden-lg {
-    display: none !important;
-  }
-  tr.hidden-sm.hidden-lg {
-    display: none !important;
-  }
-  th.hidden-sm.hidden-lg,
-  td.hidden-sm.hidden-lg {
-    display: none !important;
-  }
-}
-
-.hidden-md {
-  display: block !important;
-}
-
-tr.hidden-md {
-  display: table-row !important;
-}
-
-th.hidden-md,
-td.hidden-md {
-  display: table-cell !important;
-}
-
-@media (max-width: 767px) {
-  .hidden-md.hidden-xs {
-    display: none !important;
-  }
-  tr.hidden-md.hidden-xs {
-    display: none !important;
-  }
-  th.hidden-md.hidden-xs,
-  td.hidden-md.hidden-xs {
-    display: none !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .hidden-md.hidden-sm {
-    display: none !important;
-  }
-  tr.hidden-md.hidden-sm {
-    display: none !important;
-  }
-  th.hidden-md.hidden-sm,
-  td.hidden-md.hidden-sm {
-    display: none !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .hidden-md {
-    display: none !important;
-  }
-  tr.hidden-md {
-    display: none !important;
-  }
-  th.hidden-md,
-  td.hidden-md {
-    display: none !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .hidden-md.hidden-lg {
-    display: none !important;
-  }
-  tr.hidden-md.hidden-lg {
-    display: none !important;
-  }
-  th.hidden-md.hidden-lg,
-  td.hidden-md.hidden-lg {
-    display: none !important;
-  }
-}
-
-.hidden-lg {
-  display: block !important;
-}
-
-tr.hidden-lg {
-  display: table-row !important;
-}
-
-th.hidden-lg,
-td.hidden-lg {
-  display: table-cell !important;
-}
-
-@media (max-width: 767px) {
-  .hidden-lg.hidden-xs {
-    display: none !important;
-  }
-  tr.hidden-lg.hidden-xs {
-    display: none !important;
-  }
-  th.hidden-lg.hidden-xs,
-  td.hidden-lg.hidden-xs {
-    display: none !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .hidden-lg.hidden-sm {
-    display: none !important;
-  }
-  tr.hidden-lg.hidden-sm {
-    display: none !important;
-  }
-  th.hidden-lg.hidden-sm,
-  td.hidden-lg.hidden-sm {
-    display: none !important;
-  }
-}
-
-@media (min-width: 992px) and (max-width: 1199px) {
-  .hidden-lg.hidden-md {
-    display: none !important;
-  }
-  tr.hidden-lg.hidden-md {
-    display: none !important;
-  }
-  th.hidden-lg.hidden-md,
-  td.hidden-lg.hidden-md {
-    display: none !important;
-  }
-}
-
-@media (min-width: 1200px) {
-  .hidden-lg {
-    display: none !important;
-  }
-  tr.hidden-lg {
-    display: none !important;
-  }
-  th.hidden-lg,
-  td.hidden-lg {
-    display: none !important;
-  }
-}
-
-.visible-print {
-  display: none !important;
-}
-
-tr.visible-print {
-  display: none !important;
-}
-
-th.visible-print,
-td.visible-print {
-  display: none !important;
-}
-
-@media print {
-  .visible-print {
-    display: block !important;
-  }
-  tr.visible-print {
-    display: table-row !important;
-  }
-  th.visible-print,
-  td.visible-print {
-    display: table-cell !important;
-  }
-  .hidden-print {
-    display: none !important;
-  }
-  tr.hidden-print {
-    display: none !important;
-  }
-  th.hidden-print,
-  td.hidden-print {
-    display: none !important;
-  }
-}
--- a/kallithea/public/css/contextbar.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-/**
- * Stylesheets for the context bar
- */
-
-i[class^='icon-'] {
-    background-repeat: no-repeat;
-    background-position: center;
-    display: inline-block;
-    min-width: 16px;
-    min-height: 16px;
-    margin: -2px 0 -4px 0;
-    /* background-color: red; /* for debugging */
-}
-
-/* css classes for diff file status ... it'd be nice if css had a way to
-   inherit from another class but alas, we must make sure this content is the
-   same from the icon font file */
-
-.icon-diff-M:before {
-    font-family: 'kallithea';
-    content: '\22a1';
-    color: #d0b44c;
-}
-
-.icon-diff-D:before {
-    font-family: 'kallithea';
-    content: '\229f';
-    color: #bd2c00;
-}
-
-.icon-diff-A:before {
-    font-family: 'kallithea';
-    content: '\229e';
-    color: #6cc644;
-}
-
-.icon-diff-R:before {
-    font-family: 'kallithea';
-    content: '\e81f';
-    color: #677a85;
-}
-
-#content #context-bar {
-    position: relative;
-    overflow: visible;
-    background-color: #577632;
-    padding: 0 5px;
-    min-height: 36px;
-}
-
-#content #context-bar h2 {
-    display: inline-block;
-    color: #FFF;
-}
-
-#header #header-inner #quick a,
-#content #context-bar,
-#content #context-bar a {
-    color: #FFFFFF;
-}
-
-#header #header-inner #quick a:hover,
-#content #context-bar a:hover {
-    text-decoration: none;
-}
-
-#content #context-bar .icon {
-    display: inline-block;
-    width: 16px;
-    height: 16px;
-    vertical-align: text-bottom;
-}
-
-ul.horizontal-list {
-    display: block;
-}
-
-ul.horizontal-list > li {
-    float: left;
-    position: relative;
-}
-
-#header #header-inner #quick ul,
-ul.horizontal-list > li ul {
-    position: absolute;
-    display: none;
-    right: 0;
-    z-index: 999;
-}
-
-#header #header-inner #quick li:hover > ul,
-ul.horizontal-list li:hover > ul {
-    display: block;
-}
-
-#header #header-inner #quick li ul li,
-ul.horizontal-list ul li {
-    position: relative;
-    border-bottom: 1px solid rgba(0,0,0,0.1);
-    border-top: 1px solid rgba(255,255,255,0.1);
-}
-
-ul.horizontal-list > li ul ul {
-    position: absolute;
-    right: 100%;
-    top: -1px;
-    min-width: 200px;
-    max-height: 400px;
-    overflow-x: hidden;
-    overflow-y: auto;
-}
-
-#header #header-inner #quick ul a,
-ul.horizontal-list li a {
-    white-space: nowrap;
-}
-
-#breadcrumbs {
-    float: left;
-    padding: 6px 0 5px 0;
-    padding-left: 5px;
-    font-weight: bold;
-    font-size: 14px;
-}
-
-#breadcrumbs span {
-    font-weight: bold;
-    font-size: 1.4em;
-}
-
-#header #header-inner #quick ul,
-#revision-changer,
-#context-pages,
-#context-pages ul {
-    background: #3b6998; /* Old browsers */
-    background: linear-gradient(to bottom, #577632 0%, #577632 100%); /* W3C */
-}
-
-#header #header-inner #quick a,
-#context-actions a,
-#context-pages a {
-    background-repeat: no-repeat;
-    background-position: 10px 50%;
-    padding-left: 30px;
-}
-
-#quick a,
-#context-pages ul ul a {
-    padding-left: 10px;
-}
-
-ul#context-actions {
-    display: inline-block;
-    float: right;
-    border-radius: 4px;
-    background-image: linear-gradient(to bottom, #577632 0%, #577632 100%);
-}
-
-#content ul#context-actions li {
-    padding: 0px;
-    border-right: 1px solid rgba(0,0,0,0.1);
-    border-left: 1px solid rgba(255,255,255,0.1);
-}
-
-#context-actions a {
-    display: block;
-    cursor: pointer;
-    background: none;
-    border: none;
-    margin: 0px;
-    height: auto;
-    padding: 10px 10px 10px 30px;
-    background-repeat: no-repeat;
-    background-position: 10px 50%;
-    font-size: 1em;
-}
-
-#context-actions a {
-    padding: 11px 10px 12px 30px;
-}
-
-#header #header-inner #quick li:hover,
-#revision-changer:hover,
-#context-pages li:hover,
-#context-actions li:hover,
-#content #context-actions li:hover,
-#header #header-inner #quick li.current,
-#context-pages li.current {
-    background: #6388ad; /* Old browsers */
-    background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
-}
-
-
-#content #context-actions li:first-child {
-    border-left: none;
-    border-radius: 4px 0 0px 4px;
-}
-
-#content #context-actions li:last-child {
-    border-right: none;
-    border-radius: 0 4px 4px 0;
-}
-
-#content #context-actions .icon {
-    margin: auto;
-    margin-bottom: 5px;
-    display: block;
-    clear: both;
-    float: none;
-}
-
-#content #context-pages .follow .show-following,
-#content #context-pages .following .show-follow {
-    display: none;
-}
-
-#context-pages {
-    float: right;
-    border-left: 1px solid rgba(0,0,0,0.1);
-}
-
-#content #context-pages .icon {
-    margin-right: 5px;
-}
-
-#header #header-inner #quick li,
-#content #context-pages li {
-    border-right: 1px solid rgba(0,0,0,0.1);
-    border-left: 1px solid rgba(255,255,255,0.1);
-    padding: 0;
-}
-
-#header #header-inner #quick li:last-child,
-#content #context-pages li:last-child {
-    border-right: none;
-}
-
-#header #header-inner #quick > li:first-child {
-    border-left: none;
-}
-
-#header #header-inner #quick > li:first-child > a {
-    border-radius: 4px 0 0 4px;
-}
-
-#header #header-inner #quick a,
-#context-pages a,
-#context-pages .admin_menu a {
-    display: block;
-    padding: 0px 10px 1px 10px;
-    line-height: 35px;
-}
-
-#header #header-inner #quick a.thin,
-#context-pages a.thin,
-#context-pages .admin_menu a.thin {
-    line-height: 28px !important;
-}
-
-#header #header-inner #quick a#quick_login_link {
-    padding-left: 0px;
-}
-
-#header #header-inner #quick a {
-    overflow: hidden;
-}
-#quick a.childs:after,
-#revision-changer:before,
-#context-pages a.childs:after,
-#context-pages a.dropdown:after {
-    font-family: 'kallithea';
-    content: ' \23f7';
-}
-#context-pages a.childs {
-    padding-right: 30px;
-}
-#context-pages a.childs:after {
-    position: absolute;
-    float: right;
-    padding-left: 5px;
-    padding-right: 5px;
-}
-
-#revision-changer:before {
-    position: absolute;
-    top: 0px;
-    right: 0px;
-    border-right: 1px solid rgba(0,0,0,0.1);
-    height: 25px;
-    padding-top: 10px;
-    padding-right: 10px;
-}
-
-#context-pages li:last-child a {
-    padding-right: 10px;
-}
-
-#context-bar #revision-changer {
-    position: relative;
-    cursor: pointer;
-    border: none;
-    padding: 0;
-    margin: 0;
-    color: #FFFFFF;
-    font-size: 0.85em;
-    padding: 2px 15px;
-    padding-bottom: 3px;
-    padding-right: 30px;
-    border-right: 1px solid rgba(255,255,255,0.1);
-}
-
-#revision-changer .branch-name,
-#revision-changer .revision {
-    display: block;
-    text-align: center;
-    line-height: 1.5em;
-}
-
-#revision-changer .branch-name {
-    font-weight: bold;
-}
-
-#revision-changer .revision {
-    text-transform: uppercase;
-}
--- a/kallithea/public/css/pygments.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +0,0 @@
-div.codeblock {
-    overflow: auto;
-    padding: 0px;
-    border: 1px solid #ccc;
-    background: #f8f8f8;
-    font-size: 100%;
-    line-height: 100%;
-    /* new */
-    line-height: 125%;
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
-}
-div.codeblock .code-header {
-    border-bottom: 1px solid #CCCCCC;
-    background: #EEEEEE;
-    padding: 10px 0 5px 0;
-}
-
-div.codeblock .code-header .stats {
-    clear: both;
-    padding: 2px 8px 2px 14px;
-    border-bottom: 1px solid rgb(204, 204, 204);
-    height: 23px;
-    margin-bottom: 6px;
-}
-
-div.codeblock .code-header .stats .left {
-    float: left;
-}
-div.codeblock .code-header .stats .left.img {
-    margin-top: -2px;
-}
-div.codeblock .code-header .stats .left.item {
-    float: left;
-    padding: 0 9px 0 9px;
-    border-right: 1px solid #ccc;
-}
-div.codeblock .code-header .stats .left.item pre {
-}
-div.codeblock .code-header .stats .left.item.last {
-    border-right: none;
-}
-div.codeblock .code-header .stats .buttons {
-    float: right;
-    padding-right: 4px;
-}
-
-div.codeblock .code-header .author {
-    margin-left: 15px;
-    font-weight: bold;
-    height: 25px;
-}
-div.codeblock .code-header .author .user {
-    padding-top: 3px;
-}
-div.codeblock .code-header .commit {
-    margin-left: 15px;
-    font-weight: normal;
-    white-space: pre;
-}
-
-.code-highlighttable,
-div.codeblock .code-body table {
-    width: 0 !important;
-    border: 0px !important;
-}
-
-.code-highlighttable,
-div.codeblock .code-body table td {
-    border: 0px !important;
-}
-
-div.code-body {
-    background-color: #FFFFFF;
-}
-
-div.codeblock .code-header .search-path {
-    padding: 0px 0px 0px 10px;
-}
-
-div.search-code-body {
-    background-color: #FFFFFF;
-    padding: 5px 0px 5px 10px;
-}
-
-div.search-code-body pre .match {
-    background-color: #FAFFA6;
-}
-div.search-code-body pre .break {
-    background-color: #DDE7EF;
-    width: 100%;
-    color: #747474;
-    display: block;
-}
-div.annotatediv {
-    margin-left: 2px;
-    margin-right: 4px;
-}
-.code-highlight {
-    padding: 0px;
-    margin-top: 5px;
-    margin-bottom: 5px;
-    border-left: 1px solid #ccc;
-}
-.code-highlight pre, .linenodiv pre {
-    padding: 5px 2px 0px 5px;
-    margin: 0;
-}
-.code-highlight pre div:target {
-    background-color: #FFFFBE !important;
-}
-.linenos { padding: 0px !important; border:0px !important;}
-.linenos a { text-decoration: none; }
-
-.code { display: block; border:0px !important; }
-.code-highlight .hll, .codehilite .hll { background-color: #ffffcc }
-.code-highlight .c, .codehilite .c { color: #408080; font-style: italic } /* Comment */
-.code-highlight .err, .codehilite .err { border: 1px solid #FF0000 } /* Error */
-.code-highlight .k, .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
-.code-highlight .o, .codehilite .o { color: #666666 } /* Operator */
-.code-highlight .cm, .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
-.code-highlight .cp, .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
-.code-highlight .c1, .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
-.code-highlight .cs, .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
-.code-highlight .gd, .codehilite .gd { color: #A00000 } /* Generic.Deleted */
-.code-highlight .ge, .codehilite .ge { font-style: italic } /* Generic.Emph */
-.code-highlight .gr, .codehilite .gr { color: #FF0000 } /* Generic.Error */
-.code-highlight .gh, .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
-.code-highlight .gi, .codehilite .gi { color: #00A000 } /* Generic.Inserted */
-.code-highlight .go, .codehilite .go { color: #808080 } /* Generic.Output */
-.code-highlight .gp, .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
-.code-highlight .gs, .codehilite .gs { font-weight: bold } /* Generic.Strong */
-.code-highlight .gu, .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
-.code-highlight .gt, .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
-.code-highlight .kc, .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
-.code-highlight .kd, .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
-.code-highlight .kn, .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
-.code-highlight .kp, .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
-.code-highlight .kr, .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
-.code-highlight .kt, .codehilite .kt { color: #B00040 } /* Keyword.Type */
-.code-highlight .m, .codehilite .m { color: #666666 } /* Literal.Number */
-.code-highlight .s, .codehilite .s { color: #BA2121 } /* Literal.String */
-.code-highlight .na, .codehilite .na { color: #7D9029 } /* Name.Attribute */
-.code-highlight .nb, .codehilite .nb { color: #008000 } /* Name.Builtin */
-.code-highlight .nc, .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
-.code-highlight .no, .codehilite .no { color: #880000 } /* Name.Constant */
-.code-highlight .nd, .codehilite .nd { color: #AA22FF } /* Name.Decorator */
-.code-highlight .ni, .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
-.code-highlight .ne, .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
-.code-highlight .nf, .codehilite .nf { color: #0000FF } /* Name.Function */
-.code-highlight .nl, .codehilite .nl { color: #A0A000 } /* Name.Label */
-.code-highlight .nn, .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
-.code-highlight .nt, .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
-.code-highlight .nv, .codehilite .nv { color: #19177C } /* Name.Variable */
-.code-highlight .ow, .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
-.code-highlight .w, .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
-.code-highlight .mf, .codehilite .mf { color: #666666 } /* Literal.Number.Float */
-.code-highlight .mh, .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
-.code-highlight .mi, .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
-.code-highlight .mo, .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
-.code-highlight .sb, .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
-.code-highlight .sc, .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
-.code-highlight .sd, .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
-.code-highlight .s2, .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
-.code-highlight .se, .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
-.code-highlight .sh, .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
-.code-highlight .si, .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
-.code-highlight .sx, .codehilite .sx { color: #008000 } /* Literal.String.Other */
-.code-highlight .sr, .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
-.code-highlight .s1, .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
-.code-highlight .ss, .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
-.code-highlight .bp, .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
-.code-highlight .vc, .codehilite .vc { color: #19177C } /* Name.Variable.Class */
-.code-highlight .vg, .codehilite .vg { color: #19177C } /* Name.Variable.Global */
-.code-highlight .vi, .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
-.code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
--- a/kallithea/public/css/style.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4875 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
-    border: 0;
-    outline: 0;
-    font-size: 100%;
-    vertical-align: baseline;
-    background: transparent;
-    margin: 0;
-    padding: 0;
-}
-
-body {
-    line-height: 1;
-    height: 100%;
-    background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
-    font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
-    color: #000;
-    margin: 0;
-    padding: 0;
-    font-size: 12px;
-}
-
-ol, ul {
-    list-style: none;
-}
-
-blockquote, q {
-    quotes: none;
-}
-
-blockquote:before, blockquote:after, q:before, q:after {
-    content: none;
-}
-
-:focus {
-    outline: 0;
-}
-
-del {
-    text-decoration: line-through;
-}
-
-table {
-    border-collapse: collapse;
-    border-spacing: 0;
-}
-
-html {
-    height: 100%;
-}
-
-a {
-    color: #577632;
-    text-decoration: none;
-    cursor: pointer;
-}
-
-a:hover {
-    color: #576622;
-    text-decoration: underline;
-}
-
-h1, h2, h3, h4, h5, h6,
-div.h1, div.h2, div.h3, div.h4, div.h5, div.h6 {
-    color: #292929;
-    font-weight: 700;
-}
-
-h1, div.h1 {
-    font-size: 22px;
-}
-
-h2, div.h2 {
-    font-size: 20px;
-}
-
-h3, div.h3 {
-    font-size: 18px;
-}
-
-h4, div.h4 {
-    font-size: 16px;
-}
-
-h5, div.h5 {
-    font-size: 14px;
-}
-
-h6, div.h6 {
-    font-size: 11px;
-}
-
-ul.circle {
-    list-style-type: circle;
-}
-
-ul.disc {
-    list-style-type: disc;
-}
-
-ul.square {
-    list-style-type: square;
-}
-
-ol.lower-roman {
-    list-style-type: lower-roman;
-}
-
-ol.upper-roman {
-    list-style-type: upper-roman;
-}
-
-ol.lower-alpha {
-    list-style-type: lower-alpha;
-}
-
-ol.upper-alpha {
-    list-style-type: upper-alpha;
-}
-
-ol.decimal {
-    list-style-type: decimal;
-}
-
-div.color {
-    clear: both;
-    overflow: hidden;
-    position: absolute;
-    background: #FFF;
-    margin: 7px 0 0 60px;
-    padding: 1px 1px 1px 0;
-}
-
-div.color a {
-    width: 15px;
-    height: 15px;
-    display: block;
-    float: left;
-    margin: 0 0 0 1px;
-    padding: 0;
-}
-
-div.options {
-    clear: both;
-    overflow: hidden;
-    position: absolute;
-    background: #FFF;
-    margin: 7px 0 0 162px;
-    padding: 0;
-}
-
-div.options a {
-    height: 1%;
-    display: block;
-    text-decoration: none;
-    margin: 0;
-    padding: 3px 8px;
-}
-
-code,
-.code pre,
-div.readme .readme_box pre,
-div.rst-block pre,
-.CodeMirror .CodeMirror-code pre {
-    font-size: 12px;
-    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
-}
-
-.top-left-rounded-corner {
-    border-top-left-radius: 8px;
-}
-
-.top-right-rounded-corner {
-    border-top-right-radius: 8px;
-}
-
-.bottom-left-rounded-corner {
-    border-bottom-left-radius: 8px;
-}
-
-.bottom-right-rounded-corner {
-    border-bottom-right-radius: 8px;
-}
-
-.top-left-rounded-corner-mid {
-    border-top-left-radius: 4px;
-}
-
-.top-right-rounded-corner-mid {
-    border-top-right-radius: 4px;
-}
-
-.bottom-left-rounded-corner-mid {
-    border-bottom-left-radius: 4px;
-}
-
-.bottom-right-rounded-corner-mid {
-    border-bottom-right-radius: 4px;
-}
-
-.help-block {
-    color: #999999;
-    display: block;
-    margin-bottom: 0;
-    margin-top: 5px;
-}
-
-.empty_data {
-    color: #B9B9B9;
-}
-
-.truncate {
-       white-space: nowrap;
-       overflow: hidden;
-       text-overflow: ellipsis;
-    -o-text-overflow: ellipsis;
-    -ms-text-overflow: ellipsis;
-}
-
-.truncate.autoexpand:hover {
-    overflow: visible;
-}
-
-a.permalink {
-    visibility: hidden;
-    position: absolute;
-    margin: 3px 4px;
-}
-
-a.permalink:hover {
-    text-decoration: none;
-}
-
-h1:hover > a.permalink,
-h2:hover > a.permalink,
-h3:hover > a.permalink,
-h4:hover > a.permalink,
-h5:hover > a.permalink,
-h6:hover > a.permalink,
-div:hover > a.permalink,
-div:hover > span > a.permalink {
-    visibility: visible;
-}
-
-#header #logo {
-    padding-left: 10px;
-}
-
-div.header img {
-    padding-top: 5px;
-}
-
-#header #logo div.header,
-#header #logo div.branding {
-    font-size: 20px;
-    color: white;
-    float: left;
-    height: 44px;
-    line-height: 44px;
-    margin-right: 5px;
-}
-
-#header ul#logged-user {
-    margin-bottom: 5px !important;
-    border-radius: 0px 0px 8px 8px;
-    height: 37px;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-}
-
-#header ul#logged-user li {
-    list-style: none;
-    float: left;
-    margin: 8px 0 0;
-    padding: 4px 12px;
-    border-left: 1px solid #576622;
-}
-
-#header ul#logged-user li.first {
-    border-left: none;
-    margin: 4px;
-}
-
-#header ul#logged-user li.first div.gravatar {
-    margin-top: -2px;
-}
-
-#header ul#logged-user li.first div.account {
-    padding-top: 4px;
-    float: left;
-}
-
-#header ul#logged-user li.last {
-    border-right: none;
-}
-
-#header ul#logged-user li a {
-    color: #fff;
-    font-weight: 700;
-    text-decoration: none;
-}
-
-#header ul#logged-user li a:hover {
-    text-decoration: underline;
-}
-
-#header ul#logged-user li.highlight a {
-    color: #fff;
-}
-
-#header ul#logged-user li.highlight a:hover {
-    color: #FFF;
-}
-#header-dd {
-    clear: both;
-    position: fixed !important;
-    background-color: #577632;
-    opacity: 0.01;
-    cursor: pointer;
-    min-height: 10px;
-    width: 100% !important;
-    border-radius: 0px 0px 4px 4px;
-}
-
-#header-dd:hover {
-    opacity: 0.2;
-    transition: opacity 0.5s ease-in-out;
-}
-
-#header #header-inner {
-    min-height: 44px;
-    clear: both;
-    position: relative;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    margin: 0;
-    padding: 0;
-    display: block;
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-    border-radius: 0px 0px 4px 4px;
-}
-#header #header-inner.hover {
-    width: 100% !important;
-    border-radius: 0px 0px 0px 0px;
-    position: fixed !important;
-    z-index: 10000;
-}
-
-.header-pos-fix, .anchor {
-    margin-top: -46px;
-    padding-top: 46px;
-}
-
-#header #header-inner #home a {
-    height: 40px;
-    width: 46px;
-    display: block;
-    background-position: 0 0;
-    margin: 0;
-    padding: 0;
-}
-
-#header #header-inner #home a:hover {
-    background-position: 0 -40px;
-}
-
-#header #header-inner #logo {
-    float: left;
-    position: absolute;
-}
-
-#header #header-inner #logo h1 {
-    color: #FFF;
-    font-size: 20px;
-    margin: 12px 0 0 13px;
-    padding: 0;
-}
-
-#header #header-inner #logo a {
-    color: #fff;
-    text-decoration: none;
-}
-
-#header #header-inner #logo a:hover {
-    color: #bfe3ff;
-}
-
-#header #header-inner #quick {
-    position: relative;
-    float: right;
-    list-style-type: none;
-    list-style-position: outside;
-    margin: 4px 8px 0 0;
-    padding: 0;
-    border-radius: 4px;
-}
-
-#header #header-inner #quick li span.short {
-    padding: 9px 6px 8px 6px;
-}
-
-#header #header-inner #quick li span {
-    display: inline;
-    margin: 0;
-}
-
-#header #header-inner #quick li span.normal {
-    border: none;
-    padding: 10px 12px 8px;
-}
-
-#header #header-inner #quick li span.icon {
-    border-left: none;
-    padding-left: 10px;
-}
-
-#header #header-inner #quick li span.icon_short {
-    top: 0;
-    left: 0;
-    border-left: none;
-    border-right: 1px solid #2e5c89;
-    padding: 8px 6px 4px;
-}
-
-#header #header-inner #quick li span.icon img,
-#header #header-inner #quick li span.icon_short img {
-    vertical-align: middle;
-    margin-bottom: 2px;
-}
-
-#header #header-inner #quick ul.repo_switcher {
-    max-height: 275px;
-    overflow-x: hidden;
-    overflow-y: auto;
-}
-
-#header #header-inner #quick ul.repo_switcher li.qfilter_rs {
-    padding: 2px 3px;
-    padding-right: 17px;
-}
-
-#header #header-inner #quick ul.repo_switcher li.qfilter_rs input {
-    width: 100%;
-    border-radius: 10px;
-    padding: 2px 7px;
-}
-
-#header #header-inner #quick .repo_switcher_type {
-    position: absolute;
-    left: 0;
-    top: 9px;
-    margin: 0px 2px 0px 2px;
-}
-
-.groups_breadcrumbs a {
-    color: #fff;
-}
-
-.groups_breadcrumbs a:hover {
-    color: #bfe3ff;
-    text-decoration: none;
-}
-
-td.quick_repo_menu:before {
-    font-family: "kallithea";
-    content: "\23f5";           /* triangle-right */
-    margin-left: 3px;
-    padding-right: 3px;
-}
-
-td.quick_repo_menu {
-    cursor: pointer;
-    width: 8px;
-    border: 1px solid transparent;
-}
-
-td.quick_repo_menu.active:before {
-    font-family: "kallithea";
-    content: "\23f7";           /* triangle-down */
-    margin-left: 1px;
-    padding-right: 1px;
-}
-
-td.quick_repo_menu.active {
-    border: 1px solid #577632;
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
-    cursor: pointer;
-}
-
-td.quick_repo_menu .menu_items {
-    margin-top: 5px;
-    margin-left: -6px;
-    width: 150px;
-    position: absolute;
-    background-color: #FFF;
-    background: none repeat scroll 0 0 #FFFFFF;
-    border-color: #577632 #666666 #666666;
-    border-right: 1px solid #666666;
-    border-style: solid;
-    border-width: 1px;
-    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
-    border-top-style: none;
-}
-
-td.quick_repo_menu .menu_items li {
-    padding: 0 !important;
-}
-
-td.quick_repo_menu .menu_items a {
-    display: block;
-    padding: 4px 12px 4px 8px;
-}
-
-td.quick_repo_menu .menu_items a:hover {
-    background-color: #EEE;
-    text-decoration: none;
-}
-
-td.quick_repo_menu .menu_items .icon img {
-    margin-bottom: -2px;
-}
-
-td.quick_repo_menu .menu_items.hidden {
-    display: none;
-}
-
-.dt_repo {
-    white-space: nowrap;
-    color: #577632;
-}
-
-.dt_repo_pending {
-    opacity: 0.5;
-}
-
-.dt_repo i.icon-keyhole-circled,
-.dt_repo i.icon-globe
-{
-    font-size: 16px;
-    vertical-align: -2px;
-    margin: 0px 1px 0px 3px;
-}
-
-.dt_repo a {
-    text-decoration: none;
-}
-
-.dt_repo .dt_repo_name:hover {
-    text-decoration: underline;
-}
-
-.yui-dt-first th {
-    text-align: left;
-}
-
-/*
-    Copyright (c) 2011, Yahoo! Inc. All rights reserved.
-    Code licensed under the BSD License:
-    http://developer.yahoo.com/yui/license.html
-    version: 2.9.0
-*/
-.yui-skin-sam .yui-dt-mask {
-    position: absolute;
-    z-index: 9500;
-}
-.yui-dt-tmp {
-    position: absolute;
-    left: -9000px;
-}
-.yui-dt-scrollable .yui-dt-bd { overflow: auto }
-.yui-dt-scrollable .yui-dt-hd {
-    overflow: hidden;
-    position: relative;
-}
-.yui-dt-scrollable .yui-dt-bd thead tr,
-.yui-dt-scrollable .yui-dt-bd thead th {
-    position: absolute;
-    left: -1500px;
-}
-.yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
-.yui-skin-sam thead .yui-dt-draggable { cursor: move }
-.yui-dt-coltarget {
-    position: absolute;
-    z-index: 999;
-}
-.yui-dt-hd { zoom: 1 }
-th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
-.yui-dt-resizer {
-    position: absolute;
-    right: 0;
-    bottom: 0;
-    height: 100%;
-    cursor: e-resize;
-    cursor: col-resize;
-    background-color: #CCC;
-    opacity: 0;
-}
-.yui-dt-resizerproxy {
-    visibility: hidden;
-    position: absolute;
-    z-index: 9000;
-    background-color: #CCC;
-    opacity: 0;
-}
-th.yui-dt-hidden .yui-dt-liner,
-td.yui-dt-hidden .yui-dt-liner,
-th.yui-dt-hidden .yui-dt-resizer { display: none }
-.yui-dt-editor,
-.yui-dt-editor-shim {
-    position: absolute;
-    z-index: 9000;
-}
-.yui-skin-sam .yui-dt table {
-    margin: 0;
-    padding: 0;
-    font-family: arial;
-    font-size: inherit;
-    border-collapse: separate;
-    border-spacing: 0;
-    border: 1px solid #7f7f7f;
-}
-.yui-skin-sam .yui-dt thead { border-spacing: 0 }
-.yui-skin-sam .yui-dt caption {
-    color: #000;
-    font-size: 85%;
-    font-weight: normal;
-    font-style: italic;
-    line-height: 1;
-    padding: 1em 0;
-    text-align: center;
-}
-.yui-skin-sam .yui-dt th,
-.yui-skin-sam .yui-dt th a {
-    font-weight: normal;
-    text-decoration: none;
-    color: #000;
-    vertical-align: bottom;
-}
-.yui-skin-sam .yui-dt th {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    border-right: 1px solid #cbcbcb;
-}
-.yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
-.yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
-.yui-skin-sam .yui-dt-liner {
-    margin: 0;
-    padding: 0;
-}
-.yui-skin-sam .yui-dt-coltarget {
-    width: 5px;
-    background-color: red;
-}
-.yui-skin-sam .yui-dt td {
-    margin: 0;
-    padding: 0;
-    border: 0;
-    border-right: 1px solid #cbcbcb;
-    text-align: left;
-}
-.yui-skin-sam .yui-dt-list td { border-right: 0 }
-.yui-skin-sam .yui-dt-resizer { width: 6px }
-.yui-skin-sam .yui-dt-mask {
-    background-color: #000;
-    opacity: .25;
-}
-.yui-skin-sam .yui-dt-message { background-color: #FFF }
-.yui-skin-sam .yui-dt-scrollable table { border: 0 }
-.yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
-    border-left: 1px solid #7f7f7f;
-    border-top: 1px solid #7f7f7f;
-    border-right: 1px solid #7f7f7f;
-}
-.yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
-    border-left: 1px solid #7f7f7f;
-    border-bottom: 1px solid #7f7f7f;
-    border-right: 1px solid #7f7f7f;
-    background-color: #FFF;
-}
-.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
-.yui-skin-sam th.yui-dt-asc,
-.yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
-
-.yui-skin-sam th.yui-dt-asc .yui-dt-liner:after {
-    font-family: "kallithea";
-    content: "\23f6";           /* triangle-up */
-}
-
-.yui-skin-sam th.yui-dt-desc .yui-dt-liner:after {
-    font-family: "kallithea";
-    content: "\23f7";           /* triangle-down */
-}
-
-tbody .yui-dt-editable { cursor: pointer }
-.yui-dt-editor {
-    text-align: left;
-    background-color: #f2f2f2;
-    border: 1px solid #808080;
-    padding: 6px;
-}
-.yui-dt-editor label {
-    padding-left: 4px;
-    padding-right: 6px;
-}
-.yui-dt-editor .yui-dt-button {
-    padding-top: 6px;
-    text-align: right;
-}
-.yui-skin-sam th.yui-dt-highlighted,
-.yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
-.yui-skin-sam tr.yui-dt-highlighted,
-.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
-.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc {
-    cursor: pointer;
-    background-color: #b2d2ff;
-}
-.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
-.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
-.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
-.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
-.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc {
-    cursor: pointer;
-    background-color: #b2d2ff;
-}
-.yui-skin-sam th.yui-dt-selected,
-.yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
-.yui-skin-sam tr.yui-dt-selected td,
-.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
-.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
-    background-color: #426fd9;
-    color: #FFF;
-}
-.yui-skin-sam .yui-dt-list th.yui-dt-selected,
-.yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
-.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
-.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
-.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
-    background-color: #426fd9;
-    color: #FFF;
-}
-.yui-skin-sam .yui-dt-paginator {
-    display: block;
-    margin: 6px 0;
-    white-space: nowrap;
-}
-.yui-skin-sam .yui-dt-paginator .yui-dt-first,
-.yui-skin-sam .yui-dt-paginator .yui-dt-last,
-.yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
-.yui-skin-sam .yui-dt-paginator a.yui-dt-first,
-.yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
-.yui-skin-sam .yui-dt-paginator .yui-dt-previous,
-.yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
-.yui-skin-sam a.yui-dt-page {
-    border: 1px solid #cbcbcb;
-    padding: 2px 6px;
-    text-decoration: none;
-    background-color: #fff;
-}
-.yui-skin-sam .yui-dt-selected {
-    border: 1px solid #fff;
-    background-color: #fff;
-}
-
-#content #left {
-    left: 0;
-    width: 280px;
-    position: absolute;
-}
-
-#content #right {
-    margin: 0 60px 10px 290px;
-}
-
-#content div.box {
-    clear: both;
-    background: #fff;
-    margin: 0 0 10px;
-    padding: 0 0 10px;
-    border-radius: 4px 4px 4px 4px;
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-}
-
-#content div.box-left {
-    width: 49%;
-    clear: none;
-    float: left;
-    margin: 0 0 10px;
-}
-
-#content div.box-right {
-    width: 49%;
-    clear: none;
-    float: right;
-    margin: 0 0 10px;
-}
-
-#content div.box div.title {
-    clear: both;
-    overflow: hidden;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    margin: 0 0 20px;
-    padding: 0;
-    border-radius: 4px 4px 0 0;
-}
-
-#content div.box div.title h5 {
-    float: left;
-    border: none;
-    color: #fff;
-    margin: 0;
-    padding: 11px 0 11px 10px;
-}
-
-#content div.box div.title .link-white {
-    color: #FFFFFF;
-}
-
-#content div.box div.title .link-white.current {
-    color: #BFE3FF;
-}
-
-#content div.box div.title ul.links li {
-    list-style: none;
-    float: left;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.title ul.links li a {
-    font-size: 13px;
-    font-weight: 700;
-    height: 1%;
-    margin: 4px;
-    text-decoration: none;
-}
-
-#content div.box div.title ul.links.nav-tabs li a {
-    float: left;
-    color: #fff;
-    margin: 0;
-    padding: 11px 10px 11px 10px;
-}
-
-#content div.box div.title ul.links.icon-only-links li a {
-    float: left;
-    color: #fff;
-    margin: 0;
-    padding: 11px 10px 11px 10px;
-}
-
-#content div.box h1,
-#content div.box h2,
-#content div.box h3,
-#content div.box h4,
-#content div.box h5,
-#content div.box h6,
-#content div.box div.h1,
-#content div.box div.h2,
-#content div.box div.h3,
-#content div.box div.h4,
-#content div.box div.h5,
-#content div.box div.h6 {
-    clear: both;
-    overflow: hidden;
-    margin: 8px 20px 3px;
-    padding-bottom: 2px;
-}
-
-#content div.box div.normal-indent {
-    margin: 0 20px 10px 20px;
-}
-
-#content div.box p {
-    color: #5f5f5f;
-    font-size: 12px;
-    line-height: 150%;
-    margin: 0 24px 10px;
-    padding: 0;
-}
-
-#content div.box blockquote {
-    border-left: 4px solid #DDD;
-    color: #5f5f5f;
-    font-size: 11px;
-    line-height: 150%;
-    margin: 0 34px;
-    padding: 0 0 0 14px;
-}
-
-#content div.box blockquote p {
-    margin: 10px 0;
-    padding: 0;
-}
-
-#content div.box dl {
-    margin: 10px 0px;
-}
-
-#content div.box dt {
-    font-size: 12px;
-    margin: 0;
-}
-
-#content div.box dd {
-    font-size: 12px;
-    margin: 0;
-    padding: 8px 0 8px 15px;
-}
-
-#content div.box li {
-    font-size: 12px;
-    padding: 4px 0;
-}
-
-#content div.box ul.disc,
-#content div.box ul.circle {
-    margin: 10px 24px 10px 38px;
-}
-
-#content div.box ul.square {
-    margin: 10px 24px 10px 40px;
-}
-
-#content div.box img.left {
-    border: none;
-    float: left;
-    margin: 10px 10px 10px 0;
-}
-
-#content div.box img.right {
-    border: none;
-    float: right;
-    margin: 10px 0 10px 10px;
-}
-
-#content div.box div.messages {
-    clear: both;
-    overflow: hidden;
-    margin: 0 20px;
-    padding: 0;
-}
-
-#content div.box div.message {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 5px 0;
-    white-space: pre-wrap;
-}
-#content div.box div.expand {
-    width: 110%;
-    height: 14px;
-    font-size: 10px;
-    text-align: center;
-    cursor: pointer;
-    color: #666;
-    background: linear-gradient(to bottom,rgba(255,255,255,0),rgba(64,96,128,0.1));
-    display: none;
-    overflow: hidden;
-}
-#content div.box div.expand .expandtext {
-    background-color: #ffffff;
-    padding: 2px;
-    border-radius: 2px;
-}
-
-#content div.box div.message a {
-    font-weight: 400 !important;
-}
-
-#content div.box div.message div.image {
-    float: left;
-    margin: 9px 0 0 5px;
-    padding: 6px;
-}
-
-#content div.box div.message div.image img {
-    vertical-align: middle;
-    margin: 0;
-}
-
-#content div.box div.message div.text {
-    float: left;
-    margin: 0;
-    padding: 9px 6px;
-}
-
-#content div.box div.message div.text h1,
-#content div.box div.message div.text h2,
-#content div.box div.message div.text h3,
-#content div.box div.message div.text h4,
-#content div.box div.message div.text h5,
-#content div.box div.message div.text h6 {
-    border: none;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.message div.text span {
-    height: 1%;
-    display: block;
-    margin: 0;
-    padding: 5px 0 0;
-}
-
-#content div.box div.message-error {
-    height: 1%;
-    clear: both;
-    overflow: hidden;
-    background: #FBE3E4;
-    border: 1px solid #FBC2C4;
-    color: #860006;
-}
-
-#content div.box div.message-error h6 {
-    color: #860006;
-}
-
-#content div.box div.message-warning {
-    height: 1%;
-    clear: both;
-    overflow: hidden;
-    background: #FFF6BF;
-    border: 1px solid #FFD324;
-    color: #5f5200;
-}
-
-#content div.box div.message-warning h6 {
-    color: #5f5200;
-}
-
-#content div.box div.message-notice {
-    height: 1%;
-    clear: both;
-    overflow: hidden;
-    background: #8FBDE0;
-    border: 1px solid #6BACDE;
-    color: #003863;
-}
-
-#content div.box div.message-notice h6 {
-    color: #003863;
-}
-
-#content div.box div.message-success {
-    height: 1%;
-    clear: both;
-    overflow: hidden;
-    background: #E6EFC2;
-    border: 1px solid #C6D880;
-    color: #4e6100;
-}
-
-#content div.box div.message-success h6 {
-    color: #4e6100;
-}
-
-#content div.box div.form div.fields div.field {
-    height: 1%;
-    min-height: 12px;
-    border-bottom: 1px solid #DDD;
-    clear: both;
-    margin: 0;
-    padding: 10px 0;
-}
-
-#content div.box div.form div.fields div.field-first {
-    padding: 0 0 10px;
-}
-
-#content div.box div.form div.fields div.field-noborder {
-    border-bottom: 0 !important;
-}
-
-#content div.box div.form div.fields div.field span.error-message {
-    height: 1%;
-    display: inline-block;
-    color: red;
-    margin: 8px 0 0 4px;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field span.success {
-    height: 1%;
-    display: block;
-    color: #316309;
-    margin: 8px 0 0;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.label {
-    left: 70px;
-    width: 155px;
-    position: absolute;
-    margin: 0;
-    padding: 5px 0 0 0px;
-}
-
-#content div.box div.form div.fields div.field div.label-summary {
-    left: 30px;
-    width: 155px;
-    position: absolute;
-    margin: 0;
-    padding: 0px 0 0 0px;
-}
-
-#content div.box-left div.form div.fields div.field div.label,
-#content div.box-right div.form div.fields div.field div.label,
-#content div.box-left div.form div.fields div.field div.label,
-#content div.box-left div.form div.fields div.field div.label-summary,
-#content div.box-right div.form div.fields div.field div.label-summary,
-#content div.box-left div.form div.fields div.field div.label-summary {
-    clear: both;
-    overflow: hidden;
-    left: 0;
-    width: auto;
-    position: relative;
-    margin: 0;
-    padding: 0 0 8px;
-}
-
-#content div.box div.form div.fields div.field div.label-select {
-    padding: 5px 0 0 5px;
-}
-
-#content div.box-left div.form div.fields div.field div.label-select,
-#content div.box-right div.form div.fields div.field div.label-select {
-    padding: 0 0 8px;
-}
-
-#content div.box-left div.form div.fields div.field div.label-textarea,
-#content div.box-right div.form div.fields div.field div.label-textarea {
-    padding: 0 0 8px !important;
-}
-
-#content div.box div.form div.fields div.field div.label label,
-div.label label {
-    color: #393939;
-    font-weight: 700;
-}
-#content div.box div.form div.fields div.field div.label label,
-div.label-summary label {
-    color: #393939;
-    font-weight: 700;
-}
-#content div.box div.form div.fields div.field div.input {
-    margin: 0 0 0 200px;
-}
-
-#content div.box div.form div.fields div.field div.input.summary {
-    margin: 0 0 0 110px;
-}
-#content div.box div.form div.fields div.field div.input.summary-short {
-    margin: 0 0 0 110px;
-}
-#content div.box div.form div.fields div.field div.file {
-    margin: 0 0 0 200px;
-}
-#content div.box div.form div.fields div.field div.editor {
-    margin: 0 0 0 200px;
-}
-
-#content div.box-left div.form div.fields div.field div.input,
-#content div.box-right div.form div.fields div.field div.input {
-    margin: 0 0 0 0px;
-}
-
-#content div.box div.form div.fields div.field div.input input,
-.reviewer_ac input {
-    background: #FFF;
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    color: #000;
-    font-size: 12px;
-    margin: 0;
-    padding: 7px 7px 6px;
-}
-
-#content div.box div.form div.fields div.field div.input input#clone_url,
-#content div.box div.form div.fields div.field div.input input#clone_url_id
-{
-    font-size: 14px;
-    padding: 0 2px;
-}
-
-#content div.box div.form div.fields div.field div.file input {
-    background: none repeat scroll 0 0 #FFFFFF;
-    border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
-    border-style: solid;
-    border-width: 1px;
-    color: #000000;
-    font-size: 12px;
-    margin: 0;
-    padding: 7px 7px 6px;
-}
-
-input[readonly],
-input.disabled {
-    background-color: #F5F5F5 !important;
-}
-
-#content div.box div.form div.fields div.field div.input input.small {
-    width: 30%;
-}
-
-#content div.box div.form div.fields div.field div.input input.medium {
-    width: 55%;
-}
-
-#content div.box div.form div.fields div.field div.input input.large {
-    width: 85%;
-}
-
-#content div.box div.form div.fields div.field div.input input.date {
-    width: 177px;
-}
-
-#content div.box div.form div.fields div.field div.input input.button {
-    background: #D4D0C8;
-    border-top: 1px solid #FFF;
-    border-left: 1px solid #FFF;
-    border-right: 1px solid #404040;
-    border-bottom: 1px solid #404040;
-    color: #000;
-    margin: 0;
-    padding: 4px 8px;
-}
-
-#content div.box div.form div.fields div.field div.textarea {
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    margin: 0 0 0 200px;
-    padding: 7px 7px 6px;
-}
-
-#content div.box div.form div.fields div.field div.textarea-editor {
-    border: 1px solid #ddd;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.textarea textarea {
-    width: 100%;
-    height: 220px;
-    overflow-y: auto;
-    background: #FFF;
-    color: #000;
-    font-size: 12px;
-    outline: none;
-    border-width: 0;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box-left div.form div.fields div.field div.textarea textarea,
-#content div.box-right div.form div.fields div.field div.textarea textarea {
-    width: 100%;
-    height: 100px;
-}
-
-#content div.box div.form div.fields div.field div.textarea table {
-    width: 100%;
-    border: none;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.textarea table td {
-    background: #DDD;
-    border: none;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.textarea table td table {
-    width: auto;
-    border: none;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.textarea table td table td {
-    font-size: 11px;
-    padding: 5px 5px 5px 0;
-}
-
-#content div.box div.form div.fields div.field input[type=text]:focus,
-#content div.box div.form div.fields div.field input[type=password]:focus,
-#content div.box div.form div.fields div.field input[type=file]:focus,
-#content div.box div.form div.fields div.field textarea:focus,
-#content div.box div.form div.fields div.field select:focus,
-.reviewer_ac input:focus {
-    background: #f6f6f6;
-    border-color: #666;
-}
-
-.reviewer_ac {
-    padding: 10px
-}
-
-div.form div.fields div.field div.button {
-    margin: 0;
-    padding: 0 0 0 8px;
-}
-#content div.box table.noborder {
-    border: 1px solid transparent;
-}
-
-#content div.box table {
-    width: 100%;
-    border-collapse: separate;
-    margin: 0;
-    padding: 0;
-    border: 1px solid #eee;
-    border-radius: 4px;
-}
-
-#content div.box table th {
-    background: #eee;
-    border-bottom: 1px solid #ddd;
-    padding: 5px 0px 5px 5px;
-    text-align: left;
-}
-
-#content div.box table th.left {
-    text-align: left;
-}
-
-#content div.box table th.right {
-    text-align: right;
-}
-
-#content div.box table th.center {
-    text-align: center;
-}
-
-#content div.box table th.selected {
-    vertical-align: middle;
-    padding: 0;
-}
-
-#content div.box table td {
-    background: #fff;
-    border-bottom: 1px solid #cdcdcd;
-    vertical-align: middle;
-    padding: 5px;
-}
-
-#content div.box table td.compact {
-    padding: 0;
-}
-
-#content div.box table tr.selected td {
-    background: #FFC;
-}
-
-#content div.box table td.selected {
-    width: 3%;
-    text-align: center;
-    vertical-align: middle;
-    padding: 0;
-}
-
-#content div.box table td.action {
-    width: 45%;
-    text-align: left;
-}
-
-#content div.box table td.date {
-    width: 33%;
-    text-align: center;
-}
-
-#content div.box div.action {
-    float: right;
-    background: #FFF;
-    text-align: right;
-    margin: 10px 0 0;
-    padding: 0;
-}
-
-#content div.box div.action select {
-    font-size: 11px;
-    margin: 0;
-}
-
-#content div.box div.action .ui-selectmenu {
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.pagination {
-    height: 1%;
-    clear: both;
-    overflow: hidden;
-    margin: 10px 0 0;
-    padding: 0;
-}
-
-#content div.box div.pagination ul.pager {
-    float: right;
-    text-align: right;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.pagination ul.pager li {
-    height: 1%;
-    float: left;
-    list-style: none;
-    background: #ebebeb url("../images/pager.png") repeat-x;
-    border-top: 1px solid #dedede;
-    border-left: 1px solid #cfcfcf;
-    border-right: 1px solid #c4c4c4;
-    border-bottom: 1px solid #c4c4c4;
-    color: #4A4A4A;
-    font-weight: 700;
-    margin: 0 0 0 4px;
-    padding: 0;
-}
-
-#content div.box div.pagination ul.pager li.separator {
-    padding: 6px;
-}
-
-#content div.box div.pagination ul.pager li.current {
-    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
-    border-top: 1px solid #ccc;
-    border-left: 1px solid #bebebe;
-    border-right: 1px solid #b1b1b1;
-    border-bottom: 1px solid #afafaf;
-    color: #515151;
-    padding: 6px;
-}
-
-#content div.box div.pagination ul.pager li a {
-    height: 1%;
-    display: block;
-    float: left;
-    color: #515151;
-    text-decoration: none;
-    margin: 0;
-    padding: 6px;
-}
-
-#content div.box div.pagination ul.pager li a:hover,
-#content div.box div.pagination ul.pager li a:active {
-    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
-    border-top: 1px solid #ccc;
-    border-left: 1px solid #bebebe;
-    border-right: 1px solid #b1b1b1;
-    border-bottom: 1px solid #afafaf;
-    margin: -1px;
-}
-
-#content div.box div.pagination-right {
-    float: right;
-}
-
-#content div.box div.pagination-wh {
-    height: 1%;
-    overflow: hidden;
-    text-align: right;
-    margin: 10px 0 0;
-    padding: 0;
-}
-
-#content div.box div.pagination-wh > :first-child {
-    border-radius: 4px 0px 0px 4px;
-}
-
-#content div.box div.pagination-wh > :last-child {
-    border-radius: 0px 4px 4px 0px;
-    border-right: 1px solid #cfcfcf;
-}
-
-#content div.box div.pagination-wh a,
-#content div.box div.pagination-wh span.pager_dotdot,
-#content div.box div.pagination-wh span.yui-pg-previous,
-#content div.box div.pagination-wh span.yui-pg-last,
-#content div.box div.pagination-wh span.yui-pg-next,
-#content div.box div.pagination-wh span.yui-pg-first {
-    height: 1%;
-    float: left;
-    background: #ebebeb url("../images/pager.png") repeat-x;
-    border-top: 1px solid #dedede;
-    border-left: 1px solid #cfcfcf;
-    border-bottom: 1px solid #c4c4c4;
-    color: #4A4A4A;
-    font-weight: 700;
-    padding: 6px;
-}
-
-#content div.box div.pagination-wh span.pager_curpage {
-    height: 1%;
-    float: left;
-    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
-    border-top: 1px solid #ccc;
-    border-left: 1px solid #bebebe;
-    border-bottom: 1px solid #afafaf;
-    color: #515151;
-    font-weight: 700;
-    padding: 6px;
-}
-
-#content div.box div.pagination-wh a:hover,
-#content div.box div.pagination-wh a:active {
-    background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
-    border-top: 1px solid #ccc;
-    border-left: 1px solid #bebebe;
-    border-bottom: 1px solid #afafaf;
-    text-decoration: none;
-}
-
-#content div.box div.traffic div.legend {
-    clear: both;
-    overflow: hidden;
-    border-bottom: 1px solid #ddd;
-    margin: 0 0 10px;
-    padding: 0 0 10px;
-}
-
-#content div.box div.traffic div.legend h6 {
-    float: left;
-    border: none;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.traffic div.legend li {
-    list-style: none;
-    float: left;
-    font-size: 11px;
-    margin: 0;
-    padding: 0 8px 0 4px;
-}
-
-#content div.box div.traffic div.legend li.visits {
-    border-left: 12px solid #edc240;
-}
-
-#content div.box div.traffic div.legend li.pageviews {
-    border-left: 12px solid #afd8f8;
-}
-
-#content div.box div.traffic table {
-    width: auto;
-}
-
-#content div.box div.traffic table td {
-    background: transparent;
-    border: none;
-    padding: 2px 3px 3px;
-}
-
-#content div.box div.traffic table td.legendLabel {
-    padding: 0 3px 2px;
-}
-
-#content div.box #summary {
-    margin-right: 200px;
-    min-height: 240px;
-}
-
-#summary-menu-stats {
-    float: left;
-    width: 180px;
-    position: absolute;
-    top: 0;
-    right: 0;
-}
-
-#summary-menu-stats ul {
-    margin: 0 10px;
-    display: block;
-    background-color: #f9f9f9;
-    border: 1px solid #d1d1d1;
-    border-radius: 4px;
-}
-
-#content #summary-menu-stats li {
-    border-top: 1px solid #d1d1d1;
-    padding: 0;
-}
-
-#content #summary-menu-stats li:hover {
-    background: #f0f0f0;
-}
-
-#content #summary-menu-stats li:first-child {
-    border-top: none;
-}
-
-#summary-menu-stats a {
-    display: block;
-    padding: 12px 10px;
-    background-repeat: no-repeat;
-    background-position: 10px 50%;
-    padding-right: 10px;
-}
-
-#repo_size_2.loaded {
-    margin-left: 30px;
-    display: block;
-    padding-right: 10px;
-    padding-bottom: 7px;
-}
-
-#summary-menu-stats a:hover {
-    text-decoration: none;
-}
-
-#summary-menu-stats a span {
-    background-color: #DEDEDE;
-    color: #888 !important;
-    border-radius: 4px;
-    padding: 2px 4px;
-    font-size: 10px;
-}
-
-#summary .metatag {
-    display: inline-block;
-    padding: 3px 5px;
-    margin-bottom: 3px;
-    margin-right: 1px;
-    border-radius: 5px;
-}
-
-#content div.box #summary p {
-    margin-bottom: -5px;
-    width: 600px;
-    white-space: pre-wrap;
-}
-
-#content div.box #summary p:last-child {
-    margin-bottom: 9px;
-}
-
-#content div.box #summary p:first-of-type {
-    margin-top: 9px;
-}
-
-.metatag {
-    display: inline-block;
-    margin-right: 1px;
-    border-radius: 4px 4px 4px 4px;
-
-    border: solid 1px #9CF;
-    padding: 2px 3px 2px 3px !important;
-    background-color: #DEF;
-}
-
-.metatag[tag="dead"] {
-    background-color: #E44;
-}
-
-.metatag[tag="stale"] {
-    background-color: #EA4;
-}
-
-.metatag[tag="featured"] {
-    background-color: #AEA;
-}
-
-.metatag[tag="requires"] {
-    background-color: #9CF;
-}
-
-.metatag[tag="recommends"] {
-    background-color: #BDF;
-}
-
-.metatag[tag="lang"] {
-    background-color: #FAF474;
-}
-
-.metatag[tag="license"] {
-    border: solid 1px #9CF;
-    background-color: #DEF;
-}
-.metatag[tag="see"] {
-    border: solid 1px #CBD;
-    background-color: #EDF;
-}
-
-a.metatag[tag="license"]:hover {
-    background-color: #577632;
-    color: #FFF;
-    text-decoration: none;
-}
-
-#summary .desc {
-    white-space: pre;
-    width: 100%;
-}
-
-#summary .repo_name {
-    font-size: 1.6em;
-    font-weight: bold;
-    vertical-align: baseline;
-    clear: right
-}
-
-#footer {
-    clear: both;
-    overflow: hidden;
-    text-align: right;
-    margin: 0;
-    padding: 0 10px 4px;
-    margin: -10px 0 0;
-}
-
-#footer div#footer-inner {
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-    border-radius: 4px 4px 4px 4px;
-}
-
-#footer div#footer-inner p {
-    padding: 15px 25px 15px 0;
-    color: #FFF;
-    font-weight: 700;
-}
-
-#footer div#footer-inner .footer-link {
-    float: left;
-    padding-left: 10px;
-}
-
-#footer div#footer-inner .footer-link a,
-#footer div#footer-inner .footer-link-right a {
-    color: #FFF;
-}
-
-#login div.title {
-    clear: both;
-    overflow: hidden;
-    position: relative;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    margin: 0 auto;
-    padding: 0;
-}
-
-#login div.inner .icon-lock {
-    font-size: 80pt;
-    color: #DDD;
-}
-
-#login div.inner {
-    background: #FFF;
-    border-top: none;
-    border-bottom: none;
-    margin: 0 auto;
-    padding: 20px;
-}
-
-#login div.form div.form-horizontal div.form-group > label {
-    width: 173px;
-    float: left;
-    text-align: right;
-    margin: 2px 10px 0 0;
-    padding: 5px 0 0 5px;
-}
-
-#login div.form div.form-horizontal div.form-group div input {
-    background: #FFF;
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    color: #000;
-    font-size: 11px;
-    margin: 0;
-    padding: 7px 7px 6px;
-}
-
-#login div.form .buttons {
-    float: right;
-}
-
-#login div.form div.links {
-    clear: both;
-    overflow: hidden;
-    margin: 10px 0 0;
-    border-top: 1px solid #DDD;
-    padding: 10px 0 0;
-}
-
-.user-menu {
-    margin: 0px !important;
-    float: left;
-}
-
-.user-menu .container {
-    padding: 0px 4px 0px 4px;
-    margin: 0px 0px 0px 0px;
-}
-
-.user-menu .gravatar {
-    margin: 0px 0px 0px 0px;
-    cursor: pointer;
-}
-.user-menu .gravatar.enabled {
-    background-color: #FDF784 !important;
-}
-.user-menu .gravatar:hover {
-    background-color: #FDF784 !important;
-}
-#quick_login {
-    min-height: 110px;
-    padding: 4px;
-    position: absolute;
-    right: 0;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-
-    z-index: 999;
-    border-radius: 0px 0px 4px 4px;
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-
-    overflow: hidden;
-}
-#quick_login h4 {
-    color: #fff;
-    padding: 5px 0px 5px 14px;
-}
-
-#quick_login .password_forgoten {
-    padding-right: 0px;
-    padding-top: 0px;
-    text-align: left;
-}
-
-#quick_login .password_forgoten a {
-    font-size: 10px;
-    color: #fff;
-    padding: 0px !important;
-    line-height: 20px !important;
-}
-
-#quick_login .register {
-    padding-right: 10px;
-    padding-top: 5px;
-    text-align: left;
-}
-
-#quick_login .register a {
-    font-size: 10px;
-    color: #fff;
-    padding: 0px !important;
-    line-height: 20px !important;
-}
-
-#quick_login .submit {
-    margin: -20px 0 0 0px;
-    position: absolute;
-    right: 15px;
-}
-
-#quick_login .links_left {
-    float: left;
-    margin-right: 130px;
-    width: 170px;
-}
-#quick_login .links_right {
-
-    position: absolute;
-    right: 0;
-}
-#quick_login .full_name {
-    color: #FFFFFF;
-    font-weight: bold;
-    padding: 3px 3px 3px 6px;
-}
-#quick_login .big_gravatar {
-    padding: 4px 0px 0px 6px;
-}
-#quick_login .notifications {
-    padding: 2px 0px 0px 6px;
-    color: #FFFFFF;
-    font-weight: bold;
-    line-height: 10px !important;
-}
-#quick_login .notifications a,
-#quick_login .unread a {
-    color: #FFFFFF;
-    display: block;
-    padding: 0px !important;
-}
-#quick_login .notifications a:hover,
-#quick_login .unread a:hover {
-    background-color: inherit !important;
-}
-#quick_login .email,
-#quick_login .unread {
-    color: #FFFFFF;
-    padding: 3px 3px 3px 6px;
-}
-#quick_login .links .logout {
-}
-
-#quick_login div.form div.fields {
-    padding-top: 2px;
-    padding-left: 10px;
-}
-
-#quick_login div.form div.fields div.field {
-    padding: 5px;
-}
-
-#quick_login div.form div.fields div.field div.label label {
-    color: #fff;
-    padding-bottom: 3px;
-}
-
-#quick_login div.form div.fields div.field div.input input {
-    width: 236px;
-    background: #FFF;
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    color: #000;
-    font-size: 11px;
-    margin: 0;
-    padding: 5px 7px 4px;
-}
-
-#quick_login div.form div.fields div.buttons {
-    clear: both;
-    overflow: hidden;
-    text-align: right;
-    margin: 0;
-    padding: 5px 14px 0px 5px;
-}
-
-#quick_login div.form div.links {
-    clear: both;
-    overflow: hidden;
-    margin: 10px 0 0;
-    padding: 0 0 2px;
-}
-
-#quick_login ol.links {
-    display: block;
-    font-weight: bold;
-    list-style: none outside none;
-    text-align: right;
-}
-#quick_login ol.links li {
-    line-height: 27px;
-    margin: 0;
-    padding: 0;
-    color: #fff;
-    display: block;
-    float: none !important;
-}
-
-#quick_login ol.links li a {
-    color: #fff;
-    display: block;
-    padding: 2px;
-}
-#quick_login ol.links li a:HOVER {
-    background-color: inherit !important;
-}
-
-#register div.title {
-    clear: both;
-    overflow: hidden;
-    position: relative;
-    background-color: #577632;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #577632, #577632);
-    margin: 0 auto;
-    padding: 0;
-}
-
-#register div.inner {
-    background: #FFF;
-    border-top: none;
-    border-bottom: none;
-    margin: 0 auto;
-    padding: 20px;
-}
-
-#register div.form div.fields div.field div.label {
-    width: 135px;
-    float: left;
-    text-align: right;
-    margin: 2px 10px 0 0;
-    padding: 5px 0 0 5px;
-}
-
-#register div.form div.fields div.field div.input input {
-    width: 300px;
-    background: #FFF;
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    color: #000;
-    font-size: 11px;
-    margin: 0;
-    padding: 7px 7px 6px;
-}
-
-#register div.form div.fields div.buttons {
-    clear: both;
-    overflow: hidden;
-    border-top: 1px solid #DDD;
-    text-align: left;
-    margin: 0;
-    padding: 10px 0 0 150px;
-}
-
-#register div.form div.activation_msg {
-    padding-top: 4px;
-    padding-bottom: 4px;
-}
-
-#journal .journal_day {
-    font-size: 20px;
-    padding: 10px 0px;
-    border-bottom: 2px solid #DDD;
-    margin-left: 10px;
-    margin-right: 10px;
-}
-
-#journal .journal_container {
-    padding: 5px;
-    clear: both;
-    margin: 0px 5px 0px 10px;
-}
-
-#journal .journal_action_container {
-    padding-left: 38px;
-}
-
-#journal .journal_user {
-    color: #747474;
-    font-size: 14px;
-    font-weight: bold;
-    height: 30px;
-}
-
-#journal .journal_user.deleted {
-    color: #747474;
-    font-size: 14px;
-    font-weight: normal;
-    height: 30px;
-    font-style: italic;
-}
-
-
-#journal .journal_icon {
-    clear: both;
-    float: left;
-    padding-right: 4px;
-    padding-top: 3px;
-}
-
-#journal .journal_action {
-    padding-top: 4px;
-    min-height: 2px;
-    float: left
-}
-
-#journal .journal_action_params {
-    clear: left;
-    padding-left: 22px;
-}
-
-#journal .journal_repo {
-    float: left;
-    margin-left: 6px;
-    padding-top: 3px;
-}
-
-#journal .date {
-    clear: both;
-    color: #777777;
-    font-size: 11px;
-    padding-left: 22px;
-}
-
-#journal .journal_repo .journal_repo_name {
-    font-weight: bold;
-    font-size: 1.1em;
-}
-
-#journal .compare_view {
-    padding: 5px 0px 5px 0px;
-    width: 95px;
-}
-
-.journal_highlight {
-    font-weight: bold;
-    padding: 0 2px;
-    vertical-align: bottom;
-}
-
-.trending_language_tbl, .trending_language_tbl td {
-    border: 0 !important;
-    margin: 0 !important;
-    padding: 0 !important;
-}
-
-.trending_language_tbl, .trending_language_tbl tr {
-    border-spacing: 1px;
-}
-
-.trending_language {
-    background-color: #577632;
-    color: #FFF;
-    display: block;
-    min-width: 20px;
-    text-decoration: none;
-    height: 12px;
-    margin-bottom: 0px;
-    margin-left: 5px;
-    white-space: pre;
-    padding: 3px;
-}
-
-h3.files_location {
-    font-size: 1.8em;
-    font-weight: 700;
-    border-bottom: none !important;
-    margin: 10px 0 !important;
-}
-
-#files_data dl dt {
-    float: left;
-    width: 60px;
-    margin: 0 !important;
-    padding: 5px;
-}
-
-#files_data dl dd {
-    margin: 0 !important;
-    padding: 5px !important;
-}
-
-#files_data .codeblock #editor_container .error-message {
-    color: red;
-    padding: 10px 10px 10px 26px
-}
-
-.file_history {
-    padding-top: 10px;
-    font-size: 16px;
-}
-.file_author {
-    float: left;
-}
-
-.file_author .item {
-    float: left;
-    padding: 5px;
-    color: #888;
-}
-
-.changeset_id {
-    color: #666666;
-    margin-right: -3px;
-}
-
-.changeset-logical-index {
-    color: #666666;
-    font-style: italic;
-    font-size: 85%;
-    padding-right: 0.5em;
-    text-align: right;
-}
-
-.changeset_hash {
-    color: #000000;
-}
-
-#changeset_content {
-    border-left: 1px solid #CCC;
-    border-right: 1px solid #CCC;
-    border-bottom: 1px solid #CCC;
-    padding: 5px;
-}
-
-#changeset_compare_view_content {
-    border: 1px solid #CCC;
-    padding: 5px;
-}
-
-#changeset_content .container {
-    min-height: 100px;
-    font-size: 1.2em;
-    overflow: hidden;
-}
-
-#changeset_compare_view_content .compare_view_commits {
-    width: auto !important;
-}
-
-#changeset_compare_view_content .compare_view_commits td {
-    padding: 0px 0px 0px 12px !important;
-}
-
-#changeset_content .container .right {
-    float: right;
-    width: 20%;
-    text-align: right;
-}
-
-#changeset_content .container .message {
-    white-space: pre-wrap;
-}
-#changeset_content .container .message a:hover {
-    text-decoration: none;
-}
-.cs_files .cur_cs {
-    margin: 10px 2px;
-    font-weight: bold;
-}
-
-.cs_files .node {
-    float: left;
-}
-
-.cs_files .changes {
-    float: right;
-    color: #577632;
-}
-
-.cs_files .changes .added {
-    background-color: #BBFFBB;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
-}
-
-.cs_files .changes .deleted {
-    background-color: #FF8888;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
-}
-/*new binary
-NEW_FILENODE = 1
-DEL_FILENODE = 2
-MOD_FILENODE = 3
-RENAMED_FILENODE = 4
-CHMOD_FILENODE = 5
-BIN_FILENODE = 6
-*/
-.cs_files .changes .bin {
-    background-color: #BBFFBB;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
-}
-.cs_files .changes .bin.bin1 {
-    background-color: #BBFFBB;
-}
-
-/*deleted binary*/
-.cs_files .changes .bin.bin2 {
-    background-color: #FF8888;
-}
-
-/*mod binary*/
-.cs_files .changes .bin.bin3 {
-    background-color: #DDDDDD;
-}
-
-/*rename file*/
-.cs_files .changes .bin.bin4 {
-    background-color: #6D99FF;
-}
-
-/*rename file*/
-.cs_files .changes .bin.bin4 {
-    background-color: #6D99FF;
-}
-
-/*chmod file*/
-.cs_files .changes .bin.bin5 {
-    background-color: #6D99FF;
-}
-
-.cs_files .cs_added,
-.cs_files .cs_A {
-    height: 16px;
-    margin-top: 7px;
-    text-align: left;
-}
-
-.cs_files .cs_changed,
-.cs_files .cs_M {
-    height: 16px;
-    margin-top: 7px;
-    text-align: left;
-}
-
-.cs_files .cs_removed,
-.cs_files .cs_D {
-    height: 16px;
-    margin-top: 7px;
-    text-align: left;
-}
-
-.cs_files .cs_renamed,
-.cs_files .cs_R {
-    height: 16px;
-    margin-top: 7px;
-    text-align: left;
-}
-
-.table {
-    position: relative;
-}
-
-#graph {
-    position: relative;
-    overflow: hidden;
-}
-
-#graph_nodes {
-    position: absolute;
-}
-
-#graph_content,
-#graph .info_box,
-#graph .container_header {
-    margin-left: 100px;
-}
-
-#graph_content {
-    position: relative;
-}
-
-#graph .container_header {
-    padding: 10px;
-    height: 25px;
-}
-
-#graph_content #rev_range_container {
-    float: left;
-    margin: 0px 0px 0px 3px;
-}
-
-#graph_content #rev_range_clear {
-    float: left;
-    margin: 0px 0px 0px 3px;
-}
-
-#graph_content #changesets {
-    table-layout: fixed;
-    border-collapse: collapse;
-    border-left: none;
-    border-right: none;
-    border-color: #cdcdcd;
-}
-
-#graph_content #changesets tr.out-of-range,
-#graph_content #changesets tr.mergerow {
-    opacity: 0.5;
-}
-
-#graph_content #changesets td {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    height: 31px;
-    border-color: #cdcdcd;
-    text-align: left;
-}
-
-#graph_content .container .checkbox {
-    width: 14px;
-    font-size: 0.85em;
-}
-
-#graph_content .container .status {
-    width: 14px;
-    font-size: 0.85em;
-}
-
-#graph_content .container .author {
-   width: 105px;
-}
-
-#graph_content .container .hash {
-    width: 100px;
-    font-size: 0.85em;
-}
-
-#graph_content #changesets .container .date {
-    width: 76px;
-    color: #666;
-    font-size: 10px;
-}
-
-#graph_content_pr .compare_view_commits .expand_commit,
-#graph_content .container .expand_commit {
-    width: 24px;
-    cursor: pointer;
-}
-
-#graph_content #changesets .container .right {
-    width: 120px;
-    padding-right: 0px;
-    overflow: visible;
-    position: relative;
-}
-
-#graph_content .container .mid {
-    padding: 0;
-}
-
-#graph_content .log-container {
-    position: relative;
-}
-
-#graph_content .container #singlerange,
-#graph_content .container .changeset_range {
-    float: left;
-    margin: 6px 3px;
-}
-
-#graph_content .container .author img {
-    vertical-align: middle;
-}
-
-#graph_content .container .author .user {
-    color: #444444;
-}
-
-#graph_content .container .mid .message {
-    white-space: pre-wrap;
-    padding: 0;
-    overflow: hidden;
-    height: 1.1em;
-}
-
-#graph_content_pr .compare_view_commits .message {
-    padding: 0 !important;
-    height: 1.1em;
-}
-
-#graph_content .container .mid .message.expanded,
-#graph_content_pr .compare_view_commits .message.expanded {
-    height: auto;
-    margin: 8px 0px 8px 0px;
-    overflow: initial;
-}
-
-#graph_content .container .extra-container {
-    display: block;
-    position: absolute;
-    top: -15px;
-    right: 0;
-    padding-left: 5px;
-    background: #FFFFFF;
-    height: 41px;
-}
-
-#pull_request_overview .comments-container,
-#changeset_compare_view_content .comments-container,
-#graph_content .comments-container,
-#shortlog_data .comments-container,
-#graph_content .logtags {
-    display: block;
-    float: left;
-    overflow: hidden;
-    padding: 0;
-    margin: 0;
-    white-space: nowrap;
-}
-
-#graph_content .comments-container {
-    margin: 0.8em 0;
-    margin-right: 0.5em;
-}
-
-#graph_content .tagcontainer {
-    width: 80px;
-    position: relative;
-    float: right;
-    height: 100%;
-    top: 7px;
-    margin-left: 0.5em;
-}
-
-#graph_content .logtags {
-    min-width: 80px;
-    height: 1.1em;
-    position: absolute;
-    left: 0px;
-    width: auto;
-    top: 0px;
-}
-
-#graph_content .logtags.tags {
-    top: 14px;
-}
-
-#graph_content .logtags:hover {
-    overflow: visible;
-    position: absolute;
-    width: auto;
-    right: 0;
-    left: initial;
-}
-
-#graph_content .logtags .booktag,
-#graph_content .logtags .tagtag {
-    float: left;
-    line-height: 1em;
-    margin-bottom: 1px;
-    margin-right: 1px;
-    padding: 1px 3px;
-    font-size: 10px;
-}
-
-#graph_content .container .mid .message a:hover {
-    text-decoration: none;
-}
-
-#compare_branches + .table .revision-link,
-#compare_tags + .table .revision-link,
-#compare_bookmarks + .table .revision-link,
-.table #files_data .revision-link,
-#repos_list_wrap .revision-link,
-#shortlog_data .revision-link {
-    font-weight: normal !important;
-    font-family: monospace;
-    font-size: 12px;
-    color: #577632;
-}
-
-.revision-link {
-    color: #3F6F9F;
-    font-weight: bold !important;
-}
-
-.issue-tracker-link {
-    color: #3F6F9F;
-    font-weight: bold !important;
-}
-
-.changeset-status-container {
-    padding-right: 5px;
-    margin-top: 1px;
-    float: right;
-    height: 14px;
-}
-.code-header .changeset-status-container {
-    float: left;
-    padding: 2px 0px 0px 2px;
-}
-.code-header .changeset-status-container .changeset-status-lbl {
-    float: left;
-    padding: 0px 4px 0px 0px;
-}
-.changeset-status-container div.changeset-status-ico {
-    float: left;
-}
-.code-header .changeset-status-container .changeset-status-ico,
-.container .changeset-status-ico {
-    float: left;
-}
-
-/* changeset statuses (must be the same name as the status) */
-.changeset-status-not_reviewed {
-    color: #bababa;
-}
-.changeset-status-approved {
-    color: #81ba51;
-}
-.changeset-status-rejected {
-    color: #cc392e;
-}
-.changeset-status-under_review {
-    color: #ffc71e;
-}
-
-#graph_content .comments-cnt {
-    color: rgb(136, 136, 136);
-    padding: 5px 0;
-}
-
-#shortlog_data .comments-cnt {
-    color: rgb(136, 136, 136);
-    padding: 3px 0;
-}
-
-.right .changes {
-    clear: both;
-}
-
-.right .changes .changed_total {
-    display: block;
-    float: right;
-    text-align: center;
-    min-width: 45px;
-    cursor: pointer;
-    color: #444444;
-    background: #FEA;
-    border-radius: 0px 0px 0px 6px;
-    padding: 1px;
-}
-
-.right .changes .added,
-.changed, .removed {
-    display: block;
-    padding: 1px;
-    color: #444444;
-    float: right;
-    text-align: center;
-    min-width: 15px;
-}
-
-.right .changes .added {
-    background: #CFC;
-}
-
-.right .changes .changed {
-    background: #FEA;
-}
-
-.right .changes .removed {
-    background: #FAA;
-}
-
-.right .merge {
-    padding: 1px 3px 1px 3px;
-    background-color: #fca062;
-    font-size: 10px;
-    color: #ffffff;
-    text-transform: uppercase;
-    white-space: nowrap;
-    border-radius: 3px;
-    margin-right: 2px;
-}
-
-.right .parent {
-    color: #666666;
-    clear: both;
-}
-.right .logtags {
-    line-height: 2.2em;
-}
-.repotag, .branchtag, .logtags .tagtag, .logtags .booktag {
-    margin: 0px 2px;
-}
-
-.repotag,
-.branchtag,
-.tagtag,
-.booktag,
-.spantag {
-    padding: 1px 3px 1px 3px;
-    font-size: 10px;
-    color: #577632;
-    white-space: nowrap;
-    border-radius: 4px;
-    border: 1px solid #d9e8f8;
-    line-height: 1.5em;
-}
-
-#graph_content .branchtag,
-#graph_content .tagtag,
-#graph_content .booktag {
-    margin: 1.1em 0;
-    margin-right: 0.5em;
-}
-
-.repotag,
-.branchtag,
-.tagtag,
-.booktag {
-    float: left;
-}
-
-.right .logtags .branchtag,
-.right .logtags .tagtag,
-.right .logtags .booktag,
-.right .merge {
-    float: right;
-    line-height: 1em;
-    margin: 1px 1px !important;
-    display: block;
-}
-
-.repotag {
-    border-color: #56A546;
-    color: #46A546;
-    font-size: 8px;
-    text-transform: uppercase;
-}
-
-#context-bar .repotag,
-.repo-icons .repotag {
-    border-color: white;
-    color: white;
-    margin-top: 3px;
-}
-
-.repo-icons .repotag {
-    margin-top: 0px;
-    padding-top: 0px;
-    padding-bottom: 0px;
-}
-
-.booktag {
-    border-color: #46A546;
-    color: #46A546;
-}
-
-.tagtag {
-    border-color: #62cffc;
-    color: #62cffc;
-}
-
-.logtags .branchtag a:hover,
-.logtags .branchtag a,
-.branchtag a,
-.branchtag a:hover {
-    text-decoration: none;
-    color: inherit;
-}
-.logtags .tagtag {
-    padding: 1px 3px 1px 3px;
-    background-color: #62cffc;
-    font-size: 10px;
-    color: #ffffff;
-    white-space: nowrap;
-    border-radius: 3px;
-}
-
-.tagtag a,
-.tagtag a:hover,
-.logtags .tagtag a,
-.logtags .tagtag a:hover {
-    text-decoration: none;
-    color: inherit;
-}
-.logbooks .booktag,
-.logbooks .booktag,
-.logtags .booktag,
-.logtags .booktag {
-    padding: 1px 3px 1px 3px;
-    background-color: #46A546;
-    font-size: 10px;
-    color: #ffffff;
-    white-space: nowrap;
-    border-radius: 3px;
-}
-.logbooks .booktag,
-.logbooks .booktag a,
-.right .logtags .booktag,
-.logtags .booktag a {
-    color: #ffffff;
-}
-
-.logbooks .booktag,
-.logbooks .booktag a:hover,
-.logtags .booktag,
-.logtags .booktag a:hover,
-.booktag a,
-.booktag a:hover {
-    text-decoration: none;
-    color: inherit;
-}
-div.browserblock {
-    overflow: hidden;
-    border: 1px solid #ccc;
-    background: #f8f8f8;
-    font-size: 100%;
-    line-height: 125%;
-    padding: 0;
-    border-radius: 6px 6px 0px 0px;
-}
-
-div.browserblock .browser-header {
-    background: #FFF;
-    padding: 10px 0px 15px 0px;
-    width: 100%;
-}
-
-div.browserblock .browser-nav {
-    float: left
-}
-
-div.browserblock .browser-branch {
-    float: left;
-}
-
-div.browserblock .browser-branch label {
-    color: #4A4A4A;
-    vertical-align: text-top;
-    padding-right: 2px;
-}
-
-div.browserblock .browser-header span {
-    margin-left: 5px;
-    font-weight: 700;
-}
-
-div.browserblock .browser-search {
-    clear: both;
-    padding: 8px 8px 0px 5px;
-    height: 20px;
-}
-
-div.browserblock #node_filter_box {
-}
-
-div.browserblock .search_activate {
-    float: left
-}
-
-div.browserblock .add_node {
-    float: left;
-    padding-left: 5px;
-}
-
-div.browserblock .search_activate a:hover,
-div.browserblock .add_node a:hover {
-    text-decoration: none !important;
-}
-
-div.browserblock .browser-body {
-    background: #EEE;
-    border-top: 1px solid #CCC;
-}
-
-table.code-browser {
-    border-collapse: collapse;
-    width: 100%;
-}
-
-table.code-browser tr {
-    margin: 3px;
-}
-
-table.code-browser thead th {
-    background-color: #EEE;
-    height: 20px;
-    font-size: 1.1em;
-    font-weight: 700;
-    text-align: left;
-    padding-left: 10px;
-}
-
-table.code-browser tbody td {
-    padding-left: 10px;
-    height: 20px;
-}
-
-table.code-browser .browser-file {
-    height: 16px;
-    padding-left: 5px;
-    text-align: left;
-}
-.diffblock .changeset_header {
-    height: 16px;
-}
-.diffblock .changeset_file {
-    float: left;
-}
-.diffblock .diff-menu-wrapper {
-    float: left;
-}
-
-.diffblock .diff-menu {
-    position: absolute;
-    background: none repeat scroll 0 0 #FFFFFF;
-    border-color: #577632 #666666 #666666;
-    border-right: 1px solid #666666;
-    border-style: solid solid solid;
-    border-width: 1px;
-    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
-    margin-top: 5px;
-    margin-left: 1px;
-
-}
-.diffblock .diff-actions {
-    padding: 2px 0px 0px 2px;
-    float: left;
-}
-.diffblock .diff-menu ul li {
-    padding: 0px 0px 0px 0px !important;
-}
-.diffblock .diff-menu ul li a {
-    display: block;
-    padding: 3px 8px 3px 8px !important;
-}
-.diffblock .diff-menu ul li a:hover {
-    text-decoration: none;
-    background-color: #EEEEEE;
-}
-table.code-browser .browser-dir {
-    height: 16px;
-    padding-left: 5px;
-    text-align: left;
-}
-
-table.code-browser .submodule-dir {
-    height: 16px;
-    padding-left: 5px;
-    text-align: left;
-}
-
-/* add some padding to the right of the file, folder, or submodule icon and
-before the text */
-table.code-browser i[class^='icon-'] {
-    padding-right: .3em;
-}
-
-.box .search {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 20px 10px;
-}
-
-.box .search div.search_path {
-    background: none repeat scroll 0 0 #EEE;
-    border: 1px solid #CCC;
-    color: blue;
-    margin-bottom: 10px;
-    padding: 10px 0;
-}
-
-.box .search div.search_path div.link {
-    font-weight: 700;
-    margin-left: 25px;
-}
-
-.box .search div.search_path div.link a {
-    color: #577632;
-    cursor: pointer;
-    text-decoration: none;
-}
-
-#path_unlock {
-    color: red;
-    font-size: 1.2em;
-    padding-left: 4px;
-}
-
-.info_box span {
-    margin-left: 3px;
-    margin-right: 3px;
-}
-
-.info_box .rev {
-    color: #577632;
-    font-size: 1.6em;
-    font-weight: bold;
-    vertical-align: sub;
-}
-
-.info_box input#at_rev,
-.info_box input#size {
-    background: #FFF;
-    border-top: 1px solid #b3b3b3;
-    border-left: 1px solid #b3b3b3;
-    border-right: 1px solid #eaeaea;
-    border-bottom: 1px solid #eaeaea;
-    color: #000;
-    font-size: 12px;
-    margin: 0;
-    padding: 1px 5px 1px;
-}
-
-.info_box input#view {
-    text-align: center;
-    padding: 4px 3px 2px 2px;
-}
-
-.info_box_elem {
-    display: inline-block;
-    padding: 0 2px;
-}
-
-.yui-overlay, .yui-panel-container {
-    visibility: hidden;
-    position: absolute;
-    z-index: 2;
-}
-
-#tip-box {
-    position: absolute;
-
-    background-color: #FFF;
-    border: 2px solid #577632;
-    font: 100% sans-serif;
-    width: auto;
-    opacity: 1;
-    padding: 8px;
-
-    white-space: pre-wrap;
-    border-radius: 8px 8px 8px 8px;
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-    z-index: 100000;
-}
-
-.hl-tip-box {
-    z-index: 1;
-    position: absolute;
-    color: #666;
-    background-color: #FFF;
-    border: 2px solid #577632;
-    font: 100% sans-serif;
-    width: auto;
-    opacity: 1;
-    padding: 8px;
-    white-space: pre-wrap;
-    border-radius: 8px 8px 8px 8px;
-    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
-}
-
-
-.mentions-container {
-    width: 90% !important;
-}
-.mentions-container .yui-ac-content {
-    width: 100% !important;
-}
-
-.ac {
-    vertical-align: top;
-}
-
-.ac .yui-ac {
-    position: inherit;
-    font-size: 100%;
-}
-
-.ac .perm_ac {
-    width: 20em;
-}
-
-.ac .yui-ac-input {
-    width: 100%;
-}
-
-.ac .yui-ac-container {
-    position: absolute;
-    top: 1.6em;
-    width: auto;
-}
-
-.ac .yui-ac-content {
-    position: absolute;
-    border: 1px solid gray;
-    background: #fff;
-    z-index: 9050;
-}
-
-.ac .yui-ac-shadow {
-    position: absolute;
-    width: 100%;
-    background: #000;
-    opacity: .10;
-    z-index: 9049;
-    margin: .3em;
-}
-
-.ac .yui-ac-content ul {
-    width: 100%;
-    margin: 0;
-    padding: 0;
-    z-index: 9050;
-}
-
-.ac .yui-ac-content li {
-    cursor: default;
-    white-space: nowrap;
-    margin: 0;
-    padding: 2px 5px;
-    height: 18px;
-    z-index: 9050;
-    display: block;
-    width: auto !important;
-}
-
-.ac .yui-ac-content li .ac-container-wrap {
-    width: auto;
-}
-
-.ac .yui-ac-content li.yui-ac-prehighlight {
-    background: #B3D4FF;
-    z-index: 9050;
-}
-
-.ac .yui-ac-content li.yui-ac-highlight {
-    background: #556CB5;
-    color: #FFF;
-    z-index: 9050;
-}
-.ac .yui-ac-bd {
-    z-index: 9050;
-}
-
-#repo_size {
-    display: block;
-    margin-top: 4px;
-    color: #666;
-    float: right;
-}
-
-.currently_following {
-    padding-left: 10px;
-    padding-bottom: 5px;
-}
-
-.action_button {
-    border: 0;
-    display: inline;
-}
-
-.action_button:hover {
-    border: 0;
-    text-decoration: underline;
-    cursor: pointer;
-}
-
-#switch_repos {
-    position: absolute;
-    height: 25px;
-    z-index: 1;
-}
-
-#switch_repos select {
-    min-width: 150px;
-    max-height: 250px;
-    z-index: 1;
-}
-
-.breadcrumbs {
-    border: medium none;
-    color: #FFF;
-    float: left;
-    font-weight: 700;
-    font-size: 14px;
-    margin: 0;
-    padding: 11px 0 11px 10px;
-}
-
-.breadcrumbs .hash {
-    text-transform: none;
-    color: #fff;
-}
-
-.breadcrumbs a {
-    color: #FFF;
-}
-
-.flash_msg {
-}
-
-.flash_msg ul {
-}
-
-.error_red {
-    color: red;
-}
-
-.flash_msg .alert-error {
-    background-color: #c43c35;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
-    border-color: #c43c35 #c43c35 #882a25;
-}
-
-.flash_msg .alert-error a {
-    text-decoration: underline;
-}
-
-.flash_msg .alert-warning {
-    color: #404040 !important;
-    background-color: #eedc94;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #fceec1, #eedc94);
-    border-color: #eedc94 #eedc94 #e4c652;
-}
-
-.flash_msg .alert-warning a {
-    text-decoration: underline;
-}
-
-.flash_msg .alert-success {
-    background-color: #57a957;
-    background-repeat: repeat-x !important;
-    background-image: linear-gradient(to bottom, #62c462, #57a957);
-    border-color: #57a957 #57a957 #3d773d;
-}
-
-.flash_msg .alert-success a {
-    text-decoration: underline;
-    color: #FFF !important;
-}
-
-.flash_msg .alert-info {
-    background-color: #339bb9;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
-    border-color: #339bb9 #339bb9 #22697d;
-}
-
-.flash_msg .alert-info a {
-    text-decoration: underline;
-}
-
-.flash_msg .alert-error,
-.flash_msg .alert-warning,
-.flash_msg .alert-success,
-.flash_msg .alert-info {
-    font-size: 12px;
-    font-weight: 700;
-    min-height: 14px;
-    line-height: 14px;
-    margin-bottom: 10px;
-    margin-top: 0;
-    display: block;
-    overflow: auto;
-    padding: 6px 10px 6px 10px;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-    position: relative;
-    color: #FFF;
-    border-width: 1px;
-    border-style: solid;
-    border-radius: 4px;
-    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
-}
-
-div#legend_data {
-    padding-left: 10px;
-}
-div#legend_container table {
-    border: none !important;
-}
-div#legend_container table,
-div#legend_choices table {
-    width: auto !important;
-}
-
-table#permissions_manage {
-    width: 0 !important;
-}
-
-table#permissions_manage span.private_repo_msg {
-    font-size: 0.8em;
-    opacity: 0.6;
-}
-
-table#permissions_manage td.private_repo_msg {
-    font-size: 0.8em;
-}
-
-table#permissions_manage tr#add_perm_input td {
-    vertical-align: middle;
-}
-
-div.gravatar {
-    background-color: #FFF;
-    float: left;
-    margin-right: 0.7em;
-    padding: 1px 1px 1px 1px;
-    line-height: 0;
-    border-radius: 3px;
-}
-
-div.gravatar img {
-    border-radius: 2px;
-}
-
-#header, #content, #footer {
-    min-width: 978px;
-}
-
-#content {
-    clear: both;
-    padding: 10px 10px 14px 10px;
-}
-
-#content.hover {
-    padding: 55px 10px 14px 10px !important;
-}
-
-#content div.box div.title div.search {
-    border-left: 1px solid #576622;
-}
-
-#content div.box div.title div.search div.input input {
-    border: 1px solid #576622;
-}
-
-.btn {
-    color: #515151;
-    background-color: #DADADA;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #F4F4F4, #DADADA);
-
-    border-top: 1px solid #DDD;
-    border-left: 1px solid #c6c6c6;
-    border-right: 1px solid #DDD;
-    border-bottom: 1px solid #c6c6c6;
-    outline: none;
-    margin: 0px 3px 3px 0px;
-    border-radius: 4px 4px 4px 4px !important;
-    cursor: pointer !important;
-    padding: 3px 3px 3px 3px;
-    display: inline-block;
-}
-
-ul.nav-stacked {
-    margin: 20px;
-    color: #393939;
-    font-weight: 700;
-}
-
-ul.nav-stacked a {
-    color: inherit;
-}
-
-ul.nav-stacked li.active {
-    list-style-type: disc
-}
-
-/* make .btn inputs and buttons and divs look the same */
-button.btn,
-input.btn {
-    font-family: inherit;
-    font-size: inherit;
-    line-height: inherit;
-}
-
-.btn::-moz-focus-inner {
-    border: 0;
-    padding: 0;
-}
-
-.btn.badge {
-    cursor: default !important;
-}
-
-input[disabled].btn,
-.btn.disabled {
-    color: #999;
-}
-
-.btn.btn-danger.disabled {
-    color: #eee;
-    background-color: #c77;
-    border-color: #b66
-}
-
-.btn.btn-small {
-    padding: 2px 6px;
-}
-
-.btn.btn-mini {
-    padding: 0px 4px;
-}
-
-.btn:focus {
-    outline: none;
-}
-.btn:hover {
-    text-decoration: none;
-    color: #515151;
-    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
-}
-.btn.badge:hover {
-    box-shadow: none !important;
-}
-.btn.disabled:hover {
-    background-position: 0;
-    color: #999;
-    text-decoration: none;
-    box-shadow: none !important;
-}
-
-.btn.red {
-    color: #fff;
-    background-color: #c43c35;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
-    border-color: #c43c35 #c43c35 #882a25;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-}
-
-
-.btn.blue {
-    color: #fff;
-    background-color: #339bb9;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
-    border-color: #339bb9 #339bb9 #22697d;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-}
-
-.btn.green {
-    color: #fff;
-    background-color: #57a957;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #62c462, #57a957);
-    border-color: #57a957 #57a957 #3d773d;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-}
-
-.btn.yellow {
-    color: #fff;
-    background-color: #faa732;
-    background-repeat: repeat-x;
-    background-image: linear-gradient(to bottom, #fbb450, #f89406);
-    border-color: #f89406 #f89406 #ad6704;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-}
-
-label.disabled {
-    color: #aaa;
-}
-
-.btn.blue.hidden {
-    display: none;
-}
-
-.btn.active {
-    font-weight: bold;
-}
-
-ins, div.options a:hover {
-    text-decoration: none;
-}
-
-img,
-#header #header-inner #quick li a:hover span.normal,
-#content div.box div.form div.fields div.field div.textarea table td table td a,
-#clone_url,
-#clone_url_id
-{
-    border: none;
-}
-
-img.icon, .right .merge img {
-    vertical-align: bottom;
-}
-
-#header ul#logged-user,
-#content div.box div.title ul.links,
-#content div.box div.message div.dismiss,
-#content div.box div.traffic div.legend ul {
-    float: right;
-    margin: 0;
-    padding: 0;
-}
-
-#header #header-inner #home,
-#header #header-inner #logo,
-#content div.box ul.left,
-#content div.box ol.left,
-div#commit_history,
-div#legend_data, div#legend_container, div#legend_choices {
-    float: left;
-}
-
-#header #header-inner #quick li #quick_login,
-#header #header-inner #quick li:hover ul ul,
-#header #header-inner #quick li:hover ul ul ul,
-#header #header-inner #quick li:hover ul ul ul ul,
-#content #left #menu ul.closed,
-#content #left #menu li ul.collapsed,
-.yui-tt-shadow {
-    display: none;
-}
-
-#header #header-inner #quick li:hover #quick_login,
-#header #header-inner #quick li:hover ul,
-#header #header-inner #quick li li:hover ul,
-#header #header-inner #quick li li li:hover ul,
-#header #header-inner #quick li li li li:hover ul,
-#content #left #menu ul.opened,
-#content #left #menu li ul.expanded {
-    display: block;
-}
-
-.repo-switcher .select2-choice {
-    padding: 0px 8px 1px !important;
-    display: block;
-    height: 100%;
-}
-
-.repo-switcher .select2-container,
-.repo-switcher .select2-choice,
-.repo-switcher .select2-choice span {
-    background: transparent !important;
-    border: 0 !important;
-    box-shadow: none !important;
-    color: #FFFFFF !important;
-}
-
-.repo-switcher .select2-arrow {
-    display: none !important;
-}
-
-.repo-switcher .select2-chosen:after {
-    font-family: 'kallithea';
-    content: ' \23f7';
-}
-
-.repo-switcher-dropdown.select2-drop.select2-drop-active {
-    box-shadow: none;
-    color: #fff;
-    background-color: #576622;
-}
-
-.repo-switcher-dropdown.select2-drop.select2-drop-active .select2-results .select2-highlighted {
-    background-color: #6388ad;
-}
-
-#content div.graph {
-    padding: 0 10px 10px;
-    height: 450px;
-}
-
-#content div.box ol.lower-roman,
-#content div.box ol.upper-roman,
-#content div.box ol.lower-alpha,
-#content div.box ol.upper-alpha,
-#content div.box ol.decimal {
-    margin: 10px 24px 10px 44px;
-}
-
-#content div.box div.form,
-#content div.box div.table,
-#content div.box div.traffic {
-    position: relative;
-    clear: both;
-    margin: 0;
-    padding: 0 20px 10px;
-}
-
-#content div.box div.form div.fields,
-#login div.form-horizontal,
-#login div.form-horizontal div.form-group,
-#register div.form,
-#register div.form div.fields {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.label span,
-#login div.form div.form-horizontal div.form-group div.label span,
-#register div.form div.fields div.field div.label span {
-    height: 1%;
-    display: block;
-    color: #363636;
-    margin: 0;
-    padding: 2px 0 0;
-}
-
-#content div.box div.form div.fields div.field div.input input.error,
-#login div.form div.form-horizontal div.form-group div.input input.error,
-#register div.form div.fields div.field div.input input.error {
-    background: #FBE3E4;
-    border-top: 1px solid #e1b2b3;
-    border-left: 1px solid #e1b2b3;
-    border-right: 1px solid #FBC2C4;
-    border-bottom: 1px solid #FBC2C4;
-}
-
-#content div.box div.form div.fields div.field div.input input.success,
-#login div.form div.form-horizontal div.form-group div.input input.success,
-#register div.form div.fields div.field div.input input.success {
-    background: #E6EFC2;
-    border-top: 1px solid #cebb98;
-    border-left: 1px solid #cebb98;
-    border-right: 1px solid #c6d880;
-    border-bottom: 1px solid #c6d880;
-}
-
-#content div.box-left div.form div.fields div.field div.textarea,
-#content div.box-right div.form div.fields div.field div.textarea,
-#content div.box div.form div.fields div.field div.select select,
-#content div.box table th.selected input,
-#content div.box table td.selected input {
-    margin: 0;
-}
-
-#content div.box-left div.form div.fields div.field div.select,
-#content div.box-left div.form div.fields div.field div.checkboxes,
-#content div.box-left div.form div.fields div.field div.radios,
-#content div.box-right div.form div.fields div.field div.select,
-#content div.box-right div.form div.fields div.field div.checkboxes,
-#content div.box-right div.form div.fields div.field div.radios {
-    margin: 0 0 0 0px !important;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.select,
-#content div.box div.form div.fields div.field div.checkboxes,
-#content div.box div.form div.fields div.field div.radios {
-    margin: 0 0 0 200px;
-    padding: 0;
-}
-
-#content div.box div.form div.fields div.field div.select a:hover,
-#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,
-#content div.box div.action a:hover {
-    color: #000;
-    text-decoration: none;
-}
-
-#content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,
-#content div.box div.action a.ui-selectmenu-focus {
-    border: 1px solid #666;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox,
-#content div.box div.form div.fields div.field div.radios div.radio {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 8px 0 2px;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox input,
-#content div.box div.form div.fields div.field div.radios div.radio input {
-    float: left;
-    margin: 0;
-}
-
-#content div.box div.form div.fields div.field div.checkboxes div.checkbox label,
-#content div.box div.form div.fields div.field div.radios div.radio label {
-    height: 1%;
-    display: block;
-    float: left;
-    margin: 2px 0 0 4px;
-}
-
-div.form div.fields div.field div.button input,
-#content div.box div.form div.fields div.buttons input
-div.form div.fields div.buttons input,
-#content div.box div.action div.button input {
-    font-size: 11px;
-    font-weight: 700;
-    margin: 0;
-}
-
-div.form div.fields div.field div.highlight,
-#content div.box div.form div.fields div.buttons div.highlight {
-    display: inline;
-}
-
-#content div.box div.form div.fields div.buttons,
-div.form div.fields div.buttons {
-    margin: 10px 0 0 200px;
-    padding: 0;
-}
-
-#content div.box-left div.form div.fields div.buttons,
-#content div.box-right div.form div.fields div.buttons,
-div.box-left div.form div.fields div.buttons,
-div.box-right div.form div.fields div.buttons {
-    margin: 10px 0 0;
-}
-
-#content div.box table td.user,
-#content div.box table td.address {
-    width: 10%;
-    text-align: center;
-}
-
-#content div.box div.action div.button,
-#login div.form div.form-horizontal div.form-group div.input div.link,
-#register div.form div.fields div.field div.input div.link {
-    text-align: right;
-    margin: 6px 0 0;
-    padding: 0;
-}
-
-#content div.box div.pagination div.results,
-#content div.box div.pagination-wh div.results {
-    text-align: left;
-    float: left;
-    margin: 0;
-    padding: 0;
-}
-
-#content div.box div.pagination div.results span,
-#content div.box div.pagination-wh div.results span {
-    height: 1%;
-    display: block;
-    float: left;
-    background: #ebebeb url("../images/pager.png") repeat-x;
-    border-top: 1px solid #dedede;
-    border-left: 1px solid #cfcfcf;
-    border-right: 1px solid #c4c4c4;
-    border-bottom: 1px solid #c4c4c4;
-    color: #4A4A4A;
-    font-weight: 700;
-    margin: 0;
-    padding: 6px 8px;
-}
-
-#content div.box div.pagination ul.pager li.disabled,
-#content div.box div.pagination-wh a.disabled {
-    color: #B4B4B4;
-    padding: 6px;
-}
-
-#login, #register {
-    width: 520px;
-    margin: 10% auto 0;
-    padding: 0;
-}
-
-#login div.color,
-#register div.color {
-    clear: both;
-    overflow: hidden;
-    background: #FFF;
-    margin: 10px auto 0;
-    padding: 3px 3px 3px 0;
-}
-
-#login div.color a,
-#register div.color a {
-    width: 20px;
-    height: 20px;
-    display: block;
-    float: left;
-    margin: 0 0 0 3px;
-    padding: 0;
-}
-
-#login div.title h5,
-#register div.title h5 {
-    color: #fff;
-    margin: 10px;
-    padding: 0;
-}
-
-#login div.form div.form-horizontal div.form-group,
-#register div.form div.fields div.field {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 0 10px;
-}
-
-#login div.form div.form-horizontal div.form-group span.error-message,
-#register div.form div.fields div.field span.error-message {
-    height: 1%;
-    display: block;
-    color: red;
-    margin: 8px 0 0;
-    padding: 0;
-    max-width: 320px;
-}
-
-#login div.form div.form-horizontal div.form-group label,
-#register div.form div.fields div.field div.label label {
-    color: #000;
-    font-weight: 700;
-}
-
-#login div.form div.form-horizontal div.form-group div,
-#register div.form div.fields div.field div.input {
-    float: left;
-    margin: 0;
-    padding: 0;
-}
-
-#login div.form div.form-horizontal div.form-group div input.large {
-    width: 250px;
-}
-
-#login div.form div.form-horizontal div.form-group div.checkbox,
-#register div.form div.fields div.field div.checkbox {
-    margin: 0 0 0 184px;
-    padding: 0;
-}
-
-#login div.form div.form-horizontal div.form-group div.checkbox label,
-#register div.form div.fields div.field div.checkbox label {
-    color: #565656;
-    font-weight: 700;
-}
-
-#login div.form div.buttons input,
-#register div.form div.fields div.buttons input {
-    color: #000;
-    font-size: 1em;
-    font-weight: 700;
-    margin: 0;
-}
-
-#changeset_content .container .wrapper,
-#graph_content .container .wrapper {
-    width: 600px;
-}
-
-#changeset_content .container .date,
-.ac .match {
-    font-weight: 700;
-    padding-top: 5px;
-    padding-bottom: 5px;
-}
-
-div#legend_container table td,
-div#legend_choices table td {
-    border: none !important;
-    height: 20px !important;
-    padding: 0 !important;
-}
-
-.q_filter_box {
-    border-radius: 4px;
-    border: 0 none;
-    margin-bottom: -4px;
-    margin-top: -4px;
-    padding-left: 3px;
-}
-
-#node_filter {
-    border: 0px solid #545454;
-    color: #AAAAAA;
-    padding-left: 3px;
-}
-
-
-.group_members_wrap {
-    min-height: 85px;
-    padding-left: 20px;
-}
-
-.group_members .group_member {
-    height: 30px;
-    padding: 0px 0px 0px 0px;
-}
-
-.reviewer_status {
-    float: left;
-}
-
-.reviewers_member {
-    height: 15px;
-    padding: 0px 0px 0px 10px;
-}
-
-.emails_wrap {
-    padding: 0px 20px;
-}
-
-.emails_wrap .email_entry {
-    height: 30px;
-    padding: 0px 0px 0px 10px;
-}
-.emails_wrap .email_entry .email {
-    float: left
-}
-.emails_wrap .email_entry .email_action {
-    float: left
-}
-
-.ips_wrap {
-    padding: 0px 20px;
-}
-
-.ips_wrap .ip_entry {
-    height: 30px;
-    padding: 0px 0px 0px 10px;
-}
-.ips_wrap .ip_entry .ip {
-    float: left
-}
-.ips_wrap .ip_entry .ip_action {
-    float: left
-}
-
-
-/*README STYLE*/
-
-div.readme {
-    padding: 0px;
-}
-
-div.readme h2 {
-    font-weight: normal;
-}
-
-div.readme .readme_box {
-    background-color: #fafafa;
-}
-
-div.readme .readme_box {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 20px 10px;
-}
-
-div.readme .readme_box h1,
-div.readme .readme_box h2,
-div.readme .readme_box h3,
-div.readme .readme_box h4,
-div.readme .readme_box h5,
-div.readme .readme_box h6 {
-    border-bottom: 0 !important;
-    margin: 0 !important;
-    padding: 0 !important;
-    line-height: 1.5em !important;
-}
-
-
-div.readme .readme_box h1:first-child {
-    padding-top: .25em !important;
-}
-
-div.readme .readme_box h2,
-div.readme .readme_box h3 {
-    margin: 1em 0 !important;
-}
-
-div.readme .readme_box h2 {
-    margin-top: 1.5em !important;
-    border-top: 4px solid #e0e0e0 !important;
-    padding-top: .5em !important;
-}
-
-div.readme .readme_box p {
-    color: black !important;
-    margin: 1em 0 !important;
-    line-height: 1.5em !important;
-}
-
-div.readme .readme_box ul {
-    list-style: disc !important;
-    margin: 1em 0 1em 2em !important;
-}
-
-div.readme .readme_box ol {
-    list-style: decimal;
-    margin: 1em 0 1em 2em !important;
-}
-
-div.readme .readme_box code {
-    font-size: 12px !important;
-    background-color: ghostWhite !important;
-    color: #444 !important;
-    padding: 0 .2em !important;
-    border: 1px solid #dedede !important;
-}
-
-div.readme .readme_box pre code {
-    padding: 0 !important;
-    font-size: 12px !important;
-    background-color: #eee !important;
-    border: none !important;
-}
-
-div.readme .readme_box pre {
-    margin: 1em 0;
-    font-size: 12px;
-    background-color: #eee;
-    border: 1px solid #ddd;
-    padding: 5px;
-    color: #444;
-    overflow: auto;
-    border-radius: 3px;
-}
-
-div.readme .readme_box table {
-    display: table;
-    border-collapse: separate;
-    border-spacing: 2px;
-    border-color: gray;
-    width: auto !important;
-}
-
-
-/** RST STYLE **/
-
-
-div.rst-block {
-    padding: 0px;
-}
-
-div.rst-block h2 {
-    font-weight: normal;
-}
-
-div.rst-block {
-    background-color: #fafafa;
-}
-
-div.rst-block {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 20px;
-}
-
-div.rst-block h1,
-div.rst-block h2,
-div.rst-block h3,
-div.rst-block h4,
-div.rst-block h5,
-div.rst-block h6 {
-    border-bottom: 0 !important;
-    margin: 0 !important;
-    padding: 0 !important;
-    line-height: 1.5em !important;
-}
-
-
-div.rst-block h1:first-child {
-    padding-top: .25em !important;
-}
-
-div.rst-block h2,
-div.rst-block h3 {
-    margin: 1em 0 !important;
-}
-
-div.rst-block h2 {
-    margin-top: 1.5em !important;
-    border-top: 4px solid #e0e0e0 !important;
-    padding-top: .5em !important;
-}
-
-div.rst-block p {
-    color: black !important;
-    margin: 1em 0 !important;
-    line-height: 1.5em !important;
-}
-
-div.rst-block ul {
-    list-style: disc !important;
-    margin: 1em 0 1em 2em !important;
-}
-
-div.rst-block ol {
-    list-style: decimal;
-    margin: 1em 0 1em 2em !important;
-}
-
-div.rst-block code {
-    font-size: 12px !important;
-    background-color: ghostWhite !important;
-    color: #444 !important;
-    padding: 0 .2em !important;
-    border: 1px solid #dedede !important;
-}
-
-div.rst-block pre code {
-    padding: 0 !important;
-    font-size: 12px !important;
-    background-color: #eee !important;
-    border: none !important;
-}
-
-div.rst-block pre {
-    margin: 1em 0;
-    font-size: 12px;
-    background-color: #eee;
-    border: 1px solid #ddd;
-    padding: 5px;
-    color: #444;
-    overflow: auto;
-    border-radius: 3px;
-}
-
-
-/** comment main **/
-.comments {
-    padding: 10px 20px;
-    max-width: 978px;
-}
-
-.comments .comment .comment-wrapp {
-    border: 1px solid #ddd;
-    margin-top: 10px;
-    border-radius: 4px;
-}
-
-.comments .comment .meta {
-    background: #f8f8f8;
-    padding: 4px;
-    border-bottom: 1px solid #ddd;
-    min-height: 18px;
-    overflow: auto;
-}
-
-.comments .comment .meta img {
-    vertical-align: middle;
-}
-
-.comments .comment .meta .user {
-    font-weight: bold;
-    float: left;
-    padding: 4px 2px 2px 2px;
-}
-
-.comments .comment .meta .date {
-    float: left;
-    padding: 4px 4px 0px 4px;
-}
-
-.comments .comment .text {
-    background-color: #FAFAFA;
-}
-.comment .text div.rst-block p {
-    margin: 0.5em 0px !important;
-}
-
-.comments-number {
-    margin: 5px;
-    padding: 0px 0px 10px 0px;
-    font-weight: bold;
-    color: #666;
-    font-size: 16px;
-}
-
-.automatic-comment {
-    font-style: italic;
-}
-
-/** comment form **/
-
-.status-block {
-    margin: 5px;
-    clear: both
-}
-
-.comment-form textarea {
-    width: 100%;
-    height: 100px;
-    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
-}
-
-form.comment-form {
-    margin-top: 10px;
-    margin-left: 10px;
-}
-
-.comment-inline-form .comment-block-ta,
-.comment-form .comment-block-ta {
-    border: 1px solid #ccc;
-    border-radius: 3px;
-    box-sizing: border-box;
-}
-
-.comment-form-submit {
-    margin-top: 5px;
-    margin-left: 525px;
-}
-
-.file-comments {
-    display: none;
-}
-
-.comment-form .comment {
-    margin-left: 10px;
-}
-
-.comment-form .comment-help {
-    padding: 5px 5px 5px 5px;
-    color: #666;
-}
-.comment-form .comment-help .preview-btn,
-.comment-form .comment-help .edit-btn {
-    float: right;
-    margin: -6px 0px 0px 0px;
-}
-
-.comment-form .preview-box.unloaded,
-.comment-inline-form .preview-box.unloaded {
-    height: 50px;
-    text-align: center;
-    padding: 20px;
-    background-color: #fafafa;
-}
-
-.comment-form .comment-button {
-    padding-top: 5px;
-}
-
-.add-another-button {
-    margin-left: 10px;
-    margin-top: 10px;
-    margin-bottom: 10px;
-}
-
-.comment .buttons {
-    float: right;
-    margin: -1px 0px 0px 0px;
-}
-
-
-.show-inline-comments {
-    position: relative;
-    top: 1px
-}
-
-/** comment inline form **/
-.comment-inline-form {
-    margin: 4px;
-    max-width: 978px;
-}
-.comment-inline-form .submitting-overlay {
-    display: none;
-    height: 0;
-    text-align: center;
-    font-size: 16px;
-    opacity: 0.5;
-}
-
-.comment-inline-form .clearfix,
-.comment-form .clearfix {
-    background: #EEE;
-    border-radius: 4px;
-    padding: 5px;
-    margin: 0px;
-}
-
-div.comment-inline-form {
-    padding: 4px 0px 6px 0px;
-}
-
-.comment-inline-form textarea {
-    width: 100%;
-    height: 100px;
-    font-family: Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace;
-}
-
-form.comment-inline-form {
-    margin-top: 10px;
-    margin-left: 10px;
-}
-
-.comment-inline-form-submit {
-    margin-top: 5px;
-    margin-left: 525px;
-}
-
-.file-comments {
-    display: none;
-}
-
-.comment-inline-form .comment {
-    margin-left: 10px;
-}
-
-.comment-inline-form .comment-help {
-    padding: 5px 5px 5px 5px;
-    color: #666;
-}
-
-.comment-inline-form .comment-help .preview-btn,
-.comment-inline-form .comment-help .edit-btn {
-    float: right;
-    margin: -6px 0px 0px 0px;
-}
-
-.comment-inline-form .comment-button {
-    padding-top: 5px;
-}
-
-/** comment inline **/
-.inline-comments {
-    padding: 0 20px;
-}
-
-.inline-comments div.rst-block {
-    clear: both;
-    overflow: hidden;
-    margin: 0;
-    padding: 0 20px 0px;
-}
-
-.inline-comments .comment .comment-wrapp {
-    max-width: 978px;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-    margin: 3px 3px 5px 5px;
-    background-color: #FAFAFA;
-}
-
-.inline-comments .add-button-row {
-    padding: 2px 4px 8px 5px;
-}
-
-.inline-comments .comment .meta {
-    background: #f8f8f8;
-    padding: 4px;
-    border-bottom: 1px solid #ddd;
-    min-height: 20px;
-    overflow: auto;
-}
-
-.inline-comments .comment .meta img {
-    vertical-align: middle;
-}
-
-.inline-comments .comment .meta .user {
-    font-weight: bold;
-    float: left;
-    padding: 3px;
-}
-
-.inline-comments .comment .meta .date {
-    float: left;
-    padding: 3px;
-}
-
-.inline-comments .comment .text {
-    background-color: #FAFAFA;
-}
-
-.inline-comments .comments-number {
-    padding: 0px 0px 10px 0px;
-    font-weight: bold;
-    color: #666;
-    font-size: 16px;
-}
-.inline-comments-button .add-comment {
-    margin: 2px 0px 8px 5px !important
-}
-
-input.status_change_radio {
-    margin: 2px 0 5px 15px;
-    vertical-align: middle;
-}
-
-.notification-paginator {
-    padding: 0px 0px 4px 16px;
-}
-
-#context-pages .pull-request span,
-.menu_link_notifications {
-    padding: 4px 4px !important;
-    text-align: center;
-    color: #888 !important;
-    background-color: #DEDEDE !important;
-    border-radius: 4px !important;
-}
-
-#context-pages .forks span,
-.menu_link_notifications {
-    padding: 4px 4px !important;
-    text-align: center;
-    color: #888 !important;
-    background-color: #DEDEDE !important;
-    border-radius: 4px !important;
-}
-
-
-.notification-header {
-    padding-top: 6px;
-}
-.notification-header .desc {
-    font-size: 16px;
-    height: 24px;
-    float: left
-}
-.notification-list .container.unread {
-    background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
-}
-.notification-header .gravatar {
-    background: none repeat scroll 0 0 transparent;
-    padding: 0px 0px 0px 8px;
-}
-.notification-list .container .notification-header .desc {
-    font-weight: bold;
-    font-size: 17px;
-}
-.notification-table {
-    border: 1px solid #ccc;
-    border-radius: 6px 6px 6px 6px;
-    clear: both;
-    margin: 0px 20px 0px 20px;
-}
-.notification-header .delete-notifications {
-    float: right;
-    padding-top: 8px;
-    cursor: pointer;
-}
-.notification-header .read-notifications {
-    float: right;
-    padding-top: 8px;
-    cursor: pointer;
-}
-.notification-subject {
-    clear: both;
-    border-bottom: 1px solid #eee;
-    padding: 5px 0px 5px 38px;
-}
-
-.notification-body {
-    clear: both;
-    margin: 34px 2px 2px 8px
-}
-
-/****
-PULL REQUESTS
-*****/
-.pullrequests_section_head {
-    padding: 10px 10px 10px 0px;
-    margin: 0 20px;
-    font-size: 16px;
-    font-weight: bold;
-}
-
-div.pr-details-title.closed {
-    color: #555;
-    background: #eee;
-}
-
-div.pr-details-title {
-    font-size: 1.6em;
-    padding: 5px 0px 5px 10px;
-}
-
-div.pr {
-    margin: 0px 20px;
-    padding: 4px 4px;
-}
-div.pr-desc {
-    margin: 0px 20px;
-}
-tr.pr-closed td {
-    background-color: #eee !important;
-    color: #555 !important;
-}
-
-span.pr-closed-tag {
-    margin-bottom: 1px;
-    margin-right: 1px;
-    padding: 1px 3px;
-    font-size: 10px;
-    padding: 1px 3px 1px 3px;
-    font-size: 10px;
-    color: #577632;
-    white-space: nowrap;
-    border-radius: 4px;
-    border: 1px solid #d9e8f8;
-    line-height: 1.5em;
-}
-
-.pr-box {
-    max-width: 978px;
-}
-
-#s2id_org_ref,
-#s2id_other_ref,
-#s2id_org_repo,
-#s2id_other_repo {
-    min-width: 150px;
-    margin: 5px;
-}
-
-#pr-summary .msg-div {
-    margin: 5px 0;
-}
-
-/****
-  PERMS
-*****/
-#perms .perms_section_head {
-    padding: 10px 10px 10px 0px;
-    font-size: 16px;
-    font-weight: bold;
-    text-transform: capitalize;
-}
-
-#perms .perm_tag {
-    padding: 1px 3px 1px 3px;
-    font-size: 10px;
-    font-weight: bold;
-    text-transform: uppercase;
-    white-space: nowrap;
-    border-radius: 3px;
-}
-
-#perms .perm_tag.admin {
-    background-color: #B94A48;
-    color: #ffffff;
-}
-
-#perms .perm_tag.write {
-    background-color: #DB7525;
-    color: #ffffff;
-}
-
-#perms .perm_tag.read {
-    background-color: #468847;
-    color: #ffffff;
-}
-
-#perms .perm_tag.none {
-    background-color: #bfbfbf;
-    color: #ffffff;
-}
-
-.perm-gravatar {
-    vertical-align: middle;
-    padding: 2px;
-}
-.perm-gravatar-ac {
-    vertical-align: middle;
-    padding: 2px;
-    width: 14px;
-    height: 14px;
-}
-
-/*****************************************************************************
-                                  DIFFS CSS
-******************************************************************************/
-.diff-collapse {
-    text-align: center;
-    margin-bottom: -15px;
-}
-.diff-collapse-button {
-    cursor: pointer;
-    color: #666;
-    font-size: 16px;
-}
-.diff-container {
-
-}
-
-.diff-container.hidden {
-    display: none;
-    overflow: hidden;
-}
-
-
-div.diffblock {
-    overflow: auto;
-    padding: 0px;
-    border: 1px solid #ccc;
-    background: #f8f8f8;
-    font-size: 100%;
-    line-height: 100%;
-    /* new */
-    line-height: 125%;
-    border-radius: 6px 6px 0px 0px;
-}
-div.diffblock.margined {
-    margin: 0px 20px 0px 20px;
-}
-div.diffblock .code-header {
-    border-bottom: 1px solid #CCCCCC;
-    background: #EEEEEE;
-    padding: 10px 0 10px 0;
-    min-height: 14px;
-}
-
-div.diffblock .code-header.banner {
-    border-bottom: 1px solid #CCCCCC;
-    background: #EEEEEE;
-    height: 14px;
-    margin: 0;
-    padding: 3px 100px 11px 100px;
-}
-
-div.diffblock .code-header-title {
-    padding: 0px 0px 10px 5px !important;
-    margin: 0 !important;
-}
-div.diffblock .code-header .hash {
-    float: left;
-    padding: 2px 0 0 2px;
-}
-div.diffblock .code-header .date {
-    float: left;
-    text-transform: uppercase;
-    padding: 2px 0px 0px 2px;
-}
-div.diffblock .code-header div {
-    margin-left: 4px;
-    font-weight: bold;
-    font-size: 14px;
-}
-
-div.diffblock .parents {
-    float: left;
-    min-height: 26px;
-    font-size: 10px;
-    font-weight: 400;
-    vertical-align: middle;
-    padding: 0px 2px 2px 2px;
-    background-color: #eeeeee;
-    border-bottom: 1px solid #CCCCCC;
-}
-
-div.diffblock .children {
-    float: right;
-    min-height: 26px;
-    font-size: 10px;
-    font-weight: 400;
-    vertical-align: middle;
-    text-align: right;
-    padding: 0px 2px 2px 2px;
-    background-color: #eeeeee;
-    border-bottom: 1px solid #CCCCCC;
-}
-
-div.diffblock .code-body {
-    background: #FFFFFF;
-    clear: both;
-}
-div.diffblock pre.raw {
-    background: #FFFFFF;
-    color: #000000;
-}
-table.code-difftable {
-    border-collapse: collapse;
-    width: 99%;
-    border-radius: 0px !important;
-}
-table.code-difftable td {
-    padding: 0 !important;
-    background: none !important;
-    border: 0 !important;
-    vertical-align: baseline !important
-}
-table.code-difftable .context {
-    background: none repeat scroll 0 0 #DDE7EF;
-    color: #999;
-}
-table.code-difftable .add {
-    background: none repeat scroll 0 0 #DDFFDD;
-}
-table.code-difftable .add ins {
-    background: none repeat scroll 0 0 #AAFFAA;
-    text-decoration: none;
-}
-table.code-difftable .del {
-    background: none repeat scroll 0 0 #FFDDDD;
-}
-table.code-difftable .del del {
-    background: none repeat scroll 0 0 #FFAAAA;
-    text-decoration: none;
-}
-
-table.code-highlighttable div.code-highlight pre u:before,
-table.code-difftable td.code pre u:before {
-    content: "\21a6";
-    display: inline-block;
-    width: 0;
-}
-table.code-highlighttable div.code-highlight pre u.cr:before,
-table.code-difftable td.code pre u.cr:before {
-    content: "\21a4";
-    display: inline-block;
-    color: rgba(0,0,0,0.5);
-}
-table.code-highlighttable div.code-highlight pre u,
-table.code-difftable td.code pre u {
-    color: rgba(0,0,0,0.15);
-}
-table.code-highlighttable div.code-highlight pre i,
-table.code-difftable td.code pre i {
-    border-style: solid;
-    border-left-width: 1px;
-    color: rgba(0,0,0,0.5);
-}
-
-/** LINE NUMBERS **/
-table.code-difftable .lineno {
-    padding-left: 2px;
-    padding-right: 2px !important;
-    width: 30px;
-    -moz-user-select: none;
-    -webkit-user-select: none;
-    border-right: 1px solid #CCC !important;
-    border-left: 0px solid #CCC !important;
-    border-top: 0px solid #CCC !important;
-    border-bottom: none !important;
-    vertical-align: middle !important;
-    text-align: center;
-}
-table.code-difftable .lineno.new {
-    text-align: right;
-}
-table.code-difftable .lineno.old {
-    text-align: right;
-}
-table.code-difftable .lineno a {
-    color: #aaa !important;
-    font: 11px Lucida Console, Consolas, Monaco, Inconsolata, Liberation Mono, monospace !important;
-    letter-spacing: -1px;
-    padding-left: 10px;
-    padding-right: 8px;
-    box-sizing: border-box;
-    cursor: pointer;
-    display: block;
-    width: 100%;
-}
-
-table.code-difftable .lineno-inline {
-    background: none repeat scroll 0 0 #FFF !important;
-    padding-left: 2px;
-    padding-right: 2px;
-    text-align: right;
-    width: 30px;
-    -moz-user-select: none;
-    -webkit-user-select: none;
-}
-
-/** CODE **/
-table.code-difftable .code {
-    display: block;
-    width: 100%;
-}
-table.code-difftable .code td {
-    margin: 0;
-    padding: 0;
-}
-table.code-difftable .code pre {
-    margin: 0 0 0 12px;
-    padding: 0;
-    min-height: 17px;
-    line-height: 17px;
-    white-space: pre-wrap;
-}
-
-table.code-difftable .del .code pre:before {
-    content: "-";
-    color: #800;
-    float: left;
-    left: -1em;
-    position: relative;
-    width: 0;
-}
-
-table.code-difftable .add .code pre:before {
-    content: "+";
-    color: #080;
-    float: left;
-    left: -1em;
-    position: relative;
-    width: 0;
-}
-
-table.code-difftable .unmod .code pre:before {
-    content: " ";
-    float: left;
-    left: -1em;
-    position: relative;
-    width: 0;
-}
-
-.add-bubble {
-    position: relative;
-    display: none;
-    float: left;
-    width: 0px;
-    height: 0px;
-    left: -8px;
-    box-sizing: border-box;
-}
-
-/* comment bubble, only visible when in a commentable diff */
-.commentable-diff tr.line.add:hover td .add-bubble,
-.commentable-diff tr.line.del:hover td .add-bubble,
-.commentable-diff tr.line.unmod:hover td .add-bubble {
-    display: block;
-    z-index: 1;
-}
-
-.add-bubble div {
-    background: #577632;
-    width: 16px;
-    height: 16px;
-    cursor: pointer;
-    padding: 0 2px 2px 0.5px;
-    border: 1px solid #577632;
-    border-radius: 3px;
-    box-sizing: border-box;
-}
-
-.add-bubble div:before {
-    font-size: 14px;
-    color: #ffffff;
-    font-family: "kallithea";
-    content: '\1f5ea';
-}
-
-.add-bubble div:hover {
-    transform: scale(1.2, 1.2);
-}
-
-/* show some context of link targets - but only works when the link target
-   can be extended with any visual difference */
-div.comment:target:before {
-  display: block;
-  height: 100px;
-  margin: -100px 0 0;
-  content: "";
-}
-
-div.comment:target>.comment-wrapp {
-    border: solid 2px #ee0 !important;
-    margin: 2px 2px 4px 4px;
-}
-
-.lineno:target a {
-    border: solid 2px #ee0 !important;
-    margin: -2px;
-}
-
-.btn-image-diff-show,
-.btn-image-diff-swap {
-    margin: 5px;
-}
-
-.img-diff {
-    max-width: 45%;
-    height: auto;
-    margin: 5px;
-    /* http://lea.verou.me/demos/css3-patterns.html */
-    background-image:
-        linear-gradient(45deg, #888 25%, transparent 25%, transparent),
-        linear-gradient(-45deg, #888 25%, transparent 25%, transparent),
-        linear-gradient(45deg, transparent 75%, #888 75%),
-        linear-gradient(-45deg, transparent 75%, #888 75%);
-    background-size: 10px 10px;
-    background-color: #999;
-}
-
-.img-preview {
-    max-width: 100%;
-    height: auto;
-    margin: 5px;
-}
-
-div.comment-prev-next-links div.prev-comment,
-div.comment-prev-next-links div.next-comment {
-    display: inline-block;
-    min-width: 150px;
-    margin: 3px 6px;
-}
-
-#help_kb {
-    display: none;
-}
-
-.icon-only-links i {
-    color: white;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/public/fontello/LICENSE.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,12 @@
+Font license info
+
+
+## Font Awesome
+
+   Copyright (C) 2016 by Dave Gandy
+
+   Author:    Dave Gandy
+   License:   SIL ()
+   Homepage:  http://fortawesome.github.com/Font-Awesome/
+
+
--- a/kallithea/public/fontello/README.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/public/fontello/README.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -2,16 +2,16 @@
 
 
 ================================================================================
-Please, note, that you should obey original font licences, used to make this
+Please, note, that you should obey original font licenses, used to make this
 webfont pack. Details available in LICENSE.txt file.
 
 - Usually, it's enough to publish content of LICENSE.txt file somewhere on your
   site in "About" section.
 
 - If your project is open-source, usually, it will be ok to make LICENSE.txt
-  file publically available in your repository.
+  file publicly available in your repository.
 
-- Fonts, used in Fontello, don't require to make clickable links on your site.
+- Fonts, used in Fontello, don't require a clickable link on your site.
   But any kind of additional authors crediting is welcome.
 ================================================================================
 
@@ -29,8 +29,8 @@
 
 - LICENSE.txt - license info about source fonts, used to build your one.
 
-- config.json - keeps your settings. You can import it back to fontello anytime,
-  to continue your work
+- config.json - keeps your settings. You can import it back into fontello
+  anytime, to continue your work
 
 
 Why so many CSS files ?
@@ -38,17 +38,17 @@
 
 Because we like to fit all your needs :)
 
-- basic file, <your_font_name>.css - is usually enougth, in contains @font-face
-  and character codes definition
+- basic file, <your_font_name>.css - is usually enough, it contains @font-face
+  and character code definitions
 
 - *-ie7.css - if you need IE7 support, but still don't wish to put char codes
   directly into html
 
 - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
-  rules, but still wish to benefit of css generation. That can be very
-  convenient for automated assets build systems. When you need to update font -
-  no needs to manually edit files, just override old version with archive
-  content. See fontello source codes for example.
+  rules, but still wish to benefit from css generation. That can be very
+  convenient for automated asset build systems. When you need to update font -
+  no need to manually edit files, just override old version with archive
+  content. See fontello source code for examples.
 
 - *-embedded.css - basic css file, but with embedded WOFF font, to avoid
   CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
@@ -63,11 +63,11 @@
 Attention for server setup
 --------------------------
 
-You MUST setup server to reply with proper `mime-types` for font files. In other
-case, some browsers will fail to show fonts.
+You MUST setup server to reply with proper `mime-types` for font files -
+otherwise some browsers will fail to show fonts.
 
 Usually, `apache` already has necessary settings, but `nginx` and other
-webservers should be tuned. Here is list of mime types for our file extentions:
+webservers should be tuned. Here is list of mime types for our file extensions:
 
 - `application/vnd.ms-fontobject` - eot
 - `application/x-font-woff` - woff
--- a/kallithea/public/fontello/css/kallithea.css	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/public/fontello/css/kallithea.css	Sun Mar 31 21:28:56 2019 +0200
@@ -2,11 +2,14 @@
 
  @font-face {
   font-family: 'kallithea';
-  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');
+  /* NOTE: relative font paths have been adjusted to compensate for where the */
+  /*       content of this CSS file ends up being used */
+  src: url('../fontello/font/kallithea.eot?96450582');
+  src: url('../fontello/font/kallithea.eot?96450582#iefix') format('embedded-opentype'),
+       url('../fontello/font/kallithea.woff2?96450582') format('woff2'),
+       url('../fontello/font/kallithea.woff?96450582') format('woff'),
+       url('../fontello/font/kallithea.ttf?96450582') format('truetype'),
+       url('../fontello/font/kallithea.svg?96450582#kallithea') format('svg');
   font-weight: normal;
   font-style: normal;
 }
@@ -16,7 +19,7 @@
 @media screen and (-webkit-min-device-pixel-ratio:0) {
   @font-face {
     font-family: 'kallithea';
-    src: url('../font/kallithea.svg?23730278#kallithea') format('svg');
+    src: url('../fontello/font/kallithea.svg?96450582#kallithea') format('svg');
   }
 }
 */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/public/fontello/demo.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,459 @@
+<!DOCTYPE html>
+<html>
+  <head><!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
+    <meta charset="UTF-8"><style>/*
+ * Bootstrap v2.2.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+.clearfix {
+  *zoom: 1;
+}
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.clearfix:after {
+  clear: both;
+}
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+}
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+a:hover,
+a:active {
+  outline: 0;
+}
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+body {
+  margin: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333;
+  background-color: #fff;
+}
+a {
+  color: #08c;
+  text-decoration: none;
+}
+a:hover {
+  color: #005580;
+  text-decoration: underline;
+}
+.row {
+  margin-left: -20px;
+  *zoom: 1;
+}
+.row:before,
+.row:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.row:after {
+  clear: both;
+}
+[class*="span"] {
+  float: left;
+  min-height: 1px;
+  margin-left: 20px;
+}
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+.span12 {
+  width: 940px;
+}
+.span11 {
+  width: 860px;
+}
+.span10 {
+  width: 780px;
+}
+.span9 {
+  width: 700px;
+}
+.span8 {
+  width: 620px;
+}
+.span7 {
+  width: 540px;
+}
+.span6 {
+  width: 460px;
+}
+.span5 {
+  width: 380px;
+}
+.span4 {
+  width: 300px;
+}
+.span3 {
+  width: 220px;
+}
+.span2 {
+  width: 140px;
+}
+.span1 {
+  width: 60px;
+}
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+  float: right;
+}
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  *zoom: 1;
+}
+.container:before,
+.container:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.container:after {
+  clear: both;
+}
+p {
+  margin: 0 0 10px;
+}
+.lead {
+  margin-bottom: 20px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 30px;
+}
+small {
+  font-size: 85%;
+}
+h1 {
+  margin: 10px 0;
+  font-family: inherit;
+  font-weight: bold;
+  line-height: 20px;
+  color: inherit;
+  text-rendering: optimizelegibility;
+}
+h1 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999;
+}
+h1 {
+  line-height: 40px;
+}
+h1 {
+  font-size: 38.5px;
+}
+h1 small {
+  font-size: 24.5px;
+}
+body {
+  margin-top: 90px;
+}
+.header {
+  position: fixed;
+  top: 0;
+  left: 50%;
+  margin-left: -480px;
+  background-color: #fff;
+  border-bottom: 1px solid #ddd;
+  padding-top: 10px;
+  z-index: 10;
+}
+.footer {
+  color: #ddd;
+  font-size: 12px;
+  text-align: center;
+  margin-top: 20px;
+}
+.footer a {
+  color: #ccc;
+  text-decoration: underline;
+}
+.the-icons {
+  font-size: 14px;
+  line-height: 24px;
+}
+.switch {
+  position: absolute;
+  right: 0;
+  bottom: 10px;
+  color: #666;
+}
+.switch input {
+  margin-right: 0.3em;
+}
+.codesOn .i-name {
+  display: none;
+}
+.codesOn .i-code {
+  display: inline;
+}
+.i-code {
+  display: none;
+}
+@font-face {
+      font-family: 'kallithea';
+      src: url('./font/kallithea.eot?61441093');
+      src: url('./font/kallithea.eot?61441093#iefix') format('embedded-opentype'),
+           url('./font/kallithea.woff?61441093') format('woff'),
+           url('./font/kallithea.ttf?61441093') format('truetype'),
+           url('./font/kallithea.svg?61441093#kallithea') format('svg');
+      font-weight: normal;
+      font-style: normal;
+    }
+     
+     
+    .demo-icon
+    {
+      font-family: "kallithea";
+      font-style: normal;
+      font-weight: normal;
+      speak: none;
+     
+      display: inline-block;
+      text-decoration: inherit;
+      width: 1em;
+      margin-right: .2em;
+      text-align: center;
+      /* opacity: .8; */
+     
+      /* 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;
+     
+      /* Animation center compensation - margins should be symmetric */
+      /* remove if not needed */
+      margin-left: .2em;
+     
+      /* 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); */
+    }
+     </style>
+    <link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/kallithea-ie7.css"><![endif]-->
+    <script>
+      function toggleCodes(on) {
+        var obj = document.getElementById('icons');
+      
+        if (on) {
+          obj.className += ' codesOn';
+        } else {
+          obj.className = obj.className.replace(' codesOn', '');
+        }
+      }
+      
+    </script>
+  </head>
+  <body>
+    <div class="container header">
+      <h1>
+        kallithea
+         <small>font demo</small>
+      </h1>
+      <label class="switch">
+        <input type="checkbox" onclick="toggleCodes(this.checked)">show codes
+      </label>
+    </div>
+    <div id="icons" class="container">
+      <div class="row">
+        <div title="Code: 0x2022" class="the-icons span3"><i class="demo-icon icon-circle">&#x2022;</i> <span class="i-name">icon-circle</span><span class="i-code">0x2022</span></div>
+        <div title="Code: 0x2190" class="the-icons span3"><i class="demo-icon icon-right-open">&#x2190;</i> <span class="i-name">icon-right-open</span><span class="i-code">0x2190</span></div>
+        <div title="Code: 0x2191" class="the-icons span3"><i class="demo-icon icon-up-open">&#x2191;</i> <span class="i-name">icon-up-open</span><span class="i-code">0x2191</span></div>
+        <div title="Code: 0x2192" class="the-icons span3"><i class="demo-icon icon-left-open">&#x2192;</i> <span class="i-name">icon-left-open</span><span class="i-code">0x2192</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x2193" class="the-icons span3"><i class="demo-icon icon-down-open">&#x2193;</i> <span class="i-name">icon-down-open</span><span class="i-code">0x2193</span></div>
+        <div title="Code: 0x2194" class="the-icons span3"><i class="demo-icon icon-code">&#x2194;</i> <span class="i-name">icon-code</span><span class="i-code">0x2194</span></div>
+        <div title="Code: 0x2195" class="the-icons span3"><i class="demo-icon icon-sort">&#x2195;</i> <span class="i-name">icon-sort</span><span class="i-code">0x2195</span></div>
+        <div title="Code: 0x21c4" class="the-icons span3"><i class="demo-icon icon-exchange">&#x21c4;</i> <span class="i-name">icon-exchange</span><span class="i-code">0x21c4</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x2295" class="the-icons span3"><i class="demo-icon icon-plus-circled">&#x2295;</i> <span class="i-name">icon-plus-circled</span><span class="i-code">0x2295</span></div>
+        <div title="Code: 0x2296" class="the-icons span3"><i class="demo-icon icon-minus-circled">&#x2296;</i> <span class="i-name">icon-minus-circled</span><span class="i-code">0x2296</span></div>
+        <div title="Code: 0x229e" class="the-icons span3"><i class="demo-icon icon-diff-added">&#x229e;</i> <span class="i-name">icon-diff-added</span><span class="i-code">0x229e</span></div>
+        <div title="Code: 0x229f" class="the-icons span3"><i class="demo-icon icon-diff-removed">&#x229f;</i> <span class="i-name">icon-diff-removed</span><span class="i-code">0x229f</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x22a1" class="the-icons span3"><i class="demo-icon icon-diff-modified">&#x22a1;</i> <span class="i-name">icon-diff-modified</span><span class="i-code">0x22a1</span></div>
+        <div title="Code: 0x231a" class="the-icons span3"><i class="demo-icon icon-clock">&#x231a;</i> <span class="i-name">icon-clock</span><span class="i-code">0x231a</span></div>
+        <div title="Code: 0x23f4" class="the-icons span3"><i class="demo-icon icon-triangle-left">&#x23f4;</i> <span class="i-name">icon-triangle-left</span><span class="i-code">0x23f4</span></div>
+        <div title="Code: 0x23f5" class="the-icons span3"><i class="demo-icon icon-triangle-right">&#x23f5;</i> <span class="i-name">icon-triangle-right</span><span class="i-code">0x23f5</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x23f6" class="the-icons span3"><i class="demo-icon icon-triangle-up">&#x23f6;</i> <span class="i-name">icon-triangle-up</span><span class="i-code">0x23f6</span></div>
+        <div title="Code: 0x23f7" class="the-icons span3"><i class="demo-icon icon-triangle-down">&#x23f7;</i> <span class="i-name">icon-triangle-down</span><span class="i-code">0x23f7</span></div>
+        <div title="Code: 0x25e6" class="the-icons span3"><i class="demo-icon icon-circle-empty">&#x25e6;</i> <span class="i-name">icon-circle-empty</span><span class="i-code">0x25e6</span></div>
+        <div title="Code: 0x2661" class="the-icons span3"><i class="demo-icon icon-heart-empty">&#x2661;</i> <span class="i-name">icon-heart-empty</span><span class="i-code">0x2661</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x2665" class="the-icons span3"><i class="demo-icon icon-heart">&#x2665;</i> <span class="i-name">icon-heart</span><span class="i-code">0x2665</span></div>
+        <div title="Code: 0x2699" class="the-icons span3"><i class="demo-icon icon-cog-alt">&#x2699;</i> <span class="i-name">icon-cog-alt</span><span class="i-code">0x2699</span></div>
+        <div title="Code: 0x26c3" class="the-icons span3"><i class="demo-icon icon-database">&#x26c3;</i> <span class="i-name">icon-database</span><span class="i-code">0x26c3</span></div>
+        <div title="Code: 0x26ed" class="the-icons span3"><i class="demo-icon icon-gear">&#x26ed;</i> <span class="i-name">icon-gear</span><span class="i-code">0x26ed</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x2795" class="the-icons span3"><i class="demo-icon icon-plus">&#x2795;</i> <span class="i-name">icon-plus</span><span class="i-code">0x2795</span></div>
+        <div title="Code: 0x2796" class="the-icons span3"><i class="demo-icon icon-minus">&#x2796;</i> <span class="i-name">icon-minus</span><span class="i-code">0x2796</span></div>
+        <div title="Code: 0x2b65" class="the-icons span3"><i class="demo-icon icon-resize-vertical">&#x2b65;</i> <span class="i-name">icon-resize-vertical</span><span class="i-code">0x2b65</span></div>
+        <div title="Code: 0x2b71" class="the-icons span3"><i class="demo-icon icon-move-up">&#x2b71;</i> <span class="i-name">icon-move-up</span><span class="i-code">0x2b71</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x2b73" class="the-icons span3"><i class="demo-icon icon-move-down">&#x2b73;</i> <span class="i-name">icon-move-down</span><span class="i-code">0x2b73</span></div>
+        <div title="Code: 0xe801" class="the-icons span3"><i class="demo-icon icon-file-submodule">&#xe801;</i> <span class="i-name">icon-file-submodule</span><span class="i-code">0xe801</span></div>
+        <div title="Code: 0xe802" class="the-icons span3"><i class="demo-icon icon-git-merge">&#xe802;</i> <span class="i-name">icon-git-merge</span><span class="i-code">0xe802</span></div>
+        <div title="Code: 0xe811" class="the-icons span3"><i class="demo-icon icon-ruler">&#xe811;</i> <span class="i-name">icon-ruler</span><span class="i-code">0xe811</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe813" class="the-icons span3"><i class="demo-icon icon-download-cloud">&#xe813;</i> <span class="i-name">icon-download-cloud</span><span class="i-code">0xe813</span></div>
+        <div title="Code: 0xe814" class="the-icons span3"><i class="demo-icon icon-upload-cloud">&#xe814;</i> <span class="i-name">icon-upload-cloud</span><span class="i-code">0xe814</span></div>
+        <div title="Code: 0xe815" class="the-icons span3"><i class="demo-icon icon-graph">&#xe815;</i> <span class="i-name">icon-graph</span><span class="i-code">0xe815</span></div>
+        <div title="Code: 0xe816" class="the-icons span3"><i class="demo-icon icon-file-zip">&#xe816;</i> <span class="i-name">icon-file-zip</span><span class="i-code">0xe816</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe81a" class="the-icons span3"><i class="demo-icon icon-file-code">&#xe81a;</i> <span class="i-name">icon-file-code</span><span class="i-code">0xe81a</span></div>
+        <div title="Code: 0xe81c" class="the-icons span3"><i class="demo-icon icon-doc-text-inv">&#xe81c;</i> <span class="i-name">icon-doc-text-inv</span><span class="i-code">0xe81c</span></div>
+        <div title="Code: 0xe81d" class="the-icons span3"><i class="demo-icon icon-diff">&#xe81d;</i> <span class="i-name">icon-diff</span><span class="i-code">0xe81d</span></div>
+        <div title="Code: 0xe81e" class="the-icons span3"><i class="demo-icon icon-diff-ignored">&#xe81e;</i> <span class="i-name">icon-diff-ignored</span><span class="i-code">0xe81e</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe81f" class="the-icons span3"><i class="demo-icon icon-diff-renamed">&#xe81f;</i> <span class="i-name">icon-diff-renamed</span><span class="i-code">0xe81f</span></div>
+        <div title="Code: 0xe820" class="the-icons span3"><i class="demo-icon icon-paste">&#xe820;</i> <span class="i-name">icon-paste</span><span class="i-code">0xe820</span></div>
+        <div title="Code: 0xe823" class="the-icons span3"><i class="demo-icon icon-file-directory">&#xe823;</i> <span class="i-name">icon-file-directory</span><span class="i-code">0xe823</span></div>
+        <div title="Code: 0xe824" class="the-icons span3"><i class="demo-icon icon-git-compare">&#xe824;</i> <span class="i-name">icon-git-compare</span><span class="i-code">0xe824</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe825" class="the-icons span3"><i class="demo-icon icon-git-pull-request">&#xe825;</i> <span class="i-name">icon-git-pull-request</span><span class="i-code">0xe825</span></div>
+        <div title="Code: 0xe827" class="the-icons span3"><i class="demo-icon icon-repo-forked">&#xe827;</i> <span class="i-name">icon-repo-forked</span><span class="i-code">0xe827</span></div>
+        <div title="Code: 0xe828" class="the-icons span3"><i class="demo-icon icon-fork">&#xe828;</i> <span class="i-name">icon-fork</span><span class="i-code">0xe828</span></div>
+        <div title="Code: 0xe834" class="the-icons span3"><i class="demo-icon icon-docs">&#xe834;</i> <span class="i-name">icon-docs</span><span class="i-code">0xe834</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe836" class="the-icons span3"><i class="demo-icon icon-doc-inv">&#xe836;</i> <span class="i-name">icon-doc-inv</span><span class="i-code">0xe836</span></div>
+        <div title="Code: 0xe837" class="the-icons span3"><i class="demo-icon icon-file-powerpoint">&#xe837;</i> <span class="i-name">icon-file-powerpoint</span><span class="i-code">0xe837</span></div>
+        <div title="Code: 0xe83d" class="the-icons span3"><i class="demo-icon icon-tags">&#xe83d;</i> <span class="i-name">icon-tags</span><span class="i-code">0xe83d</span></div>
+        <div title="Code: 0xe83f" class="the-icons span3"><i class="demo-icon icon-bookmark-empty">&#xe83f;</i> <span class="i-name">icon-bookmark-empty</span><span class="i-code">0xe83f</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe840" class="the-icons span3"><i class="demo-icon icon-bookmark">&#xe840;</i> <span class="i-name">icon-bookmark</span><span class="i-code">0xe840</span></div>
+        <div title="Code: 0xe841" class="the-icons span3"><i class="demo-icon icon-align-left">&#xe841;</i> <span class="i-name">icon-align-left</span><span class="i-code">0xe841</span></div>
+        <div title="Code: 0xe843" class="the-icons span3"><i class="demo-icon icon-sliders">&#xe843;</i> <span class="i-name">icon-sliders</span><span class="i-code">0xe843</span></div>
+        <div title="Code: 0xe851" class="the-icons span3"><i class="demo-icon icon-eye-off">&#xe851;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe851</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe855" class="the-icons span3"><i class="demo-icon icon-rss">&#xe855;</i> <span class="i-name">icon-rss</span><span class="i-code">0xe855</span></div>
+        <div title="Code: 0xe856" class="the-icons span3"><i class="demo-icon icon-rss-squared">&#xe856;</i> <span class="i-name">icon-rss-squared</span><span class="i-code">0xe856</span></div>
+        <div title="Code: 0xe859" class="the-icons span3"><i class="demo-icon icon-strike">&#xe859;</i> <span class="i-name">icon-strike</span><span class="i-code">0xe859</span></div>
+        <div title="Code: 0xe85e" class="the-icons span3"><i class="demo-icon icon-box">&#xe85e;</i> <span class="i-name">icon-box</span><span class="i-code">0xe85e</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe85f" class="the-icons span3"><i class="demo-icon icon-right">&#xe85f;</i> <span class="i-name">icon-right</span><span class="i-code">0xe85f</span></div>
+        <div title="Code: 0xe860" class="the-icons span3"><i class="demo-icon icon-left">&#xe860;</i> <span class="i-name">icon-left</span><span class="i-code">0xe860</span></div>
+        <div title="Code: 0xe861" class="the-icons span3"><i class="demo-icon icon-left-circled">&#xe861;</i> <span class="i-name">icon-left-circled</span><span class="i-code">0xe861</span></div>
+        <div title="Code: 0xe862" class="the-icons span3"><i class="demo-icon icon-right-circled">&#xe862;</i> <span class="i-name">icon-right-circled</span><span class="i-code">0xe862</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0xe863" class="the-icons span3"><i class="demo-icon icon-up-circled">&#xe863;</i> <span class="i-name">icon-up-circled</span><span class="i-code">0xe863</span></div>
+        <div title="Code: 0xe864" class="the-icons span3"><i class="demo-icon icon-up">&#xe864;</i> <span class="i-name">icon-up</span><span class="i-code">0xe864</span></div>
+        <div title="Code: 0xe865" class="the-icons span3"><i class="demo-icon icon-down">&#xe865;</i> <span class="i-name">icon-down</span><span class="i-code">0xe865</span></div>
+        <div title="Code: 0xe866" class="the-icons span3"><i class="demo-icon icon-down-circled">&#xe866;</i> <span class="i-name">icon-down-circled</span><span class="i-code">0xe866</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f30e" class="the-icons span3"><i class="demo-icon icon-globe">&#x1f30e;</i> <span class="i-name">icon-globe</span><span class="i-code">0x1f30e</span></div>
+        <div title="Code: 0x1f441" class="the-icons span3"><i class="demo-icon icon-eye">&#x1f441;</i> <span class="i-name">icon-eye</span><span class="i-code">0x1f441</span></div>
+        <div title="Code: 0x1f464" class="the-icons span3"><i class="demo-icon icon-user">&#x1f464;</i> <span class="i-name">icon-user</span><span class="i-code">0x1f464</span></div>
+        <div title="Code: 0x1f465" class="the-icons span3"><i class="demo-icon icon-users">&#x1f465;</i> <span class="i-name">icon-users</span><span class="i-code">0x1f465</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f4be" class="the-icons span3"><i class="demo-icon icon-floppy">&#x1f4be;</i> <span class="i-name">icon-floppy</span><span class="i-code">0x1f4be</span></div>
+        <div title="Code: 0x1f4c1" class="the-icons span3"><i class="demo-icon icon-folder-empty">&#x1f4c1;</i> <span class="i-name">icon-folder-empty</span><span class="i-code">0x1f4c1</span></div>
+        <div title="Code: 0x1f4c2" class="the-icons span3"><i class="demo-icon icon-folder-open-empty">&#x1f4c2;</i> <span class="i-name">icon-folder-open-empty</span><span class="i-code">0x1f4c2</span></div>
+        <div title="Code: 0x1f4cb" class="the-icons span3"><i class="demo-icon icon-clippy">&#x1f4cb;</i> <span class="i-name">icon-clippy</span><span class="i-code">0x1f4cb</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f4d2" class="the-icons span3"><i class="demo-icon icon-book">&#x1f4d2;</i> <span class="i-name">icon-book</span><span class="i-code">0x1f4d2</span></div>
+        <div title="Code: 0x1f50d" class="the-icons span3"><i class="demo-icon icon-search">&#x1f50d;</i> <span class="i-name">icon-search</span><span class="i-code">0x1f50d</span></div>
+        <div title="Code: 0x1f510" class="the-icons span3"><i class="demo-icon icon-keyhole-circled">&#x1f510;</i> <span class="i-name">icon-keyhole-circled</span><span class="i-code">0x1f510</span></div>
+        <div title="Code: 0x1f511" class="the-icons span3"><i class="demo-icon icon-key">&#x1f511;</i> <span class="i-name">icon-key</span><span class="i-code">0x1f511</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f512" class="the-icons span3"><i class="demo-icon icon-lock">&#x1f512;</i> <span class="i-name">icon-lock</span><span class="i-code">0x1f512</span></div>
+        <div title="Code: 0x1f513" class="the-icons span3"><i class="demo-icon icon-lock-open-alt">&#x1f513;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0x1f513</span></div>
+        <div title="Code: 0x1f516" class="the-icons span3"><i class="demo-icon icon-tag">&#x1f516;</i> <span class="i-name">icon-tag</span><span class="i-code">0x1f516</span></div>
+        <div title="Code: 0x1f527" class="the-icons span3"><i class="demo-icon icon-wrench">&#x1f527;</i> <span class="i-name">icon-wrench</span><span class="i-code">0x1f527</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f589" class="the-icons span3"><i class="demo-icon icon-pencil">&#x1f589;</i> <span class="i-name">icon-pencil</span><span class="i-code">0x1f589</span></div>
+        <div title="Code: 0x1f58a" class="the-icons span3"><i class="demo-icon icon-pencil-squared">&#x1f58a;</i> <span class="i-name">icon-pencil-squared</span><span class="i-code">0x1f58a</span></div>
+        <div title="Code: 0x1f58b" class="the-icons span3"><i class="demo-icon icon-edit">&#x1f58b;</i> <span class="i-name">icon-edit</span><span class="i-code">0x1f58b</span></div>
+        <div title="Code: 0x1f5c0" class="the-icons span3"><i class="demo-icon icon-folder">&#x1f5c0;</i> <span class="i-name">icon-folder</span><span class="i-code">0x1f5c0</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f5c1" class="the-icons span3"><i class="demo-icon icon-folder-open">&#x1f5c1;</i> <span class="i-name">icon-folder-open</span><span class="i-code">0x1f5c1</span></div>
+        <div title="Code: 0x1f5c5" class="the-icons span3"><i class="demo-icon icon-doc">&#x1f5c5;</i> <span class="i-name">icon-doc</span><span class="i-code">0x1f5c5</span></div>
+        <div title="Code: 0x1f5c8" class="the-icons span3"><i class="demo-icon icon-doc-text">&#x1f5c8;</i> <span class="i-name">icon-doc-text</span><span class="i-code">0x1f5c8</span></div>
+        <div title="Code: 0x1f5d1" class="the-icons span3"><i class="demo-icon icon-trashcan">&#x1f5d1;</i> <span class="i-name">icon-trashcan</span><span class="i-code">0x1f5d1</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f5d8" class="the-icons span3"><i class="demo-icon icon-arrows-cw">&#x1f5d8;</i> <span class="i-name">icon-arrows-cw</span><span class="i-code">0x1f5d8</span></div>
+        <div title="Code: 0x1f5e9" class="the-icons span3"><i class="demo-icon icon-comment">&#x1f5e9;</i> <span class="i-name">icon-comment</span><span class="i-code">0x1f5e9</span></div>
+        <div title="Code: 0x1f5ea" class="the-icons span3"><i class="demo-icon icon-comment-discussion">&#x1f5ea;</i> <span class="i-name">icon-comment-discussion</span><span class="i-code">0x1f5ea</span></div>
+        <div title="Code: 0x1f5f4" class="the-icons span3"><i class="demo-icon icon-cancel">&#x1f5f4;</i> <span class="i-name">icon-cancel</span><span class="i-code">0x1f5f4</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f5f5" class="the-icons span3"><i class="demo-icon icon-cancel-circled">&#x1f5f5;</i> <span class="i-name">icon-cancel-circled</span><span class="i-code">0x1f5f5</span></div>
+        <div title="Code: 0x1f5f8" class="the-icons span3"><i class="demo-icon icon-ok">&#x1f5f8;</i> <span class="i-name">icon-ok</span><span class="i-code">0x1f5f8</span></div>
+        <div title="Code: 0x1f5f9" class="the-icons span3"><i class="demo-icon icon-ok-circled">&#x1f5f9;</i> <span class="i-name">icon-ok-circled</span><span class="i-code">0x1f5f9</span></div>
+        <div title="Code: 0x1f6ab" class="the-icons span3"><i class="demo-icon icon-block">&#x1f6ab;</i> <span class="i-name">icon-block</span><span class="i-code">0x1f6ab</span></div>
+      </div>
+      <div class="row">
+        <div title="Code: 0x1f6e0" class="the-icons span3"><i class="demo-icon icon-tools">&#x1f6e0;</i> <span class="i-name">icon-tools</span><span class="i-code">0x1f6e0</span></div>
+      </div>
+    </div>
+    <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>
+  </body>
+</html>
\ No newline at end of file
Binary file kallithea/public/fontello/font/kallithea.eot has changed
--- a/kallithea/public/fontello/font/kallithea.svg	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/public/fontello/font/kallithea.svg	Sun Mar 31 21:28:56 2019 +0200
@@ -1,112 +1,212 @@
 <?xml version="1.0" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg xmlns="http://www.w3.org/2000/svg">
-<metadata>Copyright (C) 2015 by original authors @ fontello.com</metadata>
+<metadata>Copyright (C) 2018 by original authors @ fontello.com</metadata>
 <defs>
 <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="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="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="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="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="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="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="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="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-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="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="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="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="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="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="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="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" />
-<glyph glyph-name="left-circled" unicode="&#xe861;" d="m643 404v-108q0-7-5-12t-13-5h-196v-108q0-7-5-12t-13-5q-7 0-14 5l-178 178q-5 5-5 13t5 13l179 178q5 5 13 5 7 0 12-5t6-12v-108h196q7 0 13-5t5-12z m89-54q0 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="right-circled" unicode="&#xe862;" d="m643 350q0-8-5-13l-179-178q-5-5-13-5-7 0-12 5t-5 12v108h-197q-7 0-12 5t-6 12v108q0 7 6 12t12 5h197v108q0 7 5 12t12 5q7 0 14-5l178-178q5-5 5-13z m89 0q0 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="up-circled" unicode="&#xe863;" d="m717 351q0 15-11 25l-202 202-50 50q-10 11-25 11t-26-11l-50-50-202-202q-10-10-10-25t10-26l50-50q11-10 26-10t25 10l105 105v-280q0-14 11-25t25-11h71q15 0 25 11t11 25v280l105-105q11-11 26-11t25 11l50 50q11 11 11 26z m140-1q0-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="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" />
+<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-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-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-25 10l-93 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="M654 682l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-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 25l93 92q10 11 25 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 6-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-178q-5-6-12-6-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-10h143v-143q0-15 11-25t25-11h71q15 0 25 11t11 25v143h143q14 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="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 37-38 54-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 37-38 54-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-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 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 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-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-37q12 1 17 1t17-1q28 39 51 62l4 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-232h232q23 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 16h678q23 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-25l-143-143q-10-11-25-11t-25 11l-143 143q-10 10-10 25t10 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="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="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="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="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-37t38-16z 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 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" 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-37 15t-16 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-15 27-42t11-49z" 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="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-37 38-16 38 16 16 37z 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-113-45-72-22-95-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-277q30 14 86 31 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="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-37t-38-16h-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-inv" unicode="&#xe836;" d="M571 564v264q13-8 21-16l227-228q8-7 16-20h-264z m-71-18q0-22 16-37t38-16h303v-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 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-554 131v-59h183v59h-52v93h76q43 0 66 9 37 12 60 48t22 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="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="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="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-25t-26-11h-142q-15 0-25 11t-11 25v142q0 15 11 26t25 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="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-11 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-91l157 280q4-25 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-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
+
+<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-10-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-10 23q-7 120-57 228t-129 188-188 129-227 57q-14 1-24 11t-10 24v80q0 16 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 222t-221 98q-8 1-14-5t-5-13v-71q0-7 5-12t12-6q86-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-7 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="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 55-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-101-33-38-32-38-81 0-41 37-78t156-72q38-12 96-37 33-16 53-29h-414z m283-143h229q4-22 4-51 0-62-23-119-13-31-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-76 40z" horiz-adv-x="1000" />
+
+<glyph glyph-name="box" unicode="&#xe85e;" d="M607 386q0 14-10 25t-26 10h-142q-15 0-25-10t-11-25 11-25 25-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-14-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-8-8-19-4-11 5-11 17v125h-696q-8 0-13 5t-5 12v108q0 7 5 12t13 5h696v125q0 12 11 17t19-3l215-195q5-6 5-13z" 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" />
+
+<glyph glyph-name="left-circled" unicode="&#xe861;" d="M643 404v-108q0-7-5-12t-13-5h-196v-108q0-7-5-12t-13-5q-7 0-14 5l-178 178q-5 5-5 13t5 13l179 178q5 5 13 5 7 0 12-5t6-12v-108h196q7 0 13-5t5-12z m89-54q0 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="right-circled" unicode="&#xe862;" d="M643 350q0-8-5-13l-179-178q-5-5-13-5-7 0-12 5t-5 12v108h-197q-7 0-12 5t-6 12v108q0 7 6 12t12 5h197v108q0 7 5 12t12 5q7 0 14-5l178-178q5-5 5-13z m89 0q0 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="up-circled" unicode="&#xe863;" d="M717 351q0 15-10 25l-202 202-51 51q-10 10-25 10t-25-10l-51-51-202-202q-10-10-10-25t10-26l51-50q10-10 25-10t25 10l105 105v-280q0-14 11-25t25-11h71q15 0 25 11t11 25v280l106-105q10-11 25-11t25 11l51 50q10 11 10 26z m140-1q0-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="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-8 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-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 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="M714 69q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
+
+<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 24 12q34 0 62-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-7 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 331q0 19-30 19h-607q-22 0-48-12t-39-29l-164-203q-11-13-11-22 0-20 30-20h607q23 0 48 13t40 29l164 203q10 12 10 22z m-637 90h429v90q0 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-90q0-35-25-67l-165-203q-24-30-65-49t-78-19h-607q-51 0-88 37t-37 88v536q0 51 37 88t88 37h179q51 0 88-37t37-88v-18h303q52 0 88-37t37-88v-90h107q30 0 56-13t37-40q8-17 8-37z" 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-31 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 64-32t10-72l-153-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t20 23l167 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-1 12t-12 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-7 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-14-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-25-10-11-25 11-25 25-11 25 11 10 25z 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 m354 243q0-22-13-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-5t5-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 m232 217q7-7-2-16l-163-163q-9-9-16-1-8 7 1 16l163 163q9 9 17 1z m-153-385l303 304-161 161-303-304v-161h161z m339 340l51 51q16 16 16 38t-16 38l-85 85q-15 15-38 15t-37-15l-52-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 52q16 15 38 15t38-15l85-85q16-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-17-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-18h303q52 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 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-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 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-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-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 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-25 11-16 0-26-11l-50-50q-11-11-11-26 0-14 11-25l101-101-101-101q-11-11-11-25 0-15 11-26l50-50q10-11 26-11 14 0 25 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="M933 534q0-22-16-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-76q16-15 16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="ok-circled" unicode="&#x1f5f9;" d="M717 440q0 16-10 26l-51 50q-11 11-25 11t-25-11l-228-227-126 126q-11 11-25 11t-25-11l-51-50q-10-10-10-26 0-15 10-25l202-202q10-10 25-10 15 0 26 10l303 303q10 10 10 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-111-41-153q0-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
Binary file kallithea/public/fontello/font/kallithea.woff2 has changed
Binary file kallithea/public/images/android-chrome-192x192.png has changed
Binary file kallithea/public/images/android-chrome-512x512.png has changed
Binary file kallithea/public/images/apple-touch-icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/public/images/browserconfig.xml	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+    <msapplication>
+        <tile>
+            <square150x150logo src="/images/mstile-150x150.png"/>
+            <TileColor>#da532c</TileColor>
+        </tile>
+    </msapplication>
+</browserconfig>
Binary file kallithea/public/images/favicon-16x16.png has changed
Binary file kallithea/public/images/favicon-32x32.png has changed
Binary file kallithea/public/images/favicon.ico has changed
Binary file kallithea/public/images/favicon.png has changed
Binary file kallithea/public/images/kallithea-logo.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/public/images/manifest.json	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,18 @@
+{
+    "name": "Kallithea",
+    "icons": [
+        {
+            "src": "/images/android-chrome-192x192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "/images/android-chrome-512x512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        }
+    ],
+    "theme_color": "#ffffff",
+    "background_color": "#ffffff",
+    "display": "standalone"
+}
Binary file kallithea/public/images/mstile-144x144.png has changed
Binary file kallithea/public/images/mstile-150x150.png has changed
Binary file kallithea/public/images/mstile-310x150.png has changed
Binary file kallithea/public/images/mstile-310x310.png has changed
Binary file kallithea/public/images/mstile-70x70.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/public/images/safari-pinned-tab.svg	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,44 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,16.000000) scale(0.002286,-0.002286)"
+fill="#000000" stroke="none">
+<path d="M1800 6948 c-369 -33 -683 -232 -888 -560 -23 -38 -40 -68 -37 -68 3
+0 -1 -10 -9 -22 -26 -40 -67 -160 -88 -258 -23 -105 -32 -316 -17 -397 35
+-188 77 -306 156 -437 107 -178 284 -341 473 -436 81 -41 210 -86 273 -95 26
+-4 50 -11 52 -16 3 -4 7 -174 10 -376 2 -202 7 -595 10 -873 12 -1069 15
+-1227 19 -1263 7 -48 34 -109 83 -179 50 -73 74 -98 353 -368 128 -124 270
+-261 315 -305 45 -44 179 -174 298 -288 151 -147 223 -224 243 -260 15 -29 32
+-55 38 -59 8 -5 11 -110 12 -325 l1 -318 232 0 c127 0 231 2 232 5 1 9 8 959
+13 1960 3 538 10 980 15 1012 5 31 17 71 27 90 9 18 87 103 173 189 86 86 292
+295 457 465 166 170 359 368 429 439 160 165 209 229 226 297 17 74 17 160 -6
+1353 -2 116 -7 381 -11 590 -3 209 -7 409 -8 445 l-1 65 -235 1 -235 1 1 -46
+c1 -25 5 -302 8 -616 11 -945 16 -1273 20 -1424 5 -156 -1 -187 -55 -266 -28
+-41 -49 -63 -539 -565 -484 -495 -509 -521 -587 -614 -98 -117 -140 -182 -148
+-231 -3 -22 -6 -469 -6 -994 l0 -954 -32 29 c-17 16 -34 31 -37 34 -6 6 -121
+114 -319 300 -79 74 -156 146 -171 160 -14 14 -86 81 -160 150 -207 194 -216
+203 -239 247 -11 23 -23 57 -26 75 -3 18 -7 231 -10 473 -7 674 -16 1434 -20
+1695 -3 240 0 260 35 260 34 0 193 51 269 86 231 107 445 318 555 548 30 63
+73 184 81 226 3 14 7 36 10 50 30 139 24 364 -14 505 -97 370 -363 664 -721
+798 -57 21 -156 49 -185 52 -5 0 -35 4 -65 8 -91 12 -160 13 -250 5z m254
+-580 c106 -29 183 -74 262 -153 76 -75 114 -136 145 -229 25 -72 29 -101 30
+-187 0 -164 -58 -300 -183 -421 -116 -114 -258 -168 -424 -163 -317 11 -567
+267 -567 580 0 100 16 170 59 260 81 171 236 289 429 328 56 11 183 4 249 -15z"/>
+<path d="M5350 3192 c-271 -39 -523 -241 -612 -492 -14 -40 -20 -59 -35 -120
+-2 -8 -5 -61 -7 -116 -5 -141 25 -276 89 -390 34 -62 126 -175 168 -207 18
+-14 43 -35 57 -47 33 -29 173 -96 232 -110 l46 -12 0 -391 c-1 -365 -2 -396
+-20 -444 -22 -56 -18 -52 -418 -464 -135 -138 -261 -268 -280 -288 -69 -72
+-85 -66 187 -65 l246 0 95 100 c52 54 185 192 296 306 135 139 210 225 229
+260 50 97 55 147 56 573 0 215 0 399 0 410 1 15 14 25 49 37 106 38 241 141
+330 251 40 49 94 155 119 235 32 101 40 274 17 377 -66 293 -296 525 -580 586
+-57 12 -213 19 -264 11z m225 -471 c57 -25 128 -97 157 -158 34 -72 32 -189
+-5 -259 -58 -113 -183 -183 -303 -171 -236 24 -360 291 -223 481 84 119 245
+164 374 107z"/>
+</g>
+</svg>
--- a/kallithea/public/js/base.js	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/public/js/base.js	Sun Mar 31 21:28:56 2019 +0200
@@ -335,12 +335,6 @@
 })();
 
 
-/**
- * GLOBAL YUI Shortcuts
- */
-var YUD = YAHOO.util.Dom;
-var YUE = YAHOO.util.Event;
-
 /* Invoke all functions in callbacks */
 var _run_callbacks = function(callbacks){
     if (callbacks !== undefined){
@@ -395,21 +389,22 @@
         .fail(function(jqXHR, textStatus, errorThrown) {
                 if (textStatus == "abort")
                     return;
-                $target.html('<span class="error_red">ERROR: {0}</span>'.format(textStatus));
+                $target.html('<span class="bg-danger">ERROR: {0}</span>'.format(textStatus));
                 $target.css('opacity','1.0');
             })
         ;
 };
 
-var ajaxGET = function(url,success) {
+var ajaxGET = function(url, success, failure) {
+    if(failure === undefined) {
+        failure = function(jqXHR, textStatus, errorThrown) {
+                if (textStatus != "abort")
+                    alert("Ajax GET error: " + textStatus);
+            };
+    }
     return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
         .done(success)
-        .fail(function(jqXHR, textStatus, errorThrown) {
-                if (textStatus == "abort")
-                    return;
-                alert("Ajax GET error: " + textStatus);
-        })
-        ;
+        .fail(failure);
 };
 
 var ajaxPOST = function(url, postData, success, failure) {
@@ -440,25 +435,6 @@
     });
 };
 
-/**
- * activate .lazy-cs mouseover for showing changeset tooltip
- */
-var show_changeset_tooltip = function(){
-    $('.lazy-cs').mouseover(function(e){
-        var $target = $(e.currentTarget);
-        var rid = $target.attr('raw_id');
-        var repo_name = $target.attr('repo_name');
-        if(rid && !$target.hasClass('tooltip')){
-            _show_tooltip(e, _TM['loading ...']);
-            var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": rid});
-            ajaxGET(url, function(json){
-                    $target.addClass('tooltip');
-                    _show_tooltip(e, json['message']);
-                    _activate_tooltip($target);
-                });
-        }
-    });
-};
 
 var _onSuccessFollow = function(target){
     var $target = $(target);
@@ -480,8 +456,8 @@
     }
 }
 
-var toggleFollowingRepo = function(target, follows_repo_id){
-    var args = 'follows_repo_id=' + follows_repo_id;
+var toggleFollowingRepo = function(target, follows_repository_id){
+    var args = 'follows_repository_id=' + follows_repository_id;
     args += '&amp;_authentication_token=' + _authentication_token;
     $.post(TOGGLE_FOLLOW_URL, args, function(data){
             _onSuccessFollow(target);
@@ -504,76 +480,57 @@
 };
 
 /**
- * tooltips
+ * load tooltips dynamically based on data attributes, used for .lazy-cs changeset links
  */
-
-var tooltip_activate = function(){
-    $(document).ready(_init_tooltip);
-};
-
-var _activate_tooltip = function($tt){
-    $tt.mouseover(_show_tooltip);
-    $tt.mousemove(_move_tooltip);
-    $tt.mouseout(_close_tooltip);
-};
+var get_changeset_tooltip = function() {
+    var $target = $(this);
+    var tooltip = $target.data('tooltip');
+    if (!tooltip) {
+        var raw_id = $target.data('raw_id');
+        var repo_name = $target.data('repo_name');
+        var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": raw_id});
 
-var _init_tooltip = function(){
-    var $tipBox = $('#tip-box');
-    if(!$tipBox.length){
-        $tipBox = $('<div id="tip-box"></div>');
-        $(document.body).append($tipBox);
+        $.ajax(url, {
+            async: false,
+            success: function(data) {
+                tooltip = data["message"];
+            }
+        });
+        $target.data('tooltip', tooltip);
     }
-
-    $tipBox.hide();
-    $tipBox.css('position', 'absolute');
-    $tipBox.css('max-width', '600px');
-
-    _activate_tooltip($('.tooltip'));
+    return tooltip;
 };
 
-var _show_tooltip = function(e, tipText, safe){
-    e.stopImmediatePropagation();
-    var el = e.currentTarget;
-    var $el = $(el);
-    if(tipText){
-        // just use it
-    } else if(el.tagName.toLowerCase() === 'img'){
-        tipText = el.alt ? el.alt : '';
-    } else {
-        tipText = el.title ? el.title : '';
-        safe = safe || $el.hasClass("safe-html-title");
+/**
+ * activate tooltips and popups
+ */
+var tooltip_activate = function(){
+    function placement(p, e){
+        if(e.getBoundingClientRect().top > 2*$(window).height()/3){
+            return 'top';
+        }else{
+            return 'bottom';
+        }
     }
-
-    if(tipText !== ''){
-        // save org title
-        $el.attr('tt_title', tipText);
-        // reset title to not show org tooltips
-        $el.prop('title', '');
-
-        var $tipBox = $('#tip-box');
-        if (safe) {
-            $tipBox.html(tipText);
-        } else {
-            $tipBox.text(tipText);
-        }
-        $tipBox.css('display', 'block');
-    }
+    $(document).ready(function(){
+        $('[data-toggle="tooltip"]').tooltip({
+            container: 'body',
+            placement: placement
+        });
+        $('[data-toggle="popover"]').popover({
+            html: true,
+            container: 'body',
+            placement: placement,
+            trigger: 'hover',
+            template: '<div class="popover cs-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+        });
+        $('.lazy-cs').tooltip({
+            title: get_changeset_tooltip,
+            placement: placement
+        });
+    });
 };
 
-var _move_tooltip = function(e){
-    e.stopImmediatePropagation();
-    var $tipBox = $('#tip-box');
-    $tipBox.css('top', (e.pageY + 15) + 'px');
-    $tipBox.css('left', (e.pageX + 15) + 'px');
-};
-
-var _close_tooltip = function(e){
-    e.stopImmediatePropagation();
-    var $tipBox = $('#tip-box');
-    $tipBox.hide();
-    var el = e.currentTarget;
-    $(el).prop('title', $(el).attr('tt_title'));
-};
 
 /**
  * Quick filter widget
@@ -645,9 +602,13 @@
         var line_no = $anchorcomment.data('line_no');
         if ($comment_div[0]) {
             $comment_div.append($anchorcomment.children());
-            _comment_div_append_add($comment_div, f_path, line_no);
+            if (f_path && line_no) {
+                _comment_div_append_add($comment_div, f_path, line_no);
+            } else {
+                _comment_div_append_form($comment_div, f_path, line_no);
+            }
         } else {
-           $anchorcomment.before("Comment to {0} line {1} which is outside the diff context:".format(f_path || '?', line_no || '?'));
+            $anchorcomment.before("<span class='bg-warning'>Comment to {0} line {1} which is outside the diff context:</span>".format(f_path || '?', line_no || '?'));
         }
     });
     linkInlineComments($('.firstlink'), $('.comment:first-child'));
@@ -658,7 +619,7 @@
     var children = $bubble.closest('tr.line').children('[id]');
     var line_td_id = children[children.length - 1].id;
     var $comment_div = _get_add_comment_div(line_td_id);
-    var f_path = $bubble.closest('div.full_f_path').data('f_path');
+    var f_path = $bubble.closest('[data-f_path]').data('f_path');
     var parts = line_td_id.split('_');
     var line_no = parts[parts.length-1];
     comment_div_state($comment_div, f_path, line_no, true);
@@ -676,28 +637,29 @@
     return $comments_box;
 }
 
-// set $comment_div state - showing or not showing form and Add button
-function comment_div_state($comment_div, f_path, line_no, show_form) {
+// Set $comment_div state - showing or not showing form and Add button.
+// An Add button is shown on non-empty forms when no form is shown.
+// The form is controlled by show_form_opt - if undefined, form is only shown for general comments.
+function comment_div_state($comment_div, f_path, line_no, show_form_opt) {
+    var show_form = show_form_opt !== undefined ? show_form_opt : !f_path && !line_no;
     var $forms = $comment_div.children('.comment-inline-form');
     var $buttonrow = $comment_div.children('.add-button-row');
-    var $comments = $comment_div.children('.comment');
+    var $comments = $comment_div.children('.comment:not(.submitting)');
+    $forms.remove();
+    $buttonrow.remove();
     if (show_form) {
-        if (!$forms.length) {
-            _comment_div_append_form($comment_div, f_path, line_no);
-        }
+        _comment_div_append_form($comment_div, f_path, line_no);
+    } else if ($comments.length) {
+        _comment_div_append_add($comment_div, f_path, line_no);
     } else {
-        $forms.remove();
-    }
-    $buttonrow.remove();
-    if ($comments.length && !show_form) {
-        _comment_div_append_add($comment_div, f_path, line_no);
+        $comment_div.parent('tr').remove();
     }
 }
 
 // append an Add button to $comment_div and hook it up to show form
 function _comment_div_append_add($comment_div, f_path, line_no) {
     var addlabel = TRANSLATION_MAP['Add Another Comment'];
-    var $add = $('<div class="add-button-row"><span class="btn btn-mini add-button">{0}</span></div>'.format(addlabel));
+    var $add = $('<div class="add-button-row"><span class="btn btn-default btn-xs add-button">{0}</span></div>'.format(addlabel));
     $comment_div.append($add);
     $add.children('.add-button').click(function(e) {
         comment_div_state($comment_div, f_path, line_no, true);
@@ -706,78 +668,124 @@
 
 // append a comment form to $comment_div
 function _comment_div_append_form($comment_div, f_path, line_no) {
-    var $form_div = $($('#comment-inline-form-template').html().format(f_path, line_no))
+    var $form_div = $('#comment-inline-form-template').children()
+        .clone()
         .addClass('comment-inline-form');
     $comment_div.append($form_div);
+    var $preview = $comment_div.find("div.comment-preview");
     var $form = $comment_div.find("form");
+    var $textarea = $form.find('textarea');
 
     $form.submit(function(e) {
         e.preventDefault();
 
-        var text = $('#text_'+line_no).val();
-        if (!text){
-            return;
+        var text = $textarea.val();
+        var review_status = $form.find('input:radio[name=changeset_status]:checked').val();
+        var pr_close = $form.find('input:checkbox[name=save_close]:checked').length ? 'on' : '';
+        var pr_delete = $form.find('input:checkbox[name=save_delete]:checked').length ? 'delete' : '';
+
+        if (!text && !review_status && !pr_close && !pr_delete) {
+            alert("Please provide a comment");
+            return false;
         }
 
-        $form.find('.submitting-overlay').show();
+        if (pr_delete) {
+            if (text || review_status || pr_close) {
+                alert('Cannot delete pull request while making other changes');
+                return false;
+            }
+            if (!confirm('Confirm to delete this pull request')) {
+                return false;
+            }
+            var comments = $('.comment').size();
+            if (comments > 0 &&
+                !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) {
+                return false;
+            }
+        }
 
-        var success = function(json_data) {
-            $comment_div.append(json_data['rendered_text']);
-            comment_div_state($comment_div, f_path, line_no, false);
-            linkInlineComments($('.firstlink'), $('.comment:first-child'));
-        };
+        if (review_status) {
+            var $review_status = $preview.find('.automatic-comment');
+            var review_status_lbl = $("#comment-inline-form-template input.status_change_radio[value='" + review_status + "']").parent().text().strip();
+            $review_status.find('.comment-status-label').text(review_status_lbl);
+            $review_status.show();
+        }
+        $preview.find('.comment-text div').text(text);
+        $preview.show();
+        $textarea.val('');
+        if (f_path && line_no) {
+            $form.hide();
+        }
+
         var postData = {
             'text': text,
             'f_path': f_path,
-            'line': line_no
+            'line': line_no,
+            'changeset_status': review_status,
+            'save_close': pr_close,
+            'save_delete': pr_delete
+        };
+        var success = function(json_data) {
+            if (pr_delete) {
+                location = json_data['location'];
+            } else {
+                $comment_div.append(json_data['rendered_text']);
+                comment_div_state($comment_div, f_path, line_no);
+                linkInlineComments($('.firstlink'), $('.comment:first-child'));
+                if ((review_status || pr_close) && !f_path && !line_no) {
+                    // Page changed a lot - reload it after closing the submitted form
+                    comment_div_state($comment_div, f_path, line_no, false);
+                    location.reload(true);
+                }
+            }
         };
-        ajaxPOST(AJAX_COMMENT_URL, postData, success);
+        var failure = function(x, s, e) {
+            $preview.removeClass('submitting').addClass('failed');
+            var $status = $preview.find('.comment-submission-status');
+            $('<span>', {
+                'title': e,
+                text: _TM['Unable to post']
+            }).replaceAll($status.contents());
+            $('<div>', {
+                'class': 'btn-group'
+            }).append(
+                $('<button>', {
+                    'class': 'btn btn-default btn-xs',
+                    text: _TM['Retry']
+                }).click(function() {
+                    $status.text(_TM['Submitting ...']);
+                    $preview.addClass('submitting').removeClass('failed');
+                    ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
+                }),
+                $('<button>', {
+                    'class': 'btn btn-default btn-xs',
+                    text: _TM['Cancel']
+                }).click(function() {
+                    comment_div_state($comment_div, f_path, line_no);
+                })
+            ).appendTo($status);
+        };
+        ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
     });
 
-    $('#preview-btn_'+line_no).click(function(e){
-        var text = $('#text_'+line_no).val();
-        if(!text){
-            return
-        }
-        $('#preview-box_'+line_no).addClass('unloaded');
-        $('#preview-box_'+line_no).html(_TM['Loading ...']);
-        $('#edit-container_'+line_no).hide();
-        $('#edit-btn_'+line_no).show();
-        $('#preview-container_'+line_no).show();
-        $('#preview-btn_'+line_no).hide();
-
-        var url = pyroutes.url('changeset_comment_preview', {'repo_name': REPO_NAME});
-        var post_data = {'text': text};
-        ajaxPOST(url, post_data, function(html) {
-            $('#preview-box_'+line_no).html(html);
-            $('#preview-box_'+line_no).removeClass('unloaded');
-        })
-    })
-    $('#edit-btn_'+line_no).click(function(e) {
-        $('#edit-container_'+line_no).show();
-        $('#edit-btn_'+line_no).hide();
-        $('#preview-container_'+line_no).hide();
-        $('#preview-btn_'+line_no).show();
-    })
-
-    // create event for hide button
+    // add event handler for hide/cancel buttons
     $form.find('.hide-inline-form').click(function(e) {
-        comment_div_state($comment_div, f_path, line_no, false);
+        comment_div_state($comment_div, f_path, line_no);
     });
 
-    setTimeout(function() {
-        // callbacks
-        tooltip_activate();
-        MentionsAutoComplete($('#text_'+line_no), $('#mentions_container_'+line_no),
-                             _USERS_AC_DATA);
-        $('#text_'+line_no).focus();
-    }, 10);
+    tooltip_activate();
+    if ($textarea.length > 0) {
+        MentionsAutoComplete($textarea);
+    }
+    if (f_path) {
+        $textarea.focus();
+    }
 }
 
 
 function deleteComment(comment_id) {
     var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
-    var postData = {'_method': 'delete'};
+    var postData = {};
     var success = function(o) {
         $('#comment-'+comment_id).remove();
         // Ignore that this might leave a stray Add button (or have a pending form with another comment) ...
@@ -815,20 +823,7 @@
 }
 
 /* activate files.html stuff */
-var fileBrowserListeners = function(current_url, node_list_url, url_base){
-    var current_url_branch = "?branch=__BRANCH__";
-
-    $('#stay_at_branch').on('click',function(e){
-        if(e.currentTarget.checked){
-            var uri = current_url_branch;
-            uri = uri.replace('__BRANCH__',e.currentTarget.value);
-            window.location = uri;
-        }
-        else{
-            window.location = current_url;
-        }
-    });
-
+var fileBrowserListeners = function(node_list_url, url_base){
     var $node_filter = $('#node_filter');
 
     var filterTimeout = null;
@@ -934,17 +929,15 @@
         });
 
     $('#file_enable').click(function(){
-            $('#editor_container').show();
             $('#upload_file_container').hide();
             $('#filename_container').show();
-            $('#set_mode_header').show();
+            $('#body').show();
         });
 
     $('#upload_file_enable').click(function(){
-            $('#editor_container').hide();
             $('#upload_file_container').show();
             $('#filename_container').hide();
-            $('#set_mode_header').hide();
+            $('#body').hide();
         });
 
     return myCodeMirror
@@ -1011,34 +1004,6 @@
     }
 };
 
-var deleteNotification = function(url, notification_id, callbacks){
-    var success = function(o){
-            $("#notification_"+notification_id).remove();
-            _run_callbacks(callbacks);
-        };
-    var failure = function(o){
-            alert("deleteNotification failure");
-        };
-    var postData = {'_method': 'delete'};
-    var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
-    ajaxPOST(sUrl, postData, success, failure);
-};
-
-var readNotification = function(url, notification_id, callbacks){
-    var success = function(o){
-            var $obj = $("#notification_"+notification_id);
-            $obj.removeClass('unread');
-            $obj.find('.read-notification').remove();
-            _run_callbacks(callbacks);
-        };
-    var failure = function(o){
-            alert("readNotification failure");
-        };
-    var postData = {'_method': 'put'};
-    var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
-    ajaxPOST(sUrl, postData, success, failure);
-};
-
 /**
  * Autocomplete functionality
  */
@@ -1081,240 +1046,188 @@
     return matches;
 };
 
-// Helper highlight function for the formatter
-var autocompleteHighlightMatch = function (full, snippet, matchindex) {
-    return full.substring(0, matchindex)
-        + "<span class='match'>"
-        + full.substr(matchindex, snippet.length)
-        + "</span>" + full.substring(matchindex + snippet.length);
+// Highlight the snippet if it is found in the full text, while escaping any existing markup.
+// Snippet must be lowercased already.
+var autocompleteHighlightMatch = function (full, snippet) {
+    var matchindex = full.toLowerCase().indexOf(snippet);
+    if (matchindex <0)
+        return full.html_escape();
+    return full.substring(0, matchindex).html_escape()
+        + '<span class="select2-match">'
+        + full.substr(matchindex, snippet.length).html_escape()
+        + '</span>'
+        + full.substring(matchindex + snippet.length).html_escape();
 };
 
-var gravatar = function(link, size, cssclass) {
-    var elem = '<img alt="" class="{2}" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, link, cssclass);
-    if (!link) {
-        elem = '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
+// Return html snippet for showing the provided gravatar url
+var gravatar = function(gravatar_lnk, size, cssclass) {
+    if (!gravatar_lnk) {
+        return '';
     }
-    return elem;
+    if (gravatar_lnk == 'default') {
+        return '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
+    }
+    return ('<i class="icon-gravatar {2}"' +
+            ' style="font-size: {0}px;background-image: url(\'{1}\'); background-size: {0}px"' +
+            '></i>').format(size, gravatar_lnk, cssclass);
 }
 
-var autocompleteGravatar = function(res, link, size, group) {
-    var elem = gravatar(link, size, "perm-gravatar-ac");
+var autocompleteGravatar = function(res, gravatar_lnk, size, group) {
+    var elem;
     if (group !== undefined) {
         elem = '<i class="perm-gravatar-ac icon-users"></i>';
+    } else {
+        elem = gravatar(gravatar_lnk, size, "perm-gravatar-ac");
     }
     return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
 }
 
-// Custom formatter to highlight the matching letters
+// Custom formatter to highlight the matching letters and do HTML escaping
 var autocompleteFormatter = function (oResultData, sQuery, sResultMatch) {
-    var query = sQuery.toLowerCase();
+    var query;
+    if (sQuery && sQuery.toLowerCase) // YAHOO AutoComplete
+        query = sQuery.toLowerCase();
+    else if (sResultMatch && sResultMatch.term) // select2 - parameter names doesn't match
+        query = sResultMatch.term.toLowerCase();
 
     // group
-    if (oResultData.grname != undefined) {
-        var grname = oResultData.grname;
-        var grmembers = oResultData.grmembers;
-        var grnameMatchIndex = grname.toLowerCase().indexOf(query);
-        var grprefix = "{0}: ".format(_TM['Group']);
-        var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
-
-        if (grnameMatchIndex > -1) {
-            return autocompleteGravatar(grprefix + autocompleteHighlightMatch(grname, query, grnameMatchIndex) + grsuffix, null, null, true);
-        }
-        return autocompleteGravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
+    if (oResultData.type == "group") {
+        return autocompleteGravatar(
+            "{0}: {1}".format(
+                _TM['Group'],
+                autocompleteHighlightMatch(oResultData.grname, query)),
+            null, null, true);
+    }
 
     // users
-    } else if (oResultData.nname != undefined) {
-        var fname = oResultData.fname || "";
-        var lname = oResultData.lname || "";
-        var nname = oResultData.nname;
-
-        // Guard against null value
-        var fnameMatchIndex = fname.toLowerCase().indexOf(query),
-            lnameMatchIndex = lname.toLowerCase().indexOf(query),
-            nnameMatchIndex = nname.toLowerCase().indexOf(query),
-            displayfname, displaylname, displaynname, displayname;
-
-        if (fnameMatchIndex > -1) {
-            displayfname = autocompleteHighlightMatch(fname, query, fnameMatchIndex);
-        } else {
-            displayfname = fname;
-        }
-
-        if (lnameMatchIndex > -1) {
-            displaylname = autocompleteHighlightMatch(lname, query, lnameMatchIndex);
-        } else {
-            displaylname = lname;
-        }
-
-        if (nnameMatchIndex > -1) {
-            displaynname = autocompleteHighlightMatch(nname, query, nnameMatchIndex);
-        } else {
-            displaynname = nname;
-        }
-
-        displayname = displaynname;
-        if (displayfname && displaylname) {
-            displayname = "{0} {1} ({2})".format(displayfname, displaylname, displayname);
+    if (oResultData.nname) {
+        var displayname = autocompleteHighlightMatch(oResultData.nname, query);
+        if (oResultData.fname && oResultData.lname) {
+            displayname = "{0} {1} ({2})".format(
+                autocompleteHighlightMatch(oResultData.fname, query),
+                autocompleteHighlightMatch(oResultData.lname, query),
+                displayname);
         }
 
         return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
-    } else {
-        return '';
-    }
-};
-
-// Generate a basic autocomplete instance that can be tweaked further by the caller
-var autocompleteCreate = function ($inputElement, $container, matchFunc) {
-    var datasource = new YAHOO.util.FunctionDataSource(matchFunc);
-
-    var autocomplete = new YAHOO.widget.AutoComplete($inputElement[0], $container[0], datasource);
-    autocomplete.useShadow = false;
-    autocomplete.resultTypeList = false;
-    autocomplete.animVert = false;
-    autocomplete.animHoriz = false;
-    autocomplete.animSpeed = 0.1;
-    autocomplete.formatResult = autocompleteFormatter;
-
-    return autocomplete;
-}
-
-var SimpleUserAutoComplete = function ($inputElement, $container, users_list) {
-
-    var matchUsers = function (sQuery) {
-        return autocompleteMatchUsers(sQuery, users_list);
-    }
-
-    var userAC = autocompleteCreate($inputElement, $container, matchUsers);
-
-    // Handler for selection of an entry
-    var itemSelectHandler = function (sType, aArgs) {
-        var myAC = aArgs[0]; // reference back to the AC instance
-        var elLI = aArgs[1]; // reference to the selected LI element
-        var oData = aArgs[2]; // object literal of selected item's result data
-        myAC.getInputEl().value = oData.nname;
-    };
-    userAC.itemSelectEvent.subscribe(itemSelectHandler);
-}
-
-var MembersAutoComplete = function ($inputElement, $container, users_list, groups_list) {
-
-    var matchAll = function (sQuery) {
-        var u = autocompleteMatchUsers(sQuery, users_list);
-        var g = autocompleteMatchGroups(sQuery, groups_list);
-        return u.concat(g);
-    };
-
-    var membersAC = autocompleteCreate($inputElement, $container, matchAll);
-
-    // Handler for selection of an entry
-    var itemSelectHandler = function (sType, aArgs) {
-        var nextId = $inputElement.prop('id').split('perm_new_member_name_')[1];
-        var myAC = aArgs[0]; // reference back to the AC instance
-        var elLI = aArgs[1]; // reference to the selected LI element
-        var oData = aArgs[2]; // object literal of selected item's result data
-        //fill the autocomplete with value
-        if (oData.nname != undefined) {
-            //users
-            myAC.getInputEl().value = oData.nname;
-            $('#perm_new_member_type_'+nextId).val('user');
-        } else {
-            //groups
-            myAC.getInputEl().value = oData.grname;
-            $('#perm_new_member_type_'+nextId).val('users_group');
-        }
-    };
-    membersAC.itemSelectEvent.subscribe(itemSelectHandler);
-}
-
-var MentionsAutoComplete = function ($inputElement, $container, users_list) {
-
-    var matchUsers = function (sQuery) {
-            var org_sQuery = sQuery;
-            if(this.mentionQuery == null){
-                return []
-            }
-            sQuery = this.mentionQuery;
-            return autocompleteMatchUsers(sQuery, users_list);
     }
 
-    var mentionsAC = autocompleteCreate($inputElement, $container, matchUsers);
-    mentionsAC.suppressInputUpdate = true;
-    // Overwrite formatResult to take into account mentionQuery
-    mentionsAC.formatResult = function (oResultData, sQuery, sResultMatch) {
-        var org_sQuery = sQuery;
-        if (this.dataSource.mentionQuery != null) {
-            sQuery = this.dataSource.mentionQuery;
-        }
-        return autocompleteFormatter(oResultData, sQuery, sResultMatch);
-    }
-
-    // Handler for selection of an entry
-    if(mentionsAC.itemSelectEvent){
-        mentionsAC.itemSelectEvent.subscribe(function (sType, aArgs) {
-            var myAC = aArgs[0]; // reference back to the AC instance
-            var elLI = aArgs[1]; // reference to the selected LI element
-            var oData = aArgs[2]; // object literal of selected item's result data
-            //Replace the mention name with replaced
-            var re = new RegExp();
-            var org = myAC.getInputEl().value;
-            var chunks = myAC.dataSource.chunks
-            // replace middle chunk(the search term) with actuall  match
-            chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
-                                          '@'+oData.nname+' ');
-            myAC.getInputEl().value = chunks.join('');
-            myAC.getInputEl().focus(); // Y U NO WORK !?
-        });
-    }
-
-    // in this keybuffer we will gather current value of search !
-    // since we need to get this just when someone does `@` then we do the
-    // search
-    mentionsAC.dataSource.chunks = [];
-    mentionsAC.dataSource.mentionQuery = null;
+    return '';
+};
 
-    mentionsAC.get_mention = function(msg, max_pos) {
-        var org = msg;
-        // Must match utils2.py MENTIONS_REGEX.
-        // Only matching on string up to cursor, so it must end with $
-        var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$');
-        var chunks  = [];
-
-        // cut first chunk until current pos
-        var to_max = msg.substr(0, max_pos);
-        var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
-        var msg2 = to_max.substr(at_pos);
-
-        chunks.push(org.substr(0,at_pos)); // prefix chunk
-        chunks.push(msg2);                 // search chunk
-        chunks.push(org.substr(max_pos));  // postfix chunk
-
-        // clean up msg2 for filtering and regex match
-        var msg2 = msg2.lstrip(' ').lstrip('\n');
-
-        if(re.test(msg2)){
-            var unam = re.exec(msg2)[1];
-            return [unam, chunks];
-        }
-        return [null, null];
-    };
-
-    $inputElement.keyup(function(e){
-            var currentMessage = $inputElement.val();
-            var currentCaretPosition = $inputElement[0].selectionStart;
-
-            var unam = mentionsAC.get_mention(currentMessage, currentCaretPosition);
-            var curr_search = null;
-            if(unam[0]){
-                curr_search = unam[0];
-            }
-
-            mentionsAC.dataSource.chunks = unam[1];
-            mentionsAC.dataSource.mentionQuery = curr_search;
-        });
+var SimpleUserAutoComplete = function ($inputElement) {
+    $inputElement.select2({
+        formatInputTooShort: $inputElement.attr('placeholder'),
+        initSelection : function (element, callback) {
+            $.ajax({
+                url: pyroutes.url('users_and_groups_data'),
+                dataType: 'json',
+                data: {
+                    key: element.val()
+                },
+                success: function(data){
+                  callback(data.results[0]);
+                }
+            });
+        },
+        minimumInputLength: 1,
+        ajax: {
+            url: pyroutes.url('users_and_groups_data'),
+            dataType: 'json',
+            data: function(term, page){
+              return {
+                query: term
+              };
+            },
+            results: function (data, page){
+              return data;
+            },
+            cache: true
+        },
+        formatSelection: autocompleteFormatter,
+        formatResult: autocompleteFormatter,
+        id: function(item) { return item.nname; },
+    });
 }
 
-// Important: input arguments to addReviewMember should be safe (escaped) for
-// inclusion into HTML.
+var MembersAutoComplete = function ($inputElement, $typeElement) {
+
+    $inputElement.select2({
+        placeholder: $inputElement.attr('placeholder'),
+        minimumInputLength: 1,
+        ajax: {
+            url: pyroutes.url('users_and_groups_data'),
+            dataType: 'json',
+            data: function(term, page){
+              return {
+                query: term,
+                types: 'users,groups'
+              };
+            },
+            results: function (data, page){
+              return data;
+            },
+            cache: true
+        },
+        formatSelection: autocompleteFormatter,
+        formatResult: autocompleteFormatter,
+        id: function(item) { return item.type == 'user' ? item.nname : item.grname },
+    }).on("select2-selecting", function(e) {
+        // e.choice.id is automatically used as selection value - just set the type of the selection
+        $typeElement.val(e.choice.type);
+    });
+}
+
+var MentionsAutoComplete = function ($inputElement) {
+  $inputElement.atwho({
+    at: "@",
+    callbacks: {
+      remoteFilter: function(query, callback) {
+        $.getJSON(
+          pyroutes.url('users_and_groups_data'),
+          {
+            query: query,
+            types: 'users'
+          },
+          function(data) {
+            callback(data.results)
+          }
+        );
+      },
+      sorter: function(query, items, searchKey) {
+        return items;
+      }
+    },
+    displayTpl: function(item) {
+        return "<li>" +
+            autocompleteGravatar(
+                "{0} {1} ({2})".format(item.fname, item.lname, item.nname).html_escape(),
+                '${gravatar_lnk}', 16) +
+            "</li>";
+    },
+    insertTpl: "${atwho-at}${nname}"
+  });
+};
+
+
+// Set caret at the given position in the input element
+function _setCaretPosition($inputElement, caretPos) {
+    $inputElement.each(function(){
+        if(this.createTextRange) { // IE
+            var range = this.createTextRange();
+            range.move('character', caretPos);
+            range.select();
+        }
+        else if(this.selectionStart) { // other recent browsers
+            this.focus();
+            this.setSelectionRange(caretPos, caretPos);
+        }
+        else // last resort - very old browser
+            this.focus();
+    });
+}
+
+
 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
     var displayname = nname;
     if ((fname != "") && (lname != "")) {
@@ -1326,19 +1239,21 @@
     // If you change something here it should be reflected in the template too.
     var element = (
         '     <li id="reviewer_{2}">\n'+
-        '       <div class="reviewers_member">\n'+
-        '           <div class="reviewer_status tooltip" title="not_reviewed">\n'+
-        '             <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
-        '           </div>\n'+
-        '         <div class="reviewer_gravatar gravatar">{0}</div>\n'+
-        '         <div style="float:left;">{1}</div>\n'+
+        '       <span class="reviewers_member">\n'+
         '         <input type="hidden" value="{2}" name="review_members" />\n'+
-        '         <div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">\n'+
+        '         <span class="reviewer_status" data-toggle="tooltip" title="not_reviewed">\n'+
+        '             <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
+        '         </span>\n'+
+        (gravatarelm ?
+        '         {0}\n' :
+        '')+
+        '         <span>{1}</span>\n'+
+        '         <a href="#" class="reviewer_member_remove" onclick="removeReviewMember({2})">\n'+
         '             <i class="icon-minus-circled"></i>\n'+
-        '         </div> (add not saved)\n'+
-        '       </div>\n'+
+        '         </a> (add not saved)\n'+
+        '       </span>\n'+
         '     </li>\n'
-        ).format(gravatarelm, displayname, id);
+        ).format(gravatarelm, displayname.html_escape(), id);
     // check if we don't have this ID already in
     var ids = [];
     $('#review_members').find('li').each(function() {
@@ -1358,98 +1273,50 @@
 }
 
 /* activate auto completion of users as PR reviewers */
-var PullRequestAutoComplete = function ($inputElement, $container, users_list) {
-
-    var matchUsers = function (sQuery) {
-        return autocompleteMatchUsers(sQuery, users_list);
-    };
-
-    var reviewerAC = autocompleteCreate($inputElement, $container, matchUsers);
-    reviewerAC.suppressInputUpdate = true;
-
-    // Handler for selection of an entry
-    if(reviewerAC.itemSelectEvent){
-        reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
-            var myAC = aArgs[0]; // reference back to the AC instance
-            var elLI = aArgs[1]; // reference to the selected LI element
-            var oData = aArgs[2]; // object literal of selected item's result data
-
-            // The fields in oData have already been escaped by the YUI
-            // framework. We thus should not add explicit escaping here anymore.
-            addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
-                            oData.gravatar_lnk, oData.gravatar_size);
-            myAC.getInputEl().value = '';
-        });
-    }
+var PullRequestAutoComplete = function ($inputElement) {
+    $inputElement.select2(
+    {
+        placeholder: $inputElement.attr('placeholder'),
+        minimumInputLength: 1,
+        ajax: {
+            url: pyroutes.url('users_and_groups_data'),
+            dataType: 'json',
+            data: function(term, page){
+              return {
+                query: term
+              };
+            },
+            results: function (data, page){
+              return data;
+            },
+            cache: true
+        },
+        formatSelection: autocompleteFormatter,
+        formatResult: autocompleteFormatter,
+    }).on("select2-selecting", function(e) {
+        addReviewMember(e.choice.id, e.choice.fname, e.choice.lname, e.choice.nname,
+                        e.choice.gravatar_lnk, e.choice.gravatar_size);
+        $inputElement.select2("close");
+        e.preventDefault();
+    });
 }
 
-/**
- * Activate .quick_repo_menu
- */
-var quick_repo_menu = function(){
-    $(".quick_repo_menu").mouseenter(function(e) {
-            var $menu = $(e.currentTarget).children().first().children().first();
-            if($menu.hasClass('hidden')){
-                $menu.removeClass('hidden').addClass('active');
-                $(e.currentTarget).removeClass('hidden').addClass('active');
-            }
-        });
-    $(".quick_repo_menu").mouseleave(function(e) {
-            var $menu = $(e.currentTarget).children().first().children().first();
-            if($menu.hasClass('active')){
-                $menu.removeClass('active').addClass('hidden');
-                $(e.currentTarget).removeClass('active').addClass('hidden');
-            }
-        });
-};
-
 
-/**
- * TABLE SORTING
- */
-
-var revisionSort = function(a, b, desc, field) {
-    var a_ = parseInt(a.getData('last_rev_raw') || 0);
-    var b_ = parseInt(b.getData('last_rev_raw') || 0);
-
-    return YAHOO.util.Sort.compare(a_, b_, desc);
-};
-
-var ageSort = function(a, b, desc, field) {
-    // data is like: <span class="tooltip" date="2014-06-04 18:18:55.325474" title="Wed, 04 Jun 2014 18:18:55">1 day and 23 hours ago</span>
-    var a_ = $(a.getData(field)).attr('date');
-    var b_ = $(b.getData(field)).attr('date');
-
-    return YAHOO.util.Sort.compare(a_, b_, desc);
-};
-
-var lastLoginSort = function(a, b, desc, field) {
-    var a_ = parseFloat(a.getData('last_login_raw') || 0);
-    var b_ = parseFloat(b.getData('last_login_raw') || 0);
-
-    return YAHOO.util.Sort.compare(a_, b_, desc);
-};
-
-var nameSort = function(a, b, desc, field) {
-    var a_ = a.getData('raw_name') || 0;
-    var b_ = b.getData('raw_name') || 0;
-
-    return YAHOO.util.Sort.compare(a_, b_, desc);
-};
-
-var dateSort = function(a, b, desc, field) {
-    var a_ = parseFloat(a.getData('raw_date') || 0);
-    var b_ = parseFloat(b.getData('raw_date') || 0);
-
-    return YAHOO.util.Sort.compare(a_, b_, desc);
-};
-
-var addPermAction = function(_html, users_list, groups_list){
+function addPermAction(perm_type) {
+    var template =
+        '<td><input type="radio" value="{1}.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
+        '<td><input type="radio" value="{1}.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
+        '<td><input type="radio" value="{1}.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
+        '<td><input type="radio" value="{1}.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
+        '<td>' +
+                '<input class="form-control" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text" placeholder="{2}">' +
+                '<input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">' +
+        '</td>' +
+        '<td></td>';
     var $last_node = $('.last_new_member').last(); // empty tr between last and add
     var next_id = $('.new_members').length;
-    $last_node.before($('<tr class="new_members">').append(_html.format(next_id)));
-    MembersAutoComplete($("#perm_new_member_name_"+next_id),
-            $("#perm_container_"+next_id), users_list, groups_list);
+    $last_node.before($('<tr class="new_members">').append(template.format(next_id, perm_type, _TM['Type name of user or member to grant permission'])));
+    MembersAutoComplete($("#perm_new_member_name_"+next_id), $("#perm_new_member_type_"+next_id));
 }
 
 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
@@ -1459,9 +1326,7 @@
     var failure = function (o) {
             alert(_TM['Failed to revoke permission'] + ": " + o.status);
         };
-    var query_params = {
-        '_method': 'delete'
-    }
+    var query_params = {};
     // put extra data into POST
     if (extra_data !== undefined && (typeof extra_data === 'object')){
         for(var k in extra_data){
@@ -1512,362 +1377,6 @@
         });
 }
 
-// custom paginator
-var YUI_paginator = function(links_per_page, containers){
-
-    (function () {
-
-        var Paginator = YAHOO.widget.Paginator,
-            l         = YAHOO.lang,
-            setId     = YAHOO.util.Dom.generateId;
-
-        Paginator.ui.MyFirstPageLink = function (p) {
-            this.paginator = p;
-
-            p.subscribe('recordOffsetChange',this.update,this,true);
-            p.subscribe('rowsPerPageChange',this.update,this,true);
-            p.subscribe('totalRecordsChange',this.update,this,true);
-            p.subscribe('destroy',this.destroy,this,true);
-
-            // TODO: make this work
-            p.subscribe('firstPageLinkLabelChange',this.update,this,true);
-            p.subscribe('firstPageLinkClassChange',this.update,this,true);
-        };
-
-        Paginator.ui.MyFirstPageLink.init = function (p) {
-            p.setAttributeConfig('firstPageLinkLabel', {
-                value : 1,
-                validator : l.isString
-            });
-            p.setAttributeConfig('firstPageLinkClass', {
-                value : 'yui-pg-first',
-                validator : l.isString
-            });
-            p.setAttributeConfig('firstPageLinkTitle', {
-                value : 'First Page',
-                validator : l.isString
-            });
-        };
-
-        // Instance members and methods
-        Paginator.ui.MyFirstPageLink.prototype = {
-            current   : null,
-            leftmost_page: null,
-            rightmost_page: null,
-            link      : null,
-            span      : null,
-            dotdot    : null,
-            getPos    : function(cur_page, max_page, items){
-                var edge = parseInt(items / 2) + 1;
-                if (cur_page <= edge){
-                    var radius = Math.max(parseInt(items / 2), items - cur_page);
-                }
-                else if ((max_page - cur_page) < edge) {
-                    var radius = (items - 1) - (max_page - cur_page);
-                }
-                else{
-                    var radius = parseInt(items / 2);
-                }
-
-                var left = Math.max(1, (cur_page - (radius)));
-                var right = Math.min(max_page, cur_page + (radius));
-                return [left, cur_page, right]
-            },
-            render : function (id_base) {
-                var p      = this.paginator,
-                    c      = p.get('firstPageLinkClass'),
-                    label  = p.get('firstPageLinkLabel'),
-                    title  = p.get('firstPageLinkTitle');
-
-                this.link     = document.createElement('a');
-                this.span     = document.createElement('span');
-                $(this.span).hide();
-
-                var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
-                this.leftmost_page = _pos[0];
-                this.rightmost_page = _pos[2];
-
-                setId(this.link, id_base + '-first-link');
-                this.link.href      = '#';
-                this.link.className = c;
-                this.link.innerHTML = label;
-                this.link.title     = title;
-                YUE.on(this.link,'click',this.onClick,this,true);
-
-                setId(this.span, id_base + '-first-span');
-                this.span.className = c;
-                this.span.innerHTML = label;
-
-                this.current = p.getCurrentPage() > 1 ? this.link : this.span;
-                return this.current;
-            },
-            update : function (e) {
-                var p      = this.paginator;
-                var _pos   = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
-                this.leftmost_page = _pos[0];
-                this.rightmost_page = _pos[2];
-
-                if (e && e.prevValue === e.newValue) {
-                    return;
-                }
-
-                var par = this.current ? this.current.parentNode : null;
-                if (this.leftmost_page > 1) {
-                    if (par && this.current === this.span) {
-                        par.replaceChild(this.link,this.current);
-                        this.current = this.link;
-                    }
-                } else {
-                    if (par && this.current === this.link) {
-                        par.replaceChild(this.span,this.current);
-                        this.current = this.span;
-                    }
-                }
-            },
-            destroy : function () {
-                YUE.purgeElement(this.link);
-                this.current.parentNode.removeChild(this.current);
-                this.link = this.span = null;
-            },
-            onClick : function (e) {
-                YUE.stopEvent(e);
-                this.paginator.setPage(1);
-            }
-        };
-
-        })();
-
-    (function () {
-
-        var Paginator = YAHOO.widget.Paginator,
-            l         = YAHOO.lang,
-            setId     = YAHOO.util.Dom.generateId;
-
-        Paginator.ui.MyLastPageLink = function (p) {
-            this.paginator = p;
-
-            p.subscribe('recordOffsetChange',this.update,this,true);
-            p.subscribe('rowsPerPageChange',this.update,this,true);
-            p.subscribe('totalRecordsChange',this.update,this,true);
-            p.subscribe('destroy',this.destroy,this,true);
-
-            // TODO: make this work
-            p.subscribe('lastPageLinkLabelChange',this.update,this,true);
-            p.subscribe('lastPageLinkClassChange', this.update,this,true);
-        };
-
-        Paginator.ui.MyLastPageLink.init = function (p) {
-            p.setAttributeConfig('lastPageLinkLabel', {
-                value : -1,
-                validator : l.isString
-            });
-            p.setAttributeConfig('lastPageLinkClass', {
-                value : 'yui-pg-last',
-                validator : l.isString
-            });
-            p.setAttributeConfig('lastPageLinkTitle', {
-                value : 'Last Page',
-                validator : l.isString
-            });
-
-        };
-
-        Paginator.ui.MyLastPageLink.prototype = {
-
-            current   : null,
-            leftmost_page: null,
-            rightmost_page: null,
-            link      : null,
-            span      : null,
-            dotdot    : null,
-            na        : null,
-            getPos    : function(cur_page, max_page, items){
-                var edge = parseInt(items / 2) + 1;
-                if (cur_page <= edge){
-                    var radius = Math.max(parseInt(items / 2), items - cur_page);
-                }
-                else if ((max_page - cur_page) < edge) {
-                    var radius = (items - 1) - (max_page - cur_page);
-                }
-                else{
-                    var radius = parseInt(items / 2);
-                }
-
-                var left = Math.max(1, (cur_page - (radius)));
-                var right = Math.min(max_page, cur_page + (radius));
-                return [left, cur_page, right]
-            },
-            render : function (id_base) {
-                var p      = this.paginator,
-                    c      = p.get('lastPageLinkClass'),
-                    label  = p.get('lastPageLinkLabel'),
-                    last   = p.getTotalPages(),
-                    title  = p.get('lastPageLinkTitle');
-
-                var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
-                this.leftmost_page = _pos[0];
-                this.rightmost_page = _pos[2];
-
-                this.link = document.createElement('a');
-                this.span = document.createElement('span');
-                $(this.span).hide();
-
-                this.na   = this.span.cloneNode(false);
-
-                setId(this.link, id_base + '-last-link');
-                this.link.href      = '#';
-                this.link.className = c;
-                this.link.innerHTML = label;
-                this.link.title     = title;
-                YUE.on(this.link,'click',this.onClick,this,true);
-
-                setId(this.span, id_base + '-last-span');
-                this.span.className = c;
-                this.span.innerHTML = label;
-
-                setId(this.na, id_base + '-last-na');
-
-                if (this.rightmost_page < p.getTotalPages()){
-                    this.current = this.link;
-                }
-                else{
-                    this.current = this.span;
-                }
-
-                this.current.innerHTML = p.getTotalPages();
-                return this.current;
-            },
-
-            update : function (e) {
-                var p      = this.paginator;
-
-                var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
-                this.leftmost_page = _pos[0];
-                this.rightmost_page = _pos[2];
-
-                if (e && e.prevValue === e.newValue) {
-                    return;
-                }
-
-                var par   = this.current ? this.current.parentNode : null,
-                    after = this.link;
-                if (par) {
-
-                    // only show the last page if the rightmost one is
-                    // lower, so we don't have doubled entries at the end
-                    if (!(this.rightmost_page < p.getTotalPages())){
-                        after = this.span
-                    }
-
-                    if (this.current !== after) {
-                        par.replaceChild(after,this.current);
-                        this.current = after;
-                    }
-                }
-                this.current.innerHTML = this.paginator.getTotalPages();
-
-            },
-            destroy : function () {
-                YUE.purgeElement(this.link);
-                this.current.parentNode.removeChild(this.current);
-                this.link = this.span = null;
-            },
-            onClick : function (e) {
-                YUE.stopEvent(e);
-                this.paginator.setPage(this.paginator.getTotalPages());
-            }
-        };
-
-        })();
-
-    var pagi = new YAHOO.widget.Paginator({
-        rowsPerPage: links_per_page,
-        alwaysVisible: false,
-        template : "{PreviousPageLink} {MyFirstPageLink} {PageLinks} {MyLastPageLink} {NextPageLink}",
-        pageLinks: 5,
-        containerClass: 'pagination-wh',
-        currentPageClass: 'pager_curpage',
-        pageLinkClass: 'pager_link',
-        nextPageLinkLabel: '&gt;',
-        previousPageLinkLabel: '&lt;',
-        containers:containers
-    });
-
-    return pagi
-}
-
-var YUI_datatable = function(data, fields, columns, countnode, sortkey, rows){
-    var myDataSource = new YAHOO.util.DataSource(data);
-    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-    myDataSource.responseSchema = {
-        resultsList: "records",
-        fields: fields
-        };
-    myDataSource.doBeforeCallback = function(req, raw, res, cb) {
-        // This is the filter function
-        var data     = res.results || [],
-            filtered = [],
-            i, l;
-
-        if (req) {
-            req = req.toLowerCase();
-            for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req);
-                if (pos != -1) {
-                    filtered.push(data[i]);
-                }
-            }
-            res.results = filtered;
-        }
-        $(countnode).html(res.results.length);
-        return res;
-    }
-
-    var myDataTable = new YAHOO.widget.DataTable("datatable_list_wrap", columns, myDataSource, {
-        sortedBy: {key:sortkey, dir:"asc"},
-        paginator: YUI_paginator(rows !== undefined && rows ? rows : 25, ['user-paginator']),
-        MSG_SORTASC: _TM['MSG_SORTASC'],
-        MSG_SORTDESC: _TM['MSG_SORTDESC'],
-        MSG_EMPTY: _TM['MSG_EMPTY'],
-        MSG_ERROR: _TM['MSG_ERROR'],
-        MSG_LOADING: _TM['MSG_LOADING']
-        });
-    myDataTable.subscribe('postRenderEvent',function(oArgs) {
-        tooltip_activate();
-        quick_repo_menu();
-        });
-
-    var filterTimeout = null;
-    var $q_filter = $('#q_filter');
-
-    var updateFilter = function () {
-        // Reset timeout
-        filterTimeout = null;
-
-        // Reset sort
-        var state = myDataTable.getState();
-        state.sortedBy = {key:sortkey, dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-        // Get filtered data
-        myDataSource.sendRequest($q_filter.val(), {
-            success : myDataTable.onDataReturnInitializeTable,
-            failure : myDataTable.onDataReturnInitializeTable,
-            scope   : myDataTable,
-            argument: state});
-        };
-
-    $q_filter.click(function(){
-            if(!$q_filter.hasClass('loaded')){
-                //TODO: load here full list later to do search within groups
-                $q_filter.addClass('loaded');
-            }
-        });
-
-    $q_filter.keyup(function (e) {
-            clearTimeout(filterTimeout);
-            filterTimeout = setTimeout(updateFilter, 600);
-        });
-}
 
 /**
  Branch Sorting callback for select2, modifying the filtered result so prefix
@@ -1886,9 +1395,40 @@
                 return -1;
             }
 
+            // Put early (especially prefix) matches before later matches
+            var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
+                bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
+            if (aPos < bPos) {
+                return -1;
+            }
+            if (bPos < aPos) {
+                return 1;
+            }
+
+            // Default sorting
+            if (a.text > b.text) {
+                return 1;
+            }
+            if (a.text < b.text) {
+                return -1;
+            }
+            return 0;
+        });
+    }
+    return results;
+};
+
+var prefixFirstSort = function(results, container, query) {
+    if (query.term) {
+        return results.sort(function (a, b) {
+            // if parent node, no sorting
+            if (a.children != undefined || b.children != undefined) {
+                return 0;
+            }
+
             // Put prefix matches before matches in the line
-            var aPos = a.text.indexOf(query.term),
-                bPos = b.text.indexOf(query.term);
+            var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
+                bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
             if (aPos === 0 && bPos !== 0) {
                 return -1;
             }
@@ -1909,19 +1449,61 @@
     return results;
 };
 
-// global hooks after DOM is loaded
+/* Helper for jQuery DataTables */
+
+var updateRowCountCallback = function updateRowCountCallback($elem, onlyDisplayed) {
+    return function drawCallback() {
+        var info = this.api().page.info(),
+            count = onlyDisplayed === true ? info.recordsDisplay : info.recordsTotal;
+        $elem.html(count);
+    }
+};
+
+
+/**
+ * activate changeset parent/child navigation links
+ */
+var activate_parent_child_links = function(){
 
-$(document).ready(function(){
-    $('.diff-collapse-button').click(function(e) {
-        var $button = $(e.currentTarget);
-        var $target = $('#' + $button.attr('target'));
-        if($target.hasClass('hidden')){
-            $target.removeClass('hidden');
-            $button.html("&uarr; {0} &uarr;".format(_TM['Collapse Diff']));
-        }
-        else if(!$target.hasClass('hidden')){
-            $target.addClass('hidden');
-            $button.html("&darr; {0} &darr;".format(_TM['Expand Diff']));
+    $('.parent-child-link').on('click', function(e){
+        var $this = $(this);
+        //fetch via ajax what is going to be the next link, if we have
+        //>1 links show them to user to choose
+        if(!$this.hasClass('disabled')){
+            $.ajax({
+                url: $this.data('ajax-url'),
+                success: function(data) {
+                    var repo_name = $this.data('reponame');
+                    if(data.results.length === 0){
+                        $this.addClass('disabled');
+                        $this.text(_TM['No revisions']);
+                    }
+                    if(data.results.length === 1){
+                        var commit = data.results[0];
+                        window.location = pyroutes.url('changeset_home', {'repo_name': repo_name, 'revision': commit.raw_id});
+                    }
+                    else if(data.results.length > 1){
+                        $this.addClass('disabled');
+                        $this.addClass('double');
+                        var template =
+                            ($this.data('linktype') == 'parent' ? '<i class="icon-left-open"/> ' : '') +
+                            '<a title="__title__" href="__url__">__rev__</a>' +
+                            ($this.data('linktype') == 'child' ? ' <i class="icon-right-open"/>' : '');
+                        var _html = [];
+                        for(var i = 0; i < data.results.length; i++){
+                            _html.push(template
+                                .replace('__rev__', 'r{0}:{1}'.format(data.results[i].revision, data.results[i].raw_id.substr(0, 6)))
+                                .replace('__title__', data.results[i].message)
+                                .replace('__url__', pyroutes.url('changeset_home', {
+                                    'repo_name': repo_name,
+                                    'revision': data.results[i].raw_id}))
+                                );
+                        }
+                        $this.html(_html.join('<br/>'));
+                    }
+                }
+            });
+        e.preventDefault();
         }
     });
-});
+}
--- a/kallithea/public/js/bootstrap.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1999 +0,0 @@
-/**
-* bootstrap.js v3.0.0 by @fat and @mdo
-* Copyright 2013 Twitter Inc.
-* http://www.apache.org/licenses/LICENSE-2.0
-*/
-if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
-
-/* ========================================================================
- * Bootstrap: transition.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#transitions
- * ========================================================================
- * Copyright 2013 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
-  // ============================================================
-
-  function transitionEnd() {
-    var el = document.createElement('bootstrap')
-
-    var transEndEventNames = {
-      'WebkitTransition' : 'webkitTransitionEnd'
-    , 'MozTransition'    : 'transitionend'
-    , 'OTransition'      : 'oTransitionEnd otransitionend'
-    , 'transition'       : 'transitionend'
-    }
-
-    for (var name in transEndEventNames) {
-      if (el.style[name] !== undefined) {
-        return { end: transEndEventNames[name] }
-      }
-    }
-  }
-
-  // http://blog.alexmaccaw.com/css-transitions
-  $.fn.emulateTransitionEnd = function (duration) {
-    var called = false, $el = this
-    $(this).one($.support.transition.end, function () { called = true })
-    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
-    setTimeout(callback, duration)
-    return this
-  }
-
-  $(function () {
-    $.support.transition = transitionEnd()
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: alert.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#alerts
- * ========================================================================
- * Copyright 2013 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // ALERT CLASS DEFINITION
-  // ======================
-
-  var dismiss = '[data-dismiss="alert"]'
-  var Alert   = function (el) {
-    $(el).on('click', dismiss, this.close)
-  }
-
-  Alert.prototype.close = function (e) {
-    var $this    = $(this)
-    var selector = $this.attr('data-target')
-
-    if (!selector) {
-      selector = $this.attr('href')
-      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
-    }
-
-    var $parent = $(selector)
-
-    if (e) e.preventDefault()
-
-    if (!$parent.length) {
-      $parent = $this.hasClass('alert') ? $this : $this.parent()
-    }
-
-    $parent.trigger(e = $.Event('close.bs.alert'))
-
-    if (e.isDefaultPrevented()) return
-
-    $parent.removeClass('in')
-
-    function removeElement() {
-      $parent.trigger('closed.bs.alert').remove()
-    }
-
-    $.support.transition && $parent.hasClass('fade') ?
-      $parent
-        .one($.support.transition.end, removeElement)
-        .emulateTransitionEnd(150) :
-      removeElement()
-  }
-
-
-  // ALERT PLUGIN DEFINITION
-  // =======================
-
-  var old = $.fn.alert
-
-  $.fn.alert = function (option) {
-    return this.each(function () {
-      var $this = $(this)
-      var data  = $this.data('bs.alert')
-
-      if (!data) $this.data('bs.alert', (data = new Alert(this)))
-      if (typeof option == 'string') data[option].call($this)
-    })
-  }
-
-  $.fn.alert.Constructor = Alert
-
-
-  // ALERT NO CONFLICT
-  // =================
-
-  $.fn.alert.noConflict = function () {
-    $.fn.alert = old
-    return this
-  }
-
-
-  // ALERT DATA-API
-  // ==============
-
-  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: button.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#buttons
- * ========================================================================
- * Copyright 2013 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // BUTTON PUBLIC CLASS DEFINITION
-  // ==============================
-
-  var Button = function (element, options) {
-    this.$element = $(element)
-    this.options  = $.extend({}, Button.DEFAULTS, options)
-  }
-
-  Button.DEFAULTS = {
-    loadingText: 'loading...'
-  }
-
-  Button.prototype.setState = function (state) {
-    var d    = 'disabled'
-    var $el  = this.$element
-    var val  = $el.is('input') ? 'val' : 'html'
-    var data = $el.data()
-
-    state = state + 'Text'
-
-    if (!data.resetText) $el.data('resetText', $el[val]())
-
-    $el[val](data[state] || this.options[state])
-
-    // push to event loop to allow forms to submit
-    setTimeout(function () {
-      state == 'loadingText' ?
-        $el.addClass(d).attr(d, d) :
-        $el.removeClass(d).removeAttr(d);
-    }, 0)
-  }
-
-  Button.prototype.toggle = function () {
-    var $parent = this.$element.closest('[data-toggle="buttons"]')
-
-    if ($parent.length) {
-      var $input = this.$element.find('input')
-        .prop('checked', !this.$element.hasClass('active'))
-        .trigger('change')
-      if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
-    }
-
-    this.$element.toggleClass('active')
-  }
-
-
-  // BUTTON PLUGIN DEFINITION
-  // ========================
-
-  var old = $.fn.button
-
-  $.fn.button = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.button')
-      var options = typeof option == 'object' && option
-
-      if (!data) $this.data('bs.button', (data = new Button(this, options)))
-
-      if (option == 'toggle') data.toggle()
-      else if (option) data.setState(option)
-    })
-  }
-
-  $.fn.button.Constructor = Button
-
-
-  // BUTTON NO CONFLICT
-  // ==================
-
-  $.fn.button.noConflict = function () {
-    $.fn.button = old
-    return this
-  }
-
-
-  // BUTTON DATA-API
-  // ===============
-
-  $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
-    var $btn = $(e.target)
-    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
-    $btn.button('toggle')
-    e.preventDefault()
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: carousel.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#carousel
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // CAROUSEL CLASS DEFINITION
-  // =========================
-
-  var Carousel = function (element, options) {
-    this.$element    = $(element)
-    this.$indicators = this.$element.find('.carousel-indicators')
-    this.options     = options
-    this.paused      =
-    this.sliding     =
-    this.interval    =
-    this.$active     =
-    this.$items      = null
-
-    this.options.pause == 'hover' && this.$element
-      .on('mouseenter', $.proxy(this.pause, this))
-      .on('mouseleave', $.proxy(this.cycle, this))
-  }
-
-  Carousel.DEFAULTS = {
-    interval: 5000
-  , pause: 'hover'
-  , wrap: true
-  }
-
-  Carousel.prototype.cycle =  function (e) {
-    e || (this.paused = false)
-
-    this.interval && clearInterval(this.interval)
-
-    this.options.interval
-      && !this.paused
-      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
-
-    return this
-  }
-
-  Carousel.prototype.getActiveIndex = function () {
-    this.$active = this.$element.find('.item.active')
-    this.$items  = this.$active.parent().children()
-
-    return this.$items.index(this.$active)
-  }
-
-  Carousel.prototype.to = function (pos) {
-    var that        = this
-    var activeIndex = this.getActiveIndex()
-
-    if (pos > (this.$items.length - 1) || pos < 0) return
-
-    if (this.sliding)       return this.$element.one('slid', function () { that.to(pos) })
-    if (activeIndex == pos) return this.pause().cycle()
-
-    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
-  }
-
-  Carousel.prototype.pause = function (e) {
-    e || (this.paused = true)
-
-    if (this.$element.find('.next, .prev').length && $.support.transition.end) {
-      this.$element.trigger($.support.transition.end)
-      this.cycle(true)
-    }
-
-    this.interval = clearInterval(this.interval)
-
-    return this
-  }
-
-  Carousel.prototype.next = function () {
-    if (this.sliding) return
-    return this.slide('next')
-  }
-
-  Carousel.prototype.prev = function () {
-    if (this.sliding) return
-    return this.slide('prev')
-  }
-
-  Carousel.prototype.slide = function (type, next) {
-    var $active   = this.$element.find('.item.active')
-    var $next     = next || $active[type]()
-    var isCycling = this.interval
-    var direction = type == 'next' ? 'left' : 'right'
-    var fallback  = type == 'next' ? 'first' : 'last'
-    var that      = this
-
-    if (!$next.length) {
-      if (!this.options.wrap) return
-      $next = this.$element.find('.item')[fallback]()
-    }
-
-    this.sliding = true
-
-    isCycling && this.pause()
-
-    var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
-
-    if ($next.hasClass('active')) return
-
-    if (this.$indicators.length) {
-      this.$indicators.find('.active').removeClass('active')
-      this.$element.one('slid', function () {
-        var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
-        $nextIndicator && $nextIndicator.addClass('active')
-      })
-    }
-
-    if ($.support.transition && this.$element.hasClass('slide')) {
-      this.$element.trigger(e)
-      if (e.isDefaultPrevented()) return
-      $next.addClass(type)
-      $next[0].offsetWidth // force reflow
-      $active.addClass(direction)
-      $next.addClass(direction)
-      $active
-        .one($.support.transition.end, function () {
-          $next.removeClass([type, direction].join(' ')).addClass('active')
-          $active.removeClass(['active', direction].join(' '))
-          that.sliding = false
-          setTimeout(function () { that.$element.trigger('slid') }, 0)
-        })
-        .emulateTransitionEnd(600)
-    } else {
-      this.$element.trigger(e)
-      if (e.isDefaultPrevented()) return
-      $active.removeClass('active')
-      $next.addClass('active')
-      this.sliding = false
-      this.$element.trigger('slid')
-    }
-
-    isCycling && this.cycle()
-
-    return this
-  }
-
-
-  // CAROUSEL PLUGIN DEFINITION
-  // ==========================
-
-  var old = $.fn.carousel
-
-  $.fn.carousel = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.carousel')
-      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
-      var action  = typeof option == 'string' ? option : options.slide
-
-      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
-      if (typeof option == 'number') data.to(option)
-      else if (action) data[action]()
-      else if (options.interval) data.pause().cycle()
-    })
-  }
-
-  $.fn.carousel.Constructor = Carousel
-
-
-  // CAROUSEL NO CONFLICT
-  // ====================
-
-  $.fn.carousel.noConflict = function () {
-    $.fn.carousel = old
-    return this
-  }
-
-
-  // CAROUSEL DATA-API
-  // =================
-
-  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
-    var $this   = $(this), href
-    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
-    var options = $.extend({}, $target.data(), $this.data())
-    var slideIndex = $this.attr('data-slide-to')
-    if (slideIndex) options.interval = false
-
-    $target.carousel(options)
-
-    if (slideIndex = $this.attr('data-slide-to')) {
-      $target.data('bs.carousel').to(slideIndex)
-    }
-
-    e.preventDefault()
-  })
-
-  $(window).on('load', function () {
-    $('[data-ride="carousel"]').each(function () {
-      var $carousel = $(this)
-      $carousel.carousel($carousel.data())
-    })
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: collapse.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#collapse
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // COLLAPSE PUBLIC CLASS DEFINITION
-  // ================================
-
-  var Collapse = function (element, options) {
-    this.$element      = $(element)
-    this.options       = $.extend({}, Collapse.DEFAULTS, options)
-    this.transitioning = null
-
-    if (this.options.parent) this.$parent = $(this.options.parent)
-    if (this.options.toggle) this.toggle()
-  }
-
-  Collapse.DEFAULTS = {
-    toggle: true
-  }
-
-  Collapse.prototype.dimension = function () {
-    var hasWidth = this.$element.hasClass('width')
-    return hasWidth ? 'width' : 'height'
-  }
-
-  Collapse.prototype.show = function () {
-    if (this.transitioning || this.$element.hasClass('in')) return
-
-    var startEvent = $.Event('show.bs.collapse')
-    this.$element.trigger(startEvent)
-    if (startEvent.isDefaultPrevented()) return
-
-    var actives = this.$parent && this.$parent.find('> .panel > .in')
-
-    if (actives && actives.length) {
-      var hasData = actives.data('bs.collapse')
-      if (hasData && hasData.transitioning) return
-      actives.collapse('hide')
-      hasData || actives.data('bs.collapse', null)
-    }
-
-    var dimension = this.dimension()
-
-    this.$element
-      .removeClass('collapse')
-      .addClass('collapsing')
-      [dimension](0)
-
-    this.transitioning = 1
-
-    var complete = function () {
-      this.$element
-        .removeClass('collapsing')
-        .addClass('in')
-        [dimension]('auto')
-      this.transitioning = 0
-      this.$element.trigger('shown.bs.collapse')
-    }
-
-    if (!$.support.transition) return complete.call(this)
-
-    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
-
-    this.$element
-      .one($.support.transition.end, $.proxy(complete, this))
-      .emulateTransitionEnd(350)
-      [dimension](this.$element[0][scrollSize])
-  }
-
-  Collapse.prototype.hide = function () {
-    if (this.transitioning || !this.$element.hasClass('in')) return
-
-    var startEvent = $.Event('hide.bs.collapse')
-    this.$element.trigger(startEvent)
-    if (startEvent.isDefaultPrevented()) return
-
-    var dimension = this.dimension()
-
-    this.$element
-      [dimension](this.$element[dimension]())
-      [0].offsetHeight
-
-    this.$element
-      .addClass('collapsing')
-      .removeClass('collapse')
-      .removeClass('in')
-
-    this.transitioning = 1
-
-    var complete = function () {
-      this.transitioning = 0
-      this.$element
-        .trigger('hidden.bs.collapse')
-        .removeClass('collapsing')
-        .addClass('collapse')
-    }
-
-    if (!$.support.transition) return complete.call(this)
-
-    this.$element
-      [dimension](0)
-      .one($.support.transition.end, $.proxy(complete, this))
-      .emulateTransitionEnd(350)
-  }
-
-  Collapse.prototype.toggle = function () {
-    this[this.$element.hasClass('in') ? 'hide' : 'show']()
-  }
-
-
-  // COLLAPSE PLUGIN DEFINITION
-  // ==========================
-
-  var old = $.fn.collapse
-
-  $.fn.collapse = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.collapse')
-      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
-
-      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.collapse.Constructor = Collapse
-
-
-  // COLLAPSE NO CONFLICT
-  // ====================
-
-  $.fn.collapse.noConflict = function () {
-    $.fn.collapse = old
-    return this
-  }
-
-
-  // COLLAPSE DATA-API
-  // =================
-
-  $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
-    var $this   = $(this), href
-    var target  = $this.attr('data-target')
-        || e.preventDefault()
-        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
-    var $target = $(target)
-    var data    = $target.data('bs.collapse')
-    var option  = data ? 'toggle' : $this.data()
-    var parent  = $this.attr('data-parent')
-    var $parent = parent && $(parent)
-
-    if (!data || !data.transitioning) {
-      if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
-      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
-    }
-
-    $target.collapse(option)
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: dropdown.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#dropdowns
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // DROPDOWN CLASS DEFINITION
-  // =========================
-
-  var backdrop = '.dropdown-backdrop'
-  var toggle   = '[data-toggle=dropdown]'
-  var Dropdown = function (element) {
-    var $el = $(element).on('click.bs.dropdown', this.toggle)
-  }
-
-  Dropdown.prototype.toggle = function (e) {
-    var $this = $(this)
-
-    if ($this.is('.disabled, :disabled')) return
-
-    var $parent  = getParent($this)
-    var isActive = $parent.hasClass('open')
-
-    clearMenus()
-
-    if (!isActive) {
-      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
-        // if mobile we we use a backdrop because click events don't delegate
-        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
-      }
-
-      $parent.trigger(e = $.Event('show.bs.dropdown'))
-
-      if (e.isDefaultPrevented()) return
-
-      $parent
-        .toggleClass('open')
-        .trigger('shown.bs.dropdown')
-
-      $this.focus()
-    }
-
-    return false
-  }
-
-  Dropdown.prototype.keydown = function (e) {
-    if (!/(38|40|27)/.test(e.keyCode)) return
-
-    var $this = $(this)
-
-    e.preventDefault()
-    e.stopPropagation()
-
-    if ($this.is('.disabled, :disabled')) return
-
-    var $parent  = getParent($this)
-    var isActive = $parent.hasClass('open')
-
-    if (!isActive || (isActive && e.keyCode == 27)) {
-      if (e.which == 27) $parent.find(toggle).focus()
-      return $this.click()
-    }
-
-    var $items = $('[role=menu] li:not(.divider):visible a', $parent)
-
-    if (!$items.length) return
-
-    var index = $items.index($items.filter(':focus'))
-
-    if (e.keyCode == 38 && index > 0)                 index--                        // up
-    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
-    if (!~index)                                      index=0
-
-    $items.eq(index).focus()
-  }
-
-  function clearMenus() {
-    $(backdrop).remove()
-    $(toggle).each(function (e) {
-      var $parent = getParent($(this))
-      if (!$parent.hasClass('open')) return
-      $parent.trigger(e = $.Event('hide.bs.dropdown'))
-      if (e.isDefaultPrevented()) return
-      $parent.removeClass('open').trigger('hidden.bs.dropdown')
-    })
-  }
-
-  function getParent($this) {
-    var selector = $this.attr('data-target')
-
-    if (!selector) {
-      selector = $this.attr('href')
-      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
-    }
-
-    var $parent = selector && $(selector)
-
-    return $parent && $parent.length ? $parent : $this.parent()
-  }
-
-
-  // DROPDOWN PLUGIN DEFINITION
-  // ==========================
-
-  var old = $.fn.dropdown
-
-  $.fn.dropdown = function (option) {
-    return this.each(function () {
-      var $this = $(this)
-      var data  = $this.data('dropdown')
-
-      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
-      if (typeof option == 'string') data[option].call($this)
-    })
-  }
-
-  $.fn.dropdown.Constructor = Dropdown
-
-
-  // DROPDOWN NO CONFLICT
-  // ====================
-
-  $.fn.dropdown.noConflict = function () {
-    $.fn.dropdown = old
-    return this
-  }
-
-
-  // APPLY TO STANDARD DROPDOWN ELEMENTS
-  // ===================================
-
-  $(document)
-    .on('click.bs.dropdown.data-api', clearMenus)
-    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
-    .on('click.bs.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
-    .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: modal.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#modals
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // MODAL CLASS DEFINITION
-  // ======================
-
-  var Modal = function (element, options) {
-    this.options   = options
-    this.$element  = $(element)
-    this.$backdrop =
-    this.isShown   = null
-
-    if (this.options.remote) this.$element.load(this.options.remote)
-  }
-
-  Modal.DEFAULTS = {
-      backdrop: true
-    , keyboard: true
-    , show: true
-  }
-
-  Modal.prototype.toggle = function (_relatedTarget) {
-    return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
-  }
-
-  Modal.prototype.show = function (_relatedTarget) {
-    var that = this
-    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
-
-    this.$element.trigger(e)
-
-    if (this.isShown || e.isDefaultPrevented()) return
-
-    this.isShown = true
-
-    this.escape()
-
-    this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
-
-    this.backdrop(function () {
-      var transition = $.support.transition && that.$element.hasClass('fade')
-
-      if (!that.$element.parent().length) {
-        that.$element.appendTo(document.body) // don't move modals dom position
-      }
-
-      that.$element.show()
-
-      if (transition) {
-        that.$element[0].offsetWidth // force reflow
-      }
-
-      that.$element
-        .addClass('in')
-        .attr('aria-hidden', false)
-
-      that.enforceFocus()
-
-      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
-
-      transition ?
-        that.$element.find('.modal-dialog') // wait for modal to slide in
-          .one($.support.transition.end, function () {
-            that.$element.focus().trigger(e)
-          })
-          .emulateTransitionEnd(300) :
-        that.$element.focus().trigger(e)
-    })
-  }
-
-  Modal.prototype.hide = function (e) {
-    if (e) e.preventDefault()
-
-    e = $.Event('hide.bs.modal')
-
-    this.$element.trigger(e)
-
-    if (!this.isShown || e.isDefaultPrevented()) return
-
-    this.isShown = false
-
-    this.escape()
-
-    $(document).off('focusin.bs.modal')
-
-    this.$element
-      .removeClass('in')
-      .attr('aria-hidden', true)
-      .off('click.dismiss.modal')
-
-    $.support.transition && this.$element.hasClass('fade') ?
-      this.$element
-        .one($.support.transition.end, $.proxy(this.hideModal, this))
-        .emulateTransitionEnd(300) :
-      this.hideModal()
-  }
-
-  Modal.prototype.enforceFocus = function () {
-    $(document)
-      .off('focusin.bs.modal') // guard against infinite focus loop
-      .on('focusin.bs.modal', $.proxy(function (e) {
-        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
-          this.$element.focus()
-        }
-      }, this))
-  }
-
-  Modal.prototype.escape = function () {
-    if (this.isShown && this.options.keyboard) {
-      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
-        e.which == 27 && this.hide()
-      }, this))
-    } else if (!this.isShown) {
-      this.$element.off('keyup.dismiss.bs.modal')
-    }
-  }
-
-  Modal.prototype.hideModal = function () {
-    var that = this
-    this.$element.hide()
-    this.backdrop(function () {
-      that.removeBackdrop()
-      that.$element.trigger('hidden.bs.modal')
-    })
-  }
-
-  Modal.prototype.removeBackdrop = function () {
-    this.$backdrop && this.$backdrop.remove()
-    this.$backdrop = null
-  }
-
-  Modal.prototype.backdrop = function (callback) {
-    var that    = this
-    var animate = this.$element.hasClass('fade') ? 'fade' : ''
-
-    if (this.isShown && this.options.backdrop) {
-      var doAnimate = $.support.transition && animate
-
-      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
-        .appendTo(document.body)
-
-      this.$element.on('click.dismiss.modal', $.proxy(function (e) {
-        if (e.target !== e.currentTarget) return
-        this.options.backdrop == 'static'
-          ? this.$element[0].focus.call(this.$element[0])
-          : this.hide.call(this)
-      }, this))
-
-      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
-
-      this.$backdrop.addClass('in')
-
-      if (!callback) return
-
-      doAnimate ?
-        this.$backdrop
-          .one($.support.transition.end, callback)
-          .emulateTransitionEnd(150) :
-        callback()
-
-    } else if (!this.isShown && this.$backdrop) {
-      this.$backdrop.removeClass('in')
-
-      $.support.transition && this.$element.hasClass('fade')?
-        this.$backdrop
-          .one($.support.transition.end, callback)
-          .emulateTransitionEnd(150) :
-        callback()
-
-    } else if (callback) {
-      callback()
-    }
-  }
-
-
-  // MODAL PLUGIN DEFINITION
-  // =======================
-
-  var old = $.fn.modal
-
-  $.fn.modal = function (option, _relatedTarget) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.modal')
-      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
-
-      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
-      if (typeof option == 'string') data[option](_relatedTarget)
-      else if (options.show) data.show(_relatedTarget)
-    })
-  }
-
-  $.fn.modal.Constructor = Modal
-
-
-  // MODAL NO CONFLICT
-  // =================
-
-  $.fn.modal.noConflict = function () {
-    $.fn.modal = old
-    return this
-  }
-
-
-  // MODAL DATA-API
-  // ==============
-
-  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
-    var $this   = $(this)
-    var href    = $this.attr('href')
-    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
-    var option  = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
-
-    e.preventDefault()
-
-    $target
-      .modal(option, this)
-      .one('hide', function () {
-        $this.is(':visible') && $this.focus()
-      })
-  })
-
-  $(document)
-    .on('show.bs.modal',  '.modal', function () { $(document.body).addClass('modal-open') })
-    .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: tooltip.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#tooltip
- * Inspired by the original jQuery.tipsy by Jason Frame
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // TOOLTIP PUBLIC CLASS DEFINITION
-  // ===============================
-
-  var Tooltip = function (element, options) {
-    this.type       =
-    this.options    =
-    this.enabled    =
-    this.timeout    =
-    this.hoverState =
-    this.$element   = null
-
-    this.init('tooltip', element, options)
-  }
-
-  Tooltip.DEFAULTS = {
-    animation: true
-  , placement: 'top'
-  , selector: false
-  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
-  , trigger: 'hover focus'
-  , title: ''
-  , delay: 0
-  , html: false
-  , container: false
-  }
-
-  Tooltip.prototype.init = function (type, element, options) {
-    this.enabled  = true
-    this.type     = type
-    this.$element = $(element)
-    this.options  = this.getOptions(options)
-
-    var triggers = this.options.trigger.split(' ')
-
-    for (var i = triggers.length; i--;) {
-      var trigger = triggers[i]
-
-      if (trigger == 'click') {
-        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
-      } else if (trigger != 'manual') {
-        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus'
-        var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
-
-        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
-        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
-      }
-    }
-
-    this.options.selector ?
-      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
-      this.fixTitle()
-  }
-
-  Tooltip.prototype.getDefaults = function () {
-    return Tooltip.DEFAULTS
-  }
-
-  Tooltip.prototype.getOptions = function (options) {
-    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
-
-    if (options.delay && typeof options.delay == 'number') {
-      options.delay = {
-        show: options.delay
-      , hide: options.delay
-      }
-    }
-
-    return options
-  }
-
-  Tooltip.prototype.getDelegateOptions = function () {
-    var options  = {}
-    var defaults = this.getDefaults()
-
-    this._options && $.each(this._options, function (key, value) {
-      if (defaults[key] != value) options[key] = value
-    })
-
-    return options
-  }
-
-  Tooltip.prototype.enter = function (obj) {
-    var self = obj instanceof this.constructor ?
-      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
-
-    clearTimeout(self.timeout)
-
-    self.hoverState = 'in'
-
-    if (!self.options.delay || !self.options.delay.show) return self.show()
-
-    self.timeout = setTimeout(function () {
-      if (self.hoverState == 'in') self.show()
-    }, self.options.delay.show)
-  }
-
-  Tooltip.prototype.leave = function (obj) {
-    var self = obj instanceof this.constructor ?
-      obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
-
-    clearTimeout(self.timeout)
-
-    self.hoverState = 'out'
-
-    if (!self.options.delay || !self.options.delay.hide) return self.hide()
-
-    self.timeout = setTimeout(function () {
-      if (self.hoverState == 'out') self.hide()
-    }, self.options.delay.hide)
-  }
-
-  Tooltip.prototype.show = function () {
-    var e = $.Event('show.bs.'+ this.type)
-
-    if (this.hasContent() && this.enabled) {
-      this.$element.trigger(e)
-
-      if (e.isDefaultPrevented()) return
-
-      var $tip = this.tip()
-
-      this.setContent()
-
-      if (this.options.animation) $tip.addClass('fade')
-
-      var placement = typeof this.options.placement == 'function' ?
-        this.options.placement.call(this, $tip[0], this.$element[0]) :
-        this.options.placement
-
-      var autoToken = /\s?auto?\s?/i
-      var autoPlace = autoToken.test(placement)
-      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
-
-      $tip
-        .detach()
-        .css({ top: 0, left: 0, display: 'block' })
-        .addClass(placement)
-
-      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
-
-      var pos          = this.getPosition()
-      var actualWidth  = $tip[0].offsetWidth
-      var actualHeight = $tip[0].offsetHeight
-
-      if (autoPlace) {
-        var $parent = this.$element.parent()
-
-        var orgPlacement = placement
-        var docScroll    = document.documentElement.scrollTop || document.body.scrollTop
-        var parentWidth  = this.options.container == 'body' ? window.innerWidth  : $parent.outerWidth()
-        var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
-        var parentLeft   = this.options.container == 'body' ? 0 : $parent.offset().left
-
-        placement = placement == 'bottom' && pos.top   + pos.height  + actualHeight - docScroll > parentHeight  ? 'top'    :
-                    placement == 'top'    && pos.top   - docScroll   - actualHeight < 0                         ? 'bottom' :
-                    placement == 'right'  && pos.right + actualWidth > parentWidth                              ? 'left'   :
-                    placement == 'left'   && pos.left  - actualWidth < parentLeft                               ? 'right'  :
-                    placement
-
-        $tip
-          .removeClass(orgPlacement)
-          .addClass(placement)
-      }
-
-      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
-
-      this.applyPlacement(calculatedOffset, placement)
-      this.$element.trigger('shown.bs.' + this.type)
-    }
-  }
-
-  Tooltip.prototype.applyPlacement = function(offset, placement) {
-    var replace
-    var $tip   = this.tip()
-    var width  = $tip[0].offsetWidth
-    var height = $tip[0].offsetHeight
-
-    // manually read margins because getBoundingClientRect includes difference
-    var marginTop = parseInt($tip.css('margin-top'), 10)
-    var marginLeft = parseInt($tip.css('margin-left'), 10)
-
-    // we must check for NaN for ie 8/9
-    if (isNaN(marginTop))  marginTop  = 0
-    if (isNaN(marginLeft)) marginLeft = 0
-
-    offset.top  = offset.top  + marginTop
-    offset.left = offset.left + marginLeft
-
-    $tip
-      .offset(offset)
-      .addClass('in')
-
-    // check to see if placing tip in new offset caused the tip to resize itself
-    var actualWidth  = $tip[0].offsetWidth
-    var actualHeight = $tip[0].offsetHeight
-
-    if (placement == 'top' && actualHeight != height) {
-      replace = true
-      offset.top = offset.top + height - actualHeight
-    }
-
-    if (/bottom|top/.test(placement)) {
-      var delta = 0
-
-      if (offset.left < 0) {
-        delta       = offset.left * -2
-        offset.left = 0
-
-        $tip.offset(offset)
-
-        actualWidth  = $tip[0].offsetWidth
-        actualHeight = $tip[0].offsetHeight
-      }
-
-      this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
-    } else {
-      this.replaceArrow(actualHeight - height, actualHeight, 'top')
-    }
-
-    if (replace) $tip.offset(offset)
-  }
-
-  Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
-    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
-  }
-
-  Tooltip.prototype.setContent = function () {
-    var $tip  = this.tip()
-    var title = this.getTitle()
-
-    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
-    $tip.removeClass('fade in top bottom left right')
-  }
-
-  Tooltip.prototype.hide = function () {
-    var that = this
-    var $tip = this.tip()
-    var e    = $.Event('hide.bs.' + this.type)
-
-    function complete() {
-      if (that.hoverState != 'in') $tip.detach()
-    }
-
-    this.$element.trigger(e)
-
-    if (e.isDefaultPrevented()) return
-
-    $tip.removeClass('in')
-
-    $.support.transition && this.$tip.hasClass('fade') ?
-      $tip
-        .one($.support.transition.end, complete)
-        .emulateTransitionEnd(150) :
-      complete()
-
-    this.$element.trigger('hidden.bs.' + this.type)
-
-    return this
-  }
-
-  Tooltip.prototype.fixTitle = function () {
-    var $e = this.$element
-    if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
-      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
-    }
-  }
-
-  Tooltip.prototype.hasContent = function () {
-    return this.getTitle()
-  }
-
-  Tooltip.prototype.getPosition = function () {
-    var el = this.$element[0]
-    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
-      width: el.offsetWidth
-    , height: el.offsetHeight
-    }, this.$element.offset())
-  }
-
-  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
-    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
-           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
-           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
-        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
-  }
-
-  Tooltip.prototype.getTitle = function () {
-    var title
-    var $e = this.$element
-    var o  = this.options
-
-    title = $e.attr('data-original-title')
-      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
-
-    return title
-  }
-
-  Tooltip.prototype.tip = function () {
-    return this.$tip = this.$tip || $(this.options.template)
-  }
-
-  Tooltip.prototype.arrow = function () {
-    return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
-  }
-
-  Tooltip.prototype.validate = function () {
-    if (!this.$element[0].parentNode) {
-      this.hide()
-      this.$element = null
-      this.options  = null
-    }
-  }
-
-  Tooltip.prototype.enable = function () {
-    this.enabled = true
-  }
-
-  Tooltip.prototype.disable = function () {
-    this.enabled = false
-  }
-
-  Tooltip.prototype.toggleEnabled = function () {
-    this.enabled = !this.enabled
-  }
-
-  Tooltip.prototype.toggle = function (e) {
-    var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
-    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
-  }
-
-  Tooltip.prototype.destroy = function () {
-    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
-  }
-
-
-  // TOOLTIP PLUGIN DEFINITION
-  // =========================
-
-  var old = $.fn.tooltip
-
-  $.fn.tooltip = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.tooltip')
-      var options = typeof option == 'object' && option
-
-      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.tooltip.Constructor = Tooltip
-
-
-  // TOOLTIP NO CONFLICT
-  // ===================
-
-  $.fn.tooltip.noConflict = function () {
-    $.fn.tooltip = old
-    return this
-  }
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: popover.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#popovers
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // POPOVER PUBLIC CLASS DEFINITION
-  // ===============================
-
-  var Popover = function (element, options) {
-    this.init('popover', element, options)
-  }
-
-  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
-
-  Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
-    placement: 'right'
-  , trigger: 'click'
-  , content: ''
-  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
-  })
-
-
-  // NOTE: POPOVER EXTENDS tooltip.js
-  // ================================
-
-  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
-
-  Popover.prototype.constructor = Popover
-
-  Popover.prototype.getDefaults = function () {
-    return Popover.DEFAULTS
-  }
-
-  Popover.prototype.setContent = function () {
-    var $tip    = this.tip()
-    var title   = this.getTitle()
-    var content = this.getContent()
-
-    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
-    $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
-
-    $tip.removeClass('fade top bottom left right in')
-
-    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
-    // this manually by checking the contents.
-    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
-  }
-
-  Popover.prototype.hasContent = function () {
-    return this.getTitle() || this.getContent()
-  }
-
-  Popover.prototype.getContent = function () {
-    var $e = this.$element
-    var o  = this.options
-
-    return $e.attr('data-content')
-      || (typeof o.content == 'function' ?
-            o.content.call($e[0]) :
-            o.content)
-  }
-
-  Popover.prototype.arrow = function () {
-    return this.$arrow = this.$arrow || this.tip().find('.arrow')
-  }
-
-  Popover.prototype.tip = function () {
-    if (!this.$tip) this.$tip = $(this.options.template)
-    return this.$tip
-  }
-
-
-  // POPOVER PLUGIN DEFINITION
-  // =========================
-
-  var old = $.fn.popover
-
-  $.fn.popover = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.popover')
-      var options = typeof option == 'object' && option
-
-      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.popover.Constructor = Popover
-
-
-  // POPOVER NO CONFLICT
-  // ===================
-
-  $.fn.popover.noConflict = function () {
-    $.fn.popover = old
-    return this
-  }
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: scrollspy.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#scrollspy
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // SCROLLSPY CLASS DEFINITION
-  // ==========================
-
-  function ScrollSpy(element, options) {
-    var href
-    var process  = $.proxy(this.process, this)
-
-    this.$element       = $(element).is('body') ? $(window) : $(element)
-    this.$body          = $('body')
-    this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
-    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
-    this.selector       = (this.options.target
-      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
-      || '') + ' .nav li > a'
-    this.offsets        = $([])
-    this.targets        = $([])
-    this.activeTarget   = null
-
-    this.refresh()
-    this.process()
-  }
-
-  ScrollSpy.DEFAULTS = {
-    offset: 10
-  }
-
-  ScrollSpy.prototype.refresh = function () {
-    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
-
-    this.offsets = $([])
-    this.targets = $([])
-
-    var self     = this
-    var $targets = this.$body
-      .find(this.selector)
-      .map(function () {
-        var $el   = $(this)
-        var href  = $el.data('target') || $el.attr('href')
-        var $href = /^#\w/.test(href) && $(href)
-
-        return ($href
-          && $href.length
-          && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
-      })
-      .sort(function (a, b) { return a[0] - b[0] })
-      .each(function () {
-        self.offsets.push(this[0])
-        self.targets.push(this[1])
-      })
-  }
-
-  ScrollSpy.prototype.process = function () {
-    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
-    var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
-    var maxScroll    = scrollHeight - this.$scrollElement.height()
-    var offsets      = this.offsets
-    var targets      = this.targets
-    var activeTarget = this.activeTarget
-    var i
-
-    if (scrollTop >= maxScroll) {
-      return activeTarget != (i = targets.last()[0]) && this.activate(i)
-    }
-
-    for (i = offsets.length; i--;) {
-      activeTarget != targets[i]
-        && scrollTop >= offsets[i]
-        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
-        && this.activate( targets[i] )
-    }
-  }
-
-  ScrollSpy.prototype.activate = function (target) {
-    this.activeTarget = target
-
-    $(this.selector)
-      .parents('.active')
-      .removeClass('active')
-
-    var selector = this.selector
-      + '[data-target="' + target + '"],'
-      + this.selector + '[href="' + target + '"]'
-
-    var active = $(selector)
-      .parents('li')
-      .addClass('active')
-
-    if (active.parent('.dropdown-menu').length)  {
-      active = active
-        .closest('li.dropdown')
-        .addClass('active')
-    }
-
-    active.trigger('activate')
-  }
-
-
-  // SCROLLSPY PLUGIN DEFINITION
-  // ===========================
-
-  var old = $.fn.scrollspy
-
-  $.fn.scrollspy = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.scrollspy')
-      var options = typeof option == 'object' && option
-
-      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.scrollspy.Constructor = ScrollSpy
-
-
-  // SCROLLSPY NO CONFLICT
-  // =====================
-
-  $.fn.scrollspy.noConflict = function () {
-    $.fn.scrollspy = old
-    return this
-  }
-
-
-  // SCROLLSPY DATA-API
-  // ==================
-
-  $(window).on('load', function () {
-    $('[data-spy="scroll"]').each(function () {
-      var $spy = $(this)
-      $spy.scrollspy($spy.data())
-    })
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: tab.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#tabs
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // TAB CLASS DEFINITION
-  // ====================
-
-  var Tab = function (element) {
-    this.element = $(element)
-  }
-
-  Tab.prototype.show = function () {
-    var $this    = this.element
-    var $ul      = $this.closest('ul:not(.dropdown-menu)')
-    var selector = $this.attr('data-target')
-
-    if (!selector) {
-      selector = $this.attr('href')
-      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
-    }
-
-    if ($this.parent('li').hasClass('active')) return
-
-    var previous = $ul.find('.active:last a')[0]
-    var e        = $.Event('show.bs.tab', {
-      relatedTarget: previous
-    })
-
-    $this.trigger(e)
-
-    if (e.isDefaultPrevented()) return
-
-    var $target = $(selector)
-
-    this.activate($this.parent('li'), $ul)
-    this.activate($target, $target.parent(), function () {
-      $this.trigger({
-        type: 'shown.bs.tab'
-      , relatedTarget: previous
-      })
-    })
-  }
-
-  Tab.prototype.activate = function (element, container, callback) {
-    var $active    = container.find('> .active')
-    var transition = callback
-      && $.support.transition
-      && $active.hasClass('fade')
-
-    function next() {
-      $active
-        .removeClass('active')
-        .find('> .dropdown-menu > .active')
-        .removeClass('active')
-
-      element.addClass('active')
-
-      if (transition) {
-        element[0].offsetWidth // reflow for transition
-        element.addClass('in')
-      } else {
-        element.removeClass('fade')
-      }
-
-      if (element.parent('.dropdown-menu')) {
-        element.closest('li.dropdown').addClass('active')
-      }
-
-      callback && callback()
-    }
-
-    transition ?
-      $active
-        .one($.support.transition.end, next)
-        .emulateTransitionEnd(150) :
-      next()
-
-    $active.removeClass('in')
-  }
-
-
-  // TAB PLUGIN DEFINITION
-  // =====================
-
-  var old = $.fn.tab
-
-  $.fn.tab = function ( option ) {
-    return this.each(function () {
-      var $this = $(this)
-      var data  = $this.data('bs.tab')
-
-      if (!data) $this.data('bs.tab', (data = new Tab(this)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.tab.Constructor = Tab
-
-
-  // TAB NO CONFLICT
-  // ===============
-
-  $.fn.tab.noConflict = function () {
-    $.fn.tab = old
-    return this
-  }
-
-
-  // TAB DATA-API
-  // ============
-
-  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
-    e.preventDefault()
-    $(this).tab('show')
-  })
-
-}(window.jQuery);
-
-/* ========================================================================
- * Bootstrap: affix.js v3.0.0
- * http://twbs.github.com/bootstrap/javascript.html#affix
- * ========================================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================================== */
-
-
-+function ($) { "use strict";
-
-  // AFFIX CLASS DEFINITION
-  // ======================
-
-  var Affix = function (element, options) {
-    this.options = $.extend({}, Affix.DEFAULTS, options)
-    this.$window = $(window)
-      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
-      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
-
-    this.$element = $(element)
-    this.affixed  =
-    this.unpin    = null
-
-    this.checkPosition()
-  }
-
-  Affix.RESET = 'affix affix-top affix-bottom'
-
-  Affix.DEFAULTS = {
-    offset: 0
-  }
-
-  Affix.prototype.checkPositionWithEventLoop = function () {
-    setTimeout($.proxy(this.checkPosition, this), 1)
-  }
-
-  Affix.prototype.checkPosition = function () {
-    if (!this.$element.is(':visible')) return
-
-    var scrollHeight = $(document).height()
-    var scrollTop    = this.$window.scrollTop()
-    var position     = this.$element.offset()
-    var offset       = this.options.offset
-    var offsetTop    = offset.top
-    var offsetBottom = offset.bottom
-
-    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
-    if (typeof offsetTop == 'function')    offsetTop    = offset.top()
-    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
-
-    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
-                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
-                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
-
-    if (this.affixed === affix) return
-    if (this.unpin) this.$element.css('top', '')
-
-    this.affixed = affix
-    this.unpin   = affix == 'bottom' ? position.top - scrollTop : null
-
-    this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
-
-    if (affix == 'bottom') {
-      this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
-    }
-  }
-
-
-  // AFFIX PLUGIN DEFINITION
-  // =======================
-
-  var old = $.fn.affix
-
-  $.fn.affix = function (option) {
-    return this.each(function () {
-      var $this   = $(this)
-      var data    = $this.data('bs.affix')
-      var options = typeof option == 'object' && option
-
-      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
-      if (typeof option == 'string') data[option]()
-    })
-  }
-
-  $.fn.affix.Constructor = Affix
-
-
-  // AFFIX NO CONFLICT
-  // =================
-
-  $.fn.affix.noConflict = function () {
-    $.fn.affix = old
-    return this
-  }
-
-
-  // AFFIX DATA-API
-  // ==============
-
-  $(window).on('load', function () {
-    $('[data-spy="affix"]').each(function () {
-      var $spy = $(this)
-      var data = $spy.data()
-
-      data.offset = data.offset || {}
-
-      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
-      if (data.offsetTop)    data.offset.top    = data.offsetTop
-
-      $spy.affix(data)
-    })
-  })
-
-}(window.jQuery);
--- a/kallithea/public/js/graph.js	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/public/js/graph.js	Sun Mar 31 21:28:56 2019 +0200
@@ -28,11 +28,11 @@
 	// row_id_prefix is prefix that is applied to get row id's
 	this.canvas = document.getElementById(canvas_id);
 	var content = document.getElementById(content_id);
-	
+
 	if (!document.createElement("canvas").getContext)
 		this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
 	if (!this.canvas) { // canvas creation did for some reason fail - fail silently
-		this.render = function(data,canvasWidth) {};
+		this.render = function(data) {};
 		return;
 	}
 	this.ctx = this.canvas.getContext('2d');
@@ -62,8 +62,9 @@
 		this.ctx.fillStyle = s;
 	}
 
-	this.render = function(data,canvasWidth) {
+	this.render = function(data) {
 		var idx = 1;
+		var canvasWidth = $(this.canvas).parent().width();
 
 		this.canvas.setAttribute('width',canvasWidth);
 		this.canvas.setAttribute('height',content.clientHeight);
@@ -100,15 +101,22 @@
 			}
 			var next = document.getElementById(row_id_prefix+(idx+1));
 			var extra = 0;
-			
+
 			cur = data[i];
 			node = cur[0];
 			in_l = cur[1];
 			closing = cur[2];
 			obsolete_node = cur[3];
+			bumped_node = cur[4];
+			divergent_node = cur[5];
+			extinct_node = cur[6];
+			unstable_node = cur[7];
 
-			var rowY = row.offsetTop + row.offsetHeight/2;
-			var nextY = (next == null) ? rowY + row.offsetHeight/2 : next.offsetTop + next.offsetHeight/2;
+			// center dots on the first element in a td (not necessarily the first one, but there must be one)
+			var firstincell = $(row).find('td>*:visible')[0];
+			var nextFirstincell = $(next).find('td>*:visible')[0];
+			var rowY = Math.floor(row.offsetTop + firstincell.offsetTop + firstincell.offsetHeight/2);
+			var nextY = Math.floor((next == null) ? rowY + row.offsetHeight/2 : next.offsetTop + nextFirstincell.offsetTop + nextFirstincell.offsetHeight/2);
 
 			for (var j in in_l) {
 				line = in_l[j];
@@ -116,7 +124,7 @@
 				end = line[1];
 				color = line[2];
 				obsolete_line = line[3];
-				
+
 				x = Math.floor(base_x - box_size * start);
 
 				// figure out if this is a dead-end;
@@ -157,7 +165,7 @@
 				{
 					this.setColor(color, 0.0, 0.65);
 				}
-				
+
 				this.ctx.lineWidth=this.line_width;
 				this.ctx.beginPath();
 				if (obsolete_line)
@@ -183,14 +191,17 @@
 				this.ctx.stroke();
 				this.ctx.setLineDash([]); // reset the dashed line, if any
 			}
-			
+
 			column = node[0];
 			color = node[1];
-			
+
 			x = Math.floor(base_x - box_size * column);
-		
+
 			this.setColor(color, 0.25, 0.75);
-
+			if(unstable_node)
+			{
+				this.ctx.fillStyle = 'rgb(255, 0, 0)';
+			}
 
 			r = this.dot_radius
 			if (obsolete_node)
@@ -216,7 +227,7 @@
 
 			idx++;
 		}
-				
+
 	}
 
 }
--- a/kallithea/public/js/jquery-1.11.1.min.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
-!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;
-if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")
-},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.ActiveXObject&&m(a).on("unload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
--- a/kallithea/public/js/mousetrap.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,931 +0,0 @@
-/*global define:false */
-/**
- * Copyright 2013 Craig Campbell
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Mousetrap is a simple keyboard shortcut library for Javascript with
- * no external dependencies
- *
- * @version 1.4.5
- * @url craig.is/killing/mice
- */
-(function(window, document, undefined) {
-
-    /**
-     * mapping of special keycodes to their corresponding keys
-     *
-     * everything in this dictionary cannot use keypress events
-     * so it has to be here to map to the correct keycodes for
-     * keyup/keydown events
-     *
-     * @type {Object}
-     */
-    var _MAP = {
-            8: 'backspace',
-            9: 'tab',
-            13: 'enter',
-            16: 'shift',
-            17: 'ctrl',
-            18: 'alt',
-            20: 'capslock',
-            27: 'esc',
-            32: 'space',
-            33: 'pageup',
-            34: 'pagedown',
-            35: 'end',
-            36: 'home',
-            37: 'left',
-            38: 'up',
-            39: 'right',
-            40: 'down',
-            45: 'ins',
-            46: 'del',
-            91: 'meta',
-            93: 'meta',
-            224: 'meta'
-        },
-
-        /**
-         * mapping for special characters so they can support
-         *
-         * this dictionary is only used incase you want to bind a
-         * keyup or keydown event to one of these keys
-         *
-         * @type {Object}
-         */
-        _KEYCODE_MAP = {
-            106: '*',
-            107: '+',
-            109: '-',
-            110: '.',
-            111 : '/',
-            186: ';',
-            187: '=',
-            188: ',',
-            189: '-',
-            190: '.',
-            191: '/',
-            192: '`',
-            219: '[',
-            220: '\\',
-            221: ']',
-            222: '\''
-        },
-
-        /**
-         * this is a mapping of keys that require shift on a US keypad
-         * back to the non shift equivelents
-         *
-         * this is so you can use keyup events with these keys
-         *
-         * note that this will only work reliably on US keyboards
-         *
-         * @type {Object}
-         */
-        _SHIFT_MAP = {
-            '~': '`',
-            '!': '1',
-            '@': '2',
-            '#': '3',
-            '$': '4',
-            '%': '5',
-            '^': '6',
-            '&': '7',
-            '*': '8',
-            '(': '9',
-            ')': '0',
-            '_': '-',
-            '+': '=',
-            ':': ';',
-            '\"': '\'',
-            '<': ',',
-            '>': '.',
-            '?': '/',
-            '|': '\\'
-        },
-
-        /**
-         * this is a list of special strings you can use to map
-         * to modifier keys when you specify your keyboard shortcuts
-         *
-         * @type {Object}
-         */
-        _SPECIAL_ALIASES = {
-            'option': 'alt',
-            'command': 'meta',
-            'return': 'enter',
-            'escape': 'esc',
-            'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
-        },
-
-        /**
-         * variable to store the flipped version of _MAP from above
-         * needed to check if we should use keypress or not when no action
-         * is specified
-         *
-         * @type {Object|undefined}
-         */
-        _REVERSE_MAP,
-
-        /**
-         * a list of all the callbacks setup via Mousetrap.bind()
-         *
-         * @type {Object}
-         */
-        _callbacks = {},
-
-        /**
-         * direct map of string combinations to callbacks used for trigger()
-         *
-         * @type {Object}
-         */
-        _directMap = {},
-
-        /**
-         * keeps track of what level each sequence is at since multiple
-         * sequences can start out with the same sequence
-         *
-         * @type {Object}
-         */
-        _sequenceLevels = {},
-
-        /**
-         * variable to store the setTimeout call
-         *
-         * @type {null|number}
-         */
-        _resetTimer,
-
-        /**
-         * temporary state where we will ignore the next keyup
-         *
-         * @type {boolean|string}
-         */
-        _ignoreNextKeyup = false,
-
-        /**
-         * temporary state where we will ignore the next keypress
-         *
-         * @type {boolean}
-         */
-        _ignoreNextKeypress = false,
-
-        /**
-         * are we currently inside of a sequence?
-         * type of action ("keyup" or "keydown" or "keypress") or false
-         *
-         * @type {boolean|string}
-         */
-        _nextExpectedAction = false;
-
-    /**
-     * loop through the f keys, f1 to f19 and add them to the map
-     * programatically
-     */
-    for (var i = 1; i < 20; ++i) {
-        _MAP[111 + i] = 'f' + i;
-    }
-
-    /**
-     * loop through to map numbers on the numeric keypad
-     */
-    for (i = 0; i <= 9; ++i) {
-        _MAP[i + 96] = i;
-    }
-
-    /**
-     * cross browser add event method
-     *
-     * @param {Element|HTMLDocument} object
-     * @param {string} type
-     * @param {Function} callback
-     * @returns void
-     */
-    function _addEvent(object, type, callback) {
-        if (object.addEventListener) {
-            object.addEventListener(type, callback, false);
-            return;
-        }
-
-        object.attachEvent('on' + type, callback);
-    }
-
-    /**
-     * takes the event and returns the key character
-     *
-     * @param {Event} e
-     * @return {string}
-     */
-    function _characterFromEvent(e) {
-
-        // for keypress events we should return the character as is
-        if (e.type == 'keypress') {
-            var character = String.fromCharCode(e.which);
-
-            // if the shift key is not pressed then it is safe to assume
-            // that we want the character to be lowercase.  this means if
-            // you accidentally have caps lock on then your key bindings
-            // will continue to work
-            //
-            // the only side effect that might not be desired is if you
-            // bind something like 'A' cause you want to trigger an
-            // event when capital A is pressed caps lock will no longer
-            // trigger the event.  shift+a will though.
-            if (!e.shiftKey) {
-                character = character.toLowerCase();
-            }
-
-            return character;
-        }
-
-        // for non keypress events the special maps are needed
-        if (_MAP[e.which]) {
-            return _MAP[e.which];
-        }
-
-        if (_KEYCODE_MAP[e.which]) {
-            return _KEYCODE_MAP[e.which];
-        }
-
-        // if it is not in the special map
-
-        // with keydown and keyup events the character seems to always
-        // come in as an uppercase character whether you are pressing shift
-        // or not.  we should make sure it is always lowercase for comparisons
-        return String.fromCharCode(e.which).toLowerCase();
-    }
-
-    /**
-     * checks if two arrays are equal
-     *
-     * @param {Array} modifiers1
-     * @param {Array} modifiers2
-     * @returns {boolean}
-     */
-    function _modifiersMatch(modifiers1, modifiers2) {
-        return modifiers1.sort().join(',') === modifiers2.sort().join(',');
-    }
-
-    /**
-     * resets all sequence counters except for the ones passed in
-     *
-     * @param {Object} doNotReset
-     * @returns void
-     */
-    function _resetSequences(doNotReset) {
-        doNotReset = doNotReset || {};
-
-        var activeSequences = false,
-            key;
-
-        for (key in _sequenceLevels) {
-            if (doNotReset[key]) {
-                activeSequences = true;
-                continue;
-            }
-            _sequenceLevels[key] = 0;
-        }
-
-        if (!activeSequences) {
-            _nextExpectedAction = false;
-        }
-    }
-
-    /**
-     * finds all callbacks that match based on the keycode, modifiers,
-     * and action
-     *
-     * @param {string} character
-     * @param {Array} modifiers
-     * @param {Event|Object} e
-     * @param {string=} sequenceName - name of the sequence we are looking for
-     * @param {string=} combination
-     * @param {number=} level
-     * @returns {Array}
-     */
-    function _getMatches(character, modifiers, e, sequenceName, combination, level) {
-        var i,
-            callback,
-            matches = [],
-            action = e.type;
-
-        // if there are no events related to this keycode
-        if (!_callbacks[character]) {
-            return [];
-        }
-
-        // if a modifier key is coming up on its own we should allow it
-        if (action == 'keyup' && _isModifier(character)) {
-            modifiers = [character];
-        }
-
-        // loop through all callbacks for the key that was pressed
-        // and see if any of them match
-        for (i = 0; i < _callbacks[character].length; ++i) {
-            callback = _callbacks[character][i];
-
-            // if a sequence name is not specified, but this is a sequence at
-            // the wrong level then move onto the next match
-            if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
-                continue;
-            }
-
-            // if the action we are looking for doesn't match the action we got
-            // then we should keep going
-            if (action != callback.action) {
-                continue;
-            }
-
-            // if this is a keypress event and the meta key and control key
-            // are not pressed that means that we need to only look at the
-            // character, otherwise check the modifiers as well
-            //
-            // chrome will not fire a keypress if meta or control is down
-            // safari will fire a keypress if meta or meta+shift is down
-            // firefox will fire a keypress if meta or control is down
-            if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
-
-                // when you bind a combination or sequence a second time it
-                // should overwrite the first one.  if a sequenceName or
-                // combination is specified in this call it does just that
-                //
-                // @todo make deleting its own method?
-                var deleteCombo = !sequenceName && callback.combo == combination;
-                var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
-                if (deleteCombo || deleteSequence) {
-                    _callbacks[character].splice(i, 1);
-                }
-
-                matches.push(callback);
-            }
-        }
-
-        return matches;
-    }
-
-    /**
-     * takes a key event and figures out what the modifiers are
-     *
-     * @param {Event} e
-     * @returns {Array}
-     */
-    function _eventModifiers(e) {
-        var modifiers = [];
-
-        if (e.shiftKey) {
-            modifiers.push('shift');
-        }
-
-        if (e.altKey) {
-            modifiers.push('alt');
-        }
-
-        if (e.ctrlKey) {
-            modifiers.push('ctrl');
-        }
-
-        if (e.metaKey) {
-            modifiers.push('meta');
-        }
-
-        return modifiers;
-    }
-
-    /**
-     * actually calls the callback function
-     *
-     * if your callback function returns false this will use the jquery
-     * convention - prevent default and stop propogation on the event
-     *
-     * @param {Function} callback
-     * @param {Event} e
-     * @returns void
-     */
-    function _fireCallback(callback, e, combo) {
-
-        // if this event should not happen stop here
-        if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo)) {
-            return;
-        }
-
-        if (callback(e, combo) === false) {
-            if (e.preventDefault) {
-                e.preventDefault();
-            }
-
-            if (e.stopPropagation) {
-                e.stopPropagation();
-            }
-
-            e.returnValue = false;
-            e.cancelBubble = true;
-        }
-    }
-
-    /**
-     * handles a character key event
-     *
-     * @param {string} character
-     * @param {Array} modifiers
-     * @param {Event} e
-     * @returns void
-     */
-    function _handleKey(character, modifiers, e) {
-        var callbacks = _getMatches(character, modifiers, e),
-            i,
-            doNotReset = {},
-            maxLevel = 0,
-            processedSequenceCallback = false;
-
-        // Calculate the maxLevel for sequences so we can only execute the longest callback sequence
-        for (i = 0; i < callbacks.length; ++i) {
-            if (callbacks[i].seq) {
-                maxLevel = Math.max(maxLevel, callbacks[i].level);
-            }
-        }
-
-        // loop through matching callbacks for this key event
-        for (i = 0; i < callbacks.length; ++i) {
-
-            // fire for all sequence callbacks
-            // this is because if for example you have multiple sequences
-            // bound such as "g i" and "g t" they both need to fire the
-            // callback for matching g cause otherwise you can only ever
-            // match the first one
-            if (callbacks[i].seq) {
-
-                // only fire callbacks for the maxLevel to prevent
-                // subsequences from also firing
-                //
-                // for example 'a option b' should not cause 'option b' to fire
-                // even though 'option b' is part of the other sequence
-                //
-                // any sequences that do not match here will be discarded
-                // below by the _resetSequences call
-                if (callbacks[i].level != maxLevel) {
-                    continue;
-                }
-
-                processedSequenceCallback = true;
-
-                // keep a list of which sequences were matches for later
-                doNotReset[callbacks[i].seq] = 1;
-                _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
-                continue;
-            }
-
-            // if there were no sequence matches but we are still here
-            // that means this is a regular match so we should fire that
-            if (!processedSequenceCallback) {
-                _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
-            }
-        }
-
-        // if the key you pressed matches the type of sequence without
-        // being a modifier (ie "keyup" or "keypress") then we should
-        // reset all sequences that were not matched by this event
-        //
-        // this is so, for example, if you have the sequence "h a t" and you
-        // type "h e a r t" it does not match.  in this case the "e" will
-        // cause the sequence to reset
-        //
-        // modifier keys are ignored because you can have a sequence
-        // that contains modifiers such as "enter ctrl+space" and in most
-        // cases the modifier key will be pressed before the next key
-        //
-        // also if you have a sequence such as "ctrl+b a" then pressing the
-        // "b" key will trigger a "keypress" and a "keydown"
-        //
-        // the "keydown" is expected when there is a modifier, but the
-        // "keypress" ends up matching the _nextExpectedAction since it occurs
-        // after and that causes the sequence to reset
-        //
-        // we ignore keypresses in a sequence that directly follow a keydown
-        // for the same character
-        var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
-        if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
-            _resetSequences(doNotReset);
-        }
-
-        _ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
-    }
-
-    /**
-     * handles a keydown event
-     *
-     * @param {Event} e
-     * @returns void
-     */
-    function _handleKeyEvent(e) {
-
-        // normalize e.which for key events
-        // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
-        if (typeof e.which !== 'number') {
-            e.which = e.keyCode;
-        }
-
-        var character = _characterFromEvent(e);
-
-        // no character found then stop
-        if (!character) {
-            return;
-        }
-
-        // need to use === for the character check because the character can be 0
-        if (e.type == 'keyup' && _ignoreNextKeyup === character) {
-            _ignoreNextKeyup = false;
-            return;
-        }
-
-        Mousetrap.handleKey(character, _eventModifiers(e), e);
-    }
-
-    /**
-     * determines if the keycode specified is a modifier key or not
-     *
-     * @param {string} key
-     * @returns {boolean}
-     */
-    function _isModifier(key) {
-        return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
-    }
-
-    /**
-     * called to set a 1 second timeout on the specified sequence
-     *
-     * this is so after each key press in the sequence you have 1 second
-     * to press the next key before you have to start over
-     *
-     * @returns void
-     */
-    function _resetSequenceTimer() {
-        clearTimeout(_resetTimer);
-        _resetTimer = setTimeout(_resetSequences, 1000);
-    }
-
-    /**
-     * reverses the map lookup so that we can look for specific keys
-     * to see what can and can't use keypress
-     *
-     * @return {Object}
-     */
-    function _getReverseMap() {
-        if (!_REVERSE_MAP) {
-            _REVERSE_MAP = {};
-            for (var key in _MAP) {
-
-                // pull out the numeric keypad from here cause keypress should
-                // be able to detect the keys from the character
-                if (key > 95 && key < 112) {
-                    continue;
-                }
-
-                if (_MAP.hasOwnProperty(key)) {
-                    _REVERSE_MAP[_MAP[key]] = key;
-                }
-            }
-        }
-        return _REVERSE_MAP;
-    }
-
-    /**
-     * picks the best action based on the key combination
-     *
-     * @param {string} key - character for key
-     * @param {Array} modifiers
-     * @param {string=} action passed in
-     */
-    function _pickBestAction(key, modifiers, action) {
-
-        // if no action was picked in we should try to pick the one
-        // that we think would work best for this key
-        if (!action) {
-            action = _getReverseMap()[key] ? 'keydown' : 'keypress';
-        }
-
-        // modifier keys don't work as expected with keypress,
-        // switch to keydown
-        if (action == 'keypress' && modifiers.length) {
-            action = 'keydown';
-        }
-
-        return action;
-    }
-
-    /**
-     * binds a key sequence to an event
-     *
-     * @param {string} combo - combo specified in bind call
-     * @param {Array} keys
-     * @param {Function} callback
-     * @param {string=} action
-     * @returns void
-     */
-    function _bindSequence(combo, keys, callback, action) {
-
-        // start off by adding a sequence level record for this combination
-        // and setting the level to 0
-        _sequenceLevels[combo] = 0;
-
-        /**
-         * callback to increase the sequence level for this sequence and reset
-         * all other sequences that were active
-         *
-         * @param {string} nextAction
-         * @returns {Function}
-         */
-        function _increaseSequence(nextAction) {
-            return function() {
-                _nextExpectedAction = nextAction;
-                ++_sequenceLevels[combo];
-                _resetSequenceTimer();
-            };
-        }
-
-        /**
-         * wraps the specified callback inside of another function in order
-         * to reset all sequence counters as soon as this sequence is done
-         *
-         * @param {Event} e
-         * @returns void
-         */
-        function _callbackAndReset(e) {
-            _fireCallback(callback, e, combo);
-
-            // we should ignore the next key up if the action is key down
-            // or keypress.  this is so if you finish a sequence and
-            // release the key the final key will not trigger a keyup
-            if (action !== 'keyup') {
-                _ignoreNextKeyup = _characterFromEvent(e);
-            }
-
-            // weird race condition if a sequence ends with the key
-            // another sequence begins with
-            setTimeout(_resetSequences, 10);
-        }
-
-        // loop through keys one at a time and bind the appropriate callback
-        // function.  for any key leading up to the final one it should
-        // increase the sequence. after the final, it should reset all sequences
-        //
-        // if an action is specified in the original bind call then that will
-        // be used throughout.  otherwise we will pass the action that the
-        // next key in the sequence should match.  this allows a sequence
-        // to mix and match keypress and keydown events depending on which
-        // ones are better suited to the key provided
-        for (var i = 0; i < keys.length; ++i) {
-            var isFinal = i + 1 === keys.length;
-            var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
-            _bindSingle(keys[i], wrappedCallback, action, combo, i);
-        }
-    }
-
-    /**
-     * Converts from a string key combination to an array
-     *
-     * @param  {string} combination like "command+shift+l"
-     * @return {Array}
-     */
-    function _keysFromString(combination) {
-        if (combination === '+') {
-            return ['+'];
-        }
-
-        return combination.split('+');
-    }
-
-    /**
-     * Gets info for a specific key combination
-     *
-     * @param  {string} combination key combination ("command+s" or "a" or "*")
-     * @param  {string=} action
-     * @returns {Object}
-     */
-    function _getKeyInfo(combination, action) {
-        var keys,
-            key,
-            i,
-            modifiers = [];
-
-        // take the keys from this pattern and figure out what the actual
-        // pattern is all about
-        keys = _keysFromString(combination);
-
-        for (i = 0; i < keys.length; ++i) {
-            key = keys[i];
-
-            // normalize key names
-            if (_SPECIAL_ALIASES[key]) {
-                key = _SPECIAL_ALIASES[key];
-            }
-
-            // if this is not a keypress event then we should
-            // be smart about using shift keys
-            // this will only work for US keyboards however
-            if (action && action != 'keypress' && _SHIFT_MAP[key]) {
-                key = _SHIFT_MAP[key];
-                modifiers.push('shift');
-            }
-
-            // if this key is a modifier then add it to the list of modifiers
-            if (_isModifier(key)) {
-                modifiers.push(key);
-            }
-        }
-
-        // depending on what the key combination is
-        // we will try to pick the best event for it
-        action = _pickBestAction(key, modifiers, action);
-
-        return {
-            key: key,
-            modifiers: modifiers,
-            action: action
-        };
-    }
-
-    /**
-     * binds a single keyboard combination
-     *
-     * @param {string} combination
-     * @param {Function} callback
-     * @param {string=} action
-     * @param {string=} sequenceName - name of sequence if part of sequence
-     * @param {number=} level - what part of the sequence the command is
-     * @returns void
-     */
-    function _bindSingle(combination, callback, action, sequenceName, level) {
-
-        // store a direct mapped reference for use with Mousetrap.trigger
-        _directMap[combination + ':' + action] = callback;
-
-        // make sure multiple spaces in a row become a single space
-        combination = combination.replace(/\s+/g, ' ');
-
-        var sequence = combination.split(' '),
-            info;
-
-        // if this pattern is a sequence of keys then run through this method
-        // to reprocess each pattern one key at a time
-        if (sequence.length > 1) {
-            _bindSequence(combination, sequence, callback, action);
-            return;
-        }
-
-        info = _getKeyInfo(combination, action);
-
-        // make sure to initialize array if this is the first time
-        // a callback is added for this key
-        _callbacks[info.key] = _callbacks[info.key] || [];
-
-        // remove an existing match if there is one
-        _getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
-
-        // add this call back to the array
-        // if it is a sequence put it at the beginning
-        // if not put it at the end
-        //
-        // this is important because the way these are processed expects
-        // the sequence ones to come first
-        _callbacks[info.key][sequenceName ? 'unshift' : 'push']({
-            callback: callback,
-            modifiers: info.modifiers,
-            action: info.action,
-            seq: sequenceName,
-            level: level,
-            combo: combination
-        });
-    }
-
-    /**
-     * binds multiple combinations to the same callback
-     *
-     * @param {Array} combinations
-     * @param {Function} callback
-     * @param {string|undefined} action
-     * @returns void
-     */
-    function _bindMultiple(combinations, callback, action) {
-        for (var i = 0; i < combinations.length; ++i) {
-            _bindSingle(combinations[i], callback, action);
-        }
-    }
-
-    // start!
-    _addEvent(document, 'keypress', _handleKeyEvent);
-    _addEvent(document, 'keydown', _handleKeyEvent);
-    _addEvent(document, 'keyup', _handleKeyEvent);
-
-    var Mousetrap = {
-
-        /**
-         * binds an event to mousetrap
-         *
-         * can be a single key, a combination of keys separated with +,
-         * an array of keys, or a sequence of keys separated by spaces
-         *
-         * be sure to list the modifier keys first to make sure that the
-         * correct key ends up getting bound (the last key in the pattern)
-         *
-         * @param {string|Array} keys
-         * @param {Function} callback
-         * @param {string=} action - 'keypress', 'keydown', or 'keyup'
-         * @returns void
-         */
-        bind: function(keys, callback, action) {
-            keys = keys instanceof Array ? keys : [keys];
-            _bindMultiple(keys, callback, action);
-            return this;
-        },
-
-        /**
-         * unbinds an event to mousetrap
-         *
-         * the unbinding sets the callback function of the specified key combo
-         * to an empty function and deletes the corresponding key in the
-         * _directMap dict.
-         *
-         * TODO: actually remove this from the _callbacks dictionary instead
-         * of binding an empty function
-         *
-         * the keycombo+action has to be exactly the same as
-         * it was defined in the bind method
-         *
-         * @param {string|Array} keys
-         * @param {string} action
-         * @returns void
-         */
-        unbind: function(keys, action) {
-            return Mousetrap.bind(keys, function() {}, action);
-        },
-
-        /**
-         * triggers an event that has already been bound
-         *
-         * @param {string} keys
-         * @param {string=} action
-         * @returns void
-         */
-        trigger: function(keys, action) {
-            if (_directMap[keys + ':' + action]) {
-                _directMap[keys + ':' + action]({}, keys);
-            }
-            return this;
-        },
-
-        /**
-         * resets the library back to its initial state.  this is useful
-         * if you want to clear out the current keyboard shortcuts and bind
-         * new ones - for example if you switch to another page
-         *
-         * @returns void
-         */
-        reset: function() {
-            _callbacks = {};
-            _directMap = {};
-            return this;
-        },
-
-       /**
-        * should we stop this event before firing off callbacks
-        *
-        * @param {Event} e
-        * @param {Element} element
-        * @return {boolean}
-        */
-        stopCallback: function(e, element) {
-
-            // if the element has the class "mousetrap" then no need to stop
-            if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
-                return false;
-            }
-
-            // stop for input, select, and textarea
-            return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
-        },
-
-        /**
-         * exposes _handleKey publicly so it can be overwritten by extensions
-         */
-        handleKey: _handleKey
-    };
-
-    // expose mousetrap to the global object
-    window.Mousetrap = Mousetrap;
-
-    // expose mousetrap as an AMD module
-    if (typeof define === 'function' && define.amd) {
-        define(Mousetrap);
-    }
-}) (window, document);
--- a/kallithea/public/js/native.history.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1974 +0,0 @@
-/**
- * History.js Native Adapter
- * @author Benjamin Arthur Lupton <contact@balupton.com>
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
- */
-
-// Closure
-(function(window,undefined){
-	"use strict";
-
-	// Localise Globals
-	var History = window.History = window.History||{};
-
-	// Check Existence
-	if ( typeof History.Adapter !== 'undefined' ) {
-		throw new Error('History.js Adapter has already been loaded...');
-	}
-
-	// Add the Adapter
-	History.Adapter = {
-		/**
-		 * History.Adapter.handlers[uid][eventName] = Array
-		 */
-		handlers: {},
-
-		/**
-		 * History.Adapter._uid
-		 * The current element unique identifier
-		 */
-		_uid: 1,
-
-		/**
-		 * History.Adapter.uid(element)
-		 * @param {Element} element
-		 * @return {String} uid
-		 */
-		 uid: function(element){
-			return element._uid || (element._uid = History.Adapter._uid++);
-		 },
-
-		/**
-		 * History.Adapter.bind(el,event,callback)
-		 * @param {Element} element
-		 * @param {String} eventName - custom and standard events
-		 * @param {Function} callback
-		 * @return
-		 */
-		bind: function(element,eventName,callback){
-			// Prepare
-			var uid = History.Adapter.uid(element);
-
-			// Apply Listener
-			History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {};
-			History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || [];
-			History.Adapter.handlers[uid][eventName].push(callback);
-
-			// Bind Global Listener
-			element['on'+eventName] = (function(element,eventName){
-				return function(event){
-					History.Adapter.trigger(element,eventName,event);
-				};
-			})(element,eventName);
-		},
-
-		/**
-		 * History.Adapter.trigger(el,event)
-		 * @param {Element} element
-		 * @param {String} eventName - custom and standard events
-		 * @param {Object} event - a object of event data
-		 * @return
-		 */
-		trigger: function(element,eventName,event){
-			// Prepare
-			event = event || {};
-			var uid = History.Adapter.uid(element),
-				i,n;
-
-			// Apply Listener
-			History.Adapter.handlers[uid] = History.Adapter.handlers[uid] || {};
-			History.Adapter.handlers[uid][eventName] = History.Adapter.handlers[uid][eventName] || [];
-
-			// Fire Listeners
-			for ( i=0,n=History.Adapter.handlers[uid][eventName].length; i<n; ++i ) {
-				History.Adapter.handlers[uid][eventName][i].apply(this,[event]);
-			}
-		},
-
-		/**
-		 * History.Adapter.extractEventData(key,event,extra)
-		 * @param {String} key - key for the event data to extract
-		 * @param {String} event - custom and standard events
-		 * @return {mixed}
-		 */
-		extractEventData: function(key,event){
-			var result = (event && event[key]) || undefined;
-			return result;
-		},
-
-		/**
-		 * History.Adapter.onDomLoad(callback)
-		 * @param {Function} callback
-		 * @return
-		 */
-		onDomLoad: function(callback) {
-			var timeout = window.setTimeout(function(){
-				callback();
-			},2000);
-			window.onload = function(){
-				clearTimeout(timeout);
-				callback();
-			};
-		}
-	};
-
-	// Try and Initialise History
-	if ( typeof History.init !== 'undefined' ) {
-		History.init();
-	}
-
-})(window);
-/**
- * History.js Core
- * @author Benjamin Arthur Lupton <contact@balupton.com>
- * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
- * @license New BSD License <http://creativecommons.org/licenses/BSD/>
- */
-
-(function(window,undefined){
-	"use strict";
-
-	// --------------------------------------------------------------------------
-	// Initialise
-
-	// Localise Globals
-	var
-		console = window.console||undefined, // Prevent a JSLint complain
-		document = window.document, // Make sure we are using the correct document
-		navigator = window.navigator, // Make sure we are using the correct navigator
-		amplify = window.amplify||false, // Amplify.js
-		setTimeout = window.setTimeout,
-		clearTimeout = window.clearTimeout,
-		setInterval = window.setInterval,
-		clearInterval = window.clearInterval,
-		JSON = window.JSON,
-		History = window.History = window.History||{}, // Public History Object
-		history = window.history; // Old History Object
-
-	// MooTools Compatibility
-	JSON.stringify = JSON.stringify||JSON.encode;
-	JSON.parse = JSON.parse||JSON.decode;
-
-	// Check Existence
-	if ( typeof History.init !== 'undefined' ) {
-		throw new Error('History.js Core has already been loaded...');
-	}
-
-	// Initialise History
-	History.init = function(){
-		// Check Load Status of Adapter
-		if ( typeof History.Adapter === 'undefined' ) {
-			return false;
-		}
-
-		// Check Load Status of Core
-		if ( typeof History.initCore !== 'undefined' ) {
-			History.initCore();
-		}
-
-		// Check Load Status of HTML4 Support
-		if ( typeof History.initHtml4 !== 'undefined' ) {
-			History.initHtml4();
-		}
-
-		// Return true
-		return true;
-	};
-
-	// --------------------------------------------------------------------------
-	// Initialise Core
-
-	// Initialise Core
-	History.initCore = function(){
-		// Initialise
-		if ( typeof History.initCore.initialized !== 'undefined' ) {
-			// Already Loaded
-			return false;
-		}
-		else {
-			History.initCore.initialized = true;
-		}
-
-		// ----------------------------------------------------------------------
-		// Options
-
-		/**
-		 * History.options
-		 * Configurable options
-		 */
-		History.options = History.options||{};
-
-		/**
-		 * History.options.hashChangeInterval
-		 * How long should the interval be before hashchange checks
-		 */
-		History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
-
-		/**
-		 * History.options.safariPollInterval
-		 * How long should the interval be before safari poll checks
-		 */
-		History.options.safariPollInterval = History.options.safariPollInterval || 500;
-
-		/**
-		 * History.options.doubleCheckInterval
-		 * How long should the interval be before we perform a double check
-		 */
-		History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
-
-		/**
-		 * History.options.storeInterval
-		 * How long should we wait between store calls
-		 */
-		History.options.storeInterval = History.options.storeInterval || 1000;
-
-		/**
-		 * History.options.busyDelay
-		 * How long should we wait between busy events
-		 */
-		History.options.busyDelay = History.options.busyDelay || 250;
-
-		/**
-		 * History.options.debug
-		 * If true will enable debug messages to be logged
-		 */
-		History.options.debug = History.options.debug || false;
-
-		/**
-		 * History.options.initialTitle
-		 * What is the title of the initial state
-		 */
-		History.options.initialTitle = History.options.initialTitle || document.title;
-
-
-		// ----------------------------------------------------------------------
-		// Interval record
-
-		/**
-		 * History.intervalList
-		 * List of intervals set, to be cleared when document is unloaded.
-		 */
-		History.intervalList = [];
-
-		/**
-		 * History.clearAllIntervals
-		 * Clears all setInterval instances.
-		 */
-		History.clearAllIntervals = function(){
-			var i, il = History.intervalList;
-			if (typeof il !== "undefined" && il !== null) {
-				for (i = 0; i < il.length; i++) {
-					clearInterval(il[i]);
-				}
-				History.intervalList = null;
-			}
-		};
-		History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
-		History.Adapter.bind(window,"unload",History.clearAllIntervals);
-
-
-		// ----------------------------------------------------------------------
-		// Debug
-
-		/**
-		 * History.debug(message,...)
-		 * Logs the passed arguments if debug enabled
-		 */
-		History.debug = function(){
-			if ( (History.options.debug||false) ) {
-				History.log.apply(History,arguments);
-			}
-		};
-
-		/**
-		 * History.log(message,...)
-		 * Logs the passed arguments
-		 */
-		History.log = function(){
-			// Prepare
-			var
-				consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
-				textarea = document.getElementById('log'),
-				message,
-				i,n
-				;
-
-			// Write to Console
-			if ( consoleExists ) {
-				var args = Array.prototype.slice.call(arguments);
-				message = args.shift();
-				if ( typeof console.debug !== 'undefined' ) {
-					console.debug.apply(console,[message,args]);
-				}
-				else {
-					console.log.apply(console,[message,args]);
-				}
-			}
-			else {
-				message = ("\n"+arguments[0]+"\n");
-			}
-
-			// Write to log
-			for ( i=1,n=arguments.length; i<n; ++i ) {
-				var arg = arguments[i];
-				if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
-					try {
-						arg = JSON.stringify(arg);
-					}
-					catch ( Exception ) {
-						// Recursive Object
-					}
-				}
-				message += "\n"+arg+"\n";
-			}
-
-			// Textarea
-			if ( textarea ) {
-				textarea.value += message+"\n-----\n";
-				textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
-			}
-			// No Textarea, No Console
-			else if ( !consoleExists ) {
-				alert(message);
-			}
-
-			// Return true
-			return true;
-		};
-
-		// ----------------------------------------------------------------------
-		// Emulated Status
-
-		/**
-		 * History.getInternetExplorerMajorVersion()
-		 * Gets the major version of Internet Explorer
-		 * @return {integer}
-		 * @license Public Domain
-		 * @author Benjamin Arthur Lupton <contact@balupton.com>
-		 * @author James Padolsey <https://gist.github.com/527683>
-		 */
-		History.getInternetExplorerMajorVersion = function(){
-			var result = History.getInternetExplorerMajorVersion.cached =
-					(typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
-				?	History.getInternetExplorerMajorVersion.cached
-				:	(function(){
-						var v = 3,
-								div = document.createElement('div'),
-								all = div.getElementsByTagName('i');
-						while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
-						return (v > 4) ? v : false;
-					})()
-				;
-			return result;
-		};
-
-		/**
-		 * History.isInternetExplorer()
-		 * Are we using Internet Explorer?
-		 * @return {boolean}
-		 * @license Public Domain
-		 * @author Benjamin Arthur Lupton <contact@balupton.com>
-		 */
-		History.isInternetExplorer = function(){
-			var result =
-				History.isInternetExplorer.cached =
-				(typeof History.isInternetExplorer.cached !== 'undefined')
-					?	History.isInternetExplorer.cached
-					:	Boolean(History.getInternetExplorerMajorVersion())
-				;
-			return result;
-		};
-
-		/**
-		 * History.emulated
-		 * Which features require emulating?
-		 */
-		History.emulated = {
-			pushState: !Boolean(
-				window.history && window.history.pushState && window.history.replaceState
-				&& !(
-					(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
-					|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
-				)
-			),
-			hashChange: Boolean(
-				!(('onhashchange' in window) || ('onhashchange' in document))
-				||
-				(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
-			)
-		};
-
-		/**
-		 * History.enabled
-		 * Is History enabled?
-		 */
-		History.enabled = !History.emulated.pushState;
-
-		/**
-		 * History.bugs
-		 * Which bugs are present
-		 */
-		History.bugs = {
-			/**
-			 * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
-			 * https://bugs.webkit.org/show_bug.cgi?id=56249
-			 */
-			setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
-
-			/**
-			 * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
-			 * https://bugs.webkit.org/show_bug.cgi?id=42940
-			 */
-			safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
-
-			/**
-			 * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
-			 */
-			ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
-
-			/**
-			 * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
-			 */
-			hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
-		};
-
-		/**
-		 * History.isEmptyObject(obj)
-		 * Checks to see if the Object is Empty
-		 * @param {Object} obj
-		 * @return {boolean}
-		 */
-		History.isEmptyObject = function(obj) {
-			for ( var name in obj ) {
-				return false;
-			}
-			return true;
-		};
-
-		/**
-		 * History.cloneObject(obj)
-		 * Clones a object
-		 * @param {Object} obj
-		 * @return {Object}
-		 */
-		History.cloneObject = function(obj) {
-			var hash,newObj;
-			if ( obj ) {
-				hash = JSON.stringify(obj);
-				newObj = JSON.parse(hash);
-			}
-			else {
-				newObj = {};
-			}
-			return newObj;
-		};
-
-		// ----------------------------------------------------------------------
-		// URL Helpers
-
-		/**
-		 * History.getRootUrl()
-		 * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
-		 * @return {String} rootUrl
-		 */
-		History.getRootUrl = function(){
-			// Create
-			var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
-			if ( document.location.port||false ) {
-				rootUrl += ':'+document.location.port;
-			}
-			rootUrl += '/';
-
-			// Return
-			return rootUrl;
-		};
-
-		/**
-		 * History.getBaseHref()
-		 * Fetches the `href` attribute of the `<base href="...">` element if it exists
-		 * @return {String} baseHref
-		 */
-		History.getBaseHref = function(){
-			// Create
-			var
-				baseElements = document.getElementsByTagName('base'),
-				baseElement = null,
-				baseHref = '';
-
-			// Test for Base Element
-			if ( baseElements.length === 1 ) {
-				// Prepare for Base Element
-				baseElement = baseElements[0];
-				baseHref = baseElement.href.replace(/[^\/]+$/,'');
-			}
-
-			// Adjust trailing slash
-			baseHref = baseHref.replace(/\/+$/,'');
-			if ( baseHref ) baseHref += '/';
-
-			// Return
-			return baseHref;
-		};
-
-		/**
-		 * History.getBaseUrl()
-		 * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
-		 * @return {String} baseUrl
-		 */
-		History.getBaseUrl = function(){
-			// Create
-			var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
-
-			// Return
-			return baseUrl;
-		};
-
-		/**
-		 * History.getPageUrl()
-		 * Fetches the URL of the current page
-		 * @return {String} pageUrl
-		 */
-		History.getPageUrl = function(){
-			// Fetch
-			var
-				State = History.getState(false,false),
-				stateUrl = (State||{}).url||document.URL||document.location.href;
-
-			// Create
-			var pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
-				return (/\./).test(part) ? part : part+'/';
-			});
-
-			// Return
-			return pageUrl;
-		};
-
-		/**
-		 * History.getBasePageUrl()
-		 * Fetches the Url of the directory of the current page
-		 * @return {String} basePageUrl
-		 */
-		History.getBasePageUrl = function(){
-			// Create
-			var basePageUrl = (document.URL||document.location.href).replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
-				return (/[^\/]$/).test(part) ? '' : part;
-			}).replace(/\/+$/,'')+'/';
-
-			// Return
-			return basePageUrl;
-		};
-
-		/**
-		 * History.getFullUrl(url)
-		 * Ensures that we have an absolute URL and not a relative URL
-		 * @param {string} url
-		 * @param {Boolean} allowBaseHref
-		 * @return {string} fullUrl
-		 */
-		History.getFullUrl = function(url,allowBaseHref){
-			// Prepare
-			var fullUrl = url, firstChar = url.substring(0,1);
-			allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
-
-			// Check
-			if ( /[a-z]+\:\/\//.test(url) ) {
-				// Full URL
-			}
-			else if ( firstChar === '/' ) {
-				// Root URL
-				fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
-			}
-			else if ( firstChar === '#' ) {
-				// Anchor URL
-				fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
-			}
-			else if ( firstChar === '?' ) {
-				// Query URL
-				fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
-			}
-			else {
-				// Relative URL
-				if ( allowBaseHref ) {
-					fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
-				} else {
-					fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
-				}
-				// We have an if condition above as we do not want hashes
-				// which are relative to the baseHref in our URLs
-				// as if the baseHref changes, then all our bookmarks
-				// would now point to different locations
-				// whereas the basePageUrl will always stay the same
-			}
-
-			// Return
-			return fullUrl.replace(/\#$/,'');
-		};
-
-		/**
-		 * History.getShortUrl(url)
-		 * Ensures that we have a relative URL and not a absolute URL
-		 * @param {string} url
-		 * @return {string} url
-		 */
-		History.getShortUrl = function(url){
-			// Prepare
-			var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
-
-			// Trim baseUrl
-			if ( History.emulated.pushState ) {
-				// We are in a if statement as when pushState is not emulated
-				// The actual url these short urls are relative to can change
-				// So within the same session, we the url may end up somewhere different
-				shortUrl = shortUrl.replace(baseUrl,'');
-			}
-
-			// Trim rootUrl
-			shortUrl = shortUrl.replace(rootUrl,'/');
-
-			// Ensure we can still detect it as a state
-			if ( History.isTraditionalAnchor(shortUrl) ) {
-				shortUrl = './'+shortUrl;
-			}
-
-			// Clean It
-			shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
-
-			// Return
-			return shortUrl;
-		};
-
-		// ----------------------------------------------------------------------
-		// State Storage
-
-		/**
-		 * History.store
-		 * The store for all session specific data
-		 */
-		History.store = amplify ? (amplify.store('History.store')||{}) : {};
-		History.store.idToState = History.store.idToState||{};
-		History.store.urlToId = History.store.urlToId||{};
-		History.store.stateToId = History.store.stateToId||{};
-
-		/**
-		 * History.idToState
-		 * 1-1: State ID to State Object
-		 */
-		History.idToState = History.idToState||{};
-
-		/**
-		 * History.stateToId
-		 * 1-1: State String to State ID
-		 */
-		History.stateToId = History.stateToId||{};
-
-		/**
-		 * History.urlToId
-		 * 1-1: State URL to State ID
-		 */
-		History.urlToId = History.urlToId||{};
-
-		/**
-		 * History.storedStates
-		 * Store the states in an array
-		 */
-		History.storedStates = History.storedStates||[];
-
-		/**
-		 * History.savedStates
-		 * Saved the states in an array
-		 */
-		History.savedStates = History.savedStates||[];
-
-		/**
-		 * History.getState()
-		 * Get an object containing the data, title and url of the current state
-		 * @param {Boolean} friendly
-		 * @param {Boolean} create
-		 * @return {Object} State
-		 */
-		History.getState = function(friendly,create){
-			// Prepare
-			if ( typeof friendly === 'undefined' ) { friendly = true; }
-			if ( typeof create === 'undefined' ) { create = true; }
-
-			// Fetch
-			var State = History.getLastSavedState();
-
-			// Create
-			if ( !State && create ) {
-				State = History.createStateObject();
-			}
-
-			// Adjust
-			if ( friendly ) {
-				State = History.cloneObject(State);
-				State.url = State.cleanUrl||State.url;
-			}
-
-			// Return
-			return State;
-		};
-
-		/**
-		 * History.getIdByState(State)
-		 * Gets a ID for a State
-		 * @param {State} newState
-		 * @return {String} id
-		 */
-		History.getIdByState = function(newState){
-
-			// Fetch ID
-			var id = History.extractId(newState.url);
-			if ( !id ) {
-				// Find ID via State String
-				var str = History.getStateString(newState);
-				if ( typeof History.stateToId[str] !== 'undefined' ) {
-					id = History.stateToId[str];
-				}
-				else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
-					id = History.store.stateToId[str];
-				}
-				else {
-					// Generate a new ID
-					while ( true ) {
-						id = String(Math.floor(Math.random()*1000));
-						if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
-							break;
-						}
-					}
-
-					// Apply the new State to the ID
-					History.stateToId[str] = id;
-					History.idToState[id] = newState;
-				}
-			}
-
-			// Return ID
-			return id;
-		};
-
-		/**
-		 * History.normalizeState(State)
-		 * Expands a State Object
-		 * @param {object} State
-		 * @return {object}
-		 */
-		History.normalizeState = function(oldState){
-			// Prepare
-			if ( !oldState || (typeof oldState !== 'object') ) {
-				oldState = {};
-			}
-
-			// Check
-			if ( typeof oldState.normalized !== 'undefined' ) {
-				return oldState;
-			}
-
-			// Adjust
-			if ( !oldState.data || (typeof oldState.data !== 'object') ) {
-				oldState.data = {};
-			}
-
-			// ----------------------------------------------------------------------
-
-			// Create
-			var newState = {};
-			newState.normalized = true;
-			newState.title = oldState.title||'';
-			newState.url = History.getFullUrl(oldState.url?decodeURIComponent(oldState.url):(document.URL||document.location.href));
-			newState.hash = History.getShortUrl(newState.url);
-			newState.data = History.cloneObject(oldState.data);
-
-			// Fetch ID
-			newState.id = History.getIdByState(newState);
-
-			// ----------------------------------------------------------------------
-
-			// Clean the URL
-			newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
-			newState.url = newState.cleanUrl;
-
-			// Check to see if we have more than just a url
-			var dataNotEmpty = !History.isEmptyObject(newState.data);
-
-			// Apply
-			if ( newState.title || dataNotEmpty ) {
-				// Add ID to Hash
-				newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
-				if ( !/\?/.test(newState.hash) ) {
-					newState.hash += '?';
-				}
-				newState.hash += '&_suid='+newState.id;
-			}
-
-			// Create the Hashed URL
-			newState.hashedUrl = History.getFullUrl(newState.hash);
-
-			// ----------------------------------------------------------------------
-
-			// Update the URL if we have a duplicate
-			if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
-				newState.url = newState.hashedUrl;
-			}
-
-			// ----------------------------------------------------------------------
-
-			// Return
-			return newState;
-		};
-
-		/**
-		 * History.createStateObject(data,title,url)
-		 * Creates a object based on the data, title and url state params
-		 * @param {object} data
-		 * @param {string} title
-		 * @param {string} url
-		 * @return {object}
-		 */
-		History.createStateObject = function(data,title,url){
-			// Hashify
-			var State = {
-				'data': data,
-				'title': title,
-				'url': encodeURIComponent(url||"")
-			};
-
-			// Expand the State
-			State = History.normalizeState(State);
-
-			// Return object
-			return State;
-		};
-
-		/**
-		 * History.getStateById(id)
-		 * Get a state by it's UID
-		 * @param {String} id
-		 */
-		History.getStateById = function(id){
-			// Prepare
-			id = String(id);
-
-			// Retrieve
-			var State = History.idToState[id] || History.store.idToState[id] || undefined;
-
-			// Return State
-			return State;
-		};
-
-		/**
-		 * Get a State's String
-		 * @param {State} passedState
-		 */
-		History.getStateString = function(passedState){
-			// Prepare
-			var State = History.normalizeState(passedState);
-
-			// Clean
-			var cleanedState = {
-				data: State.data,
-				title: passedState.title,
-				url: passedState.url
-			};
-
-			// Fetch
-			var str = JSON.stringify(cleanedState);
-
-			// Return
-			return str;
-		};
-
-		/**
-		 * Get a State's ID
-		 * @param {State} passedState
-		 * @return {String} id
-		 */
-		History.getStateId = function(passedState){
-			// Prepare
-			var State = History.normalizeState(passedState);
-
-			// Fetch
-			var id = State.id;
-
-			// Return
-			return id;
-		};
-
-		/**
-		 * History.getHashByState(State)
-		 * Creates a Hash for the State Object
-		 * @param {State} passedState
-		 * @return {String} hash
-		 */
-		History.getHashByState = function(passedState){
-			// Prepare
-			var hash, State = History.normalizeState(passedState);
-
-			// Fetch
-			hash = State.hash;
-
-			// Return
-			return hash;
-		};
-
-		/**
-		 * History.extractId(url_or_hash)
-		 * Get a State ID by it's URL or Hash
-		 * @param {string} url_or_hash
-		 * @return {string} id
-		 */
-		History.extractId = function ( url_or_hash ) {
-			// Prepare
-			var id;
-
-			// Extract
-			var parts,url;
-			parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
-			url = parts ? (parts[1]||url_or_hash) : url_or_hash;
-			id = parts ? String(parts[2]||'') : '';
-
-			// Return
-			return id||false;
-		};
-
-		/**
-		 * History.isTraditionalAnchor
-		 * Checks to see if the url is a traditional anchor or not
-		 * @param {String} url_or_hash
-		 * @return {Boolean}
-		 */
-		History.isTraditionalAnchor = function(url_or_hash){
-			// Check
-			var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
-
-			// Return
-			return isTraditional;
-		};
-
-		/**
-		 * History.extractState
-		 * Get a State by it's URL or Hash
-		 * @param {String} url_or_hash
-		 * @return {State|null}
-		 */
-		History.extractState = function(url_or_hash,create){
-			// Prepare
-			var State = null;
-			create = create||false;
-
-			// Fetch SUID
-			var id = History.extractId(url_or_hash);
-			if ( id ) {
-				State = History.getStateById(id);
-			}
-
-			// Fetch SUID returned no State
-			if ( !State ) {
-				// Fetch URL
-				var url = History.getFullUrl(url_or_hash);
-
-				// Check URL
-				id = History.getIdByUrl(url)||false;
-				if ( id ) {
-					State = History.getStateById(id);
-				}
-
-				// Create State
-				if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
-					State = History.createStateObject(null,null,url);
-				}
-			}
-
-			// Return
-			return State;
-		};
-
-		/**
-		 * History.getIdByUrl()
-		 * Get a State ID by a State URL
-		 */
-		History.getIdByUrl = function(url){
-			// Fetch
-			var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
-
-			// Return
-			return id;
-		};
-
-		/**
-		 * History.getLastSavedState()
-		 * Get an object containing the data, title and url of the current state
-		 * @return {Object} State
-		 */
-		History.getLastSavedState = function(){
-			return History.savedStates[History.savedStates.length-1]||undefined;
-		};
-
-		/**
-		 * History.getLastStoredState()
-		 * Get an object containing the data, title and url of the current state
-		 * @return {Object} State
-		 */
-		History.getLastStoredState = function(){
-			return History.storedStates[History.storedStates.length-1]||undefined;
-		};
-
-		/**
-		 * History.hasUrlDuplicate
-		 * Checks if a Url will have a url conflict
-		 * @param {Object} newState
-		 * @return {Boolean} hasDuplicate
-		 */
-		History.hasUrlDuplicate = function(newState) {
-			// Prepare
-			var hasDuplicate = false;
-
-			// Fetch
-			var oldState = History.extractState(newState.url);
-
-			// Check
-			hasDuplicate = oldState && oldState.id !== newState.id;
-
-			// Return
-			return hasDuplicate;
-		};
-
-		/**
-		 * History.storeState
-		 * Store a State
-		 * @param {Object} newState
-		 * @return {Object} newState
-		 */
-		History.storeState = function(newState){
-			// Store the State
-			History.urlToId[newState.url] = newState.id;
-
-			// Push the State
-			History.storedStates.push(History.cloneObject(newState));
-
-			// Return newState
-			return newState;
-		};
-
-		/**
-		 * History.isLastSavedState(newState)
-		 * Tests to see if the state is the last state
-		 * @param {Object} newState
-		 * @return {boolean} isLast
-		 */
-		History.isLastSavedState = function(newState){
-			// Prepare
-			var isLast = false;
-
-			// Check
-			if ( History.savedStates.length ) {
-				var
-					newId = newState.id,
-					oldState = History.getLastSavedState(),
-					oldId = oldState.id;
-
-				// Check
-				isLast = (newId === oldId);
-			}
-
-			// Return
-			return isLast;
-		};
-
-		/**
-		 * History.saveState
-		 * Push a State
-		 * @param {Object} newState
-		 * @return {boolean} changed
-		 */
-		History.saveState = function(newState){
-			// Check Hash
-			if ( History.isLastSavedState(newState) ) {
-				return false;
-			}
-
-			// Push the State
-			History.savedStates.push(History.cloneObject(newState));
-
-			// Return true
-			return true;
-		};
-
-		/**
-		 * History.getStateByIndex()
-		 * Gets a state by the index
-		 * @param {integer} index
-		 * @return {Object}
-		 */
-		History.getStateByIndex = function(index){
-			// Prepare
-			var State = null;
-
-			// Handle
-			if ( typeof index === 'undefined' ) {
-				// Get the last inserted
-				State = History.savedStates[History.savedStates.length-1];
-			}
-			else if ( index < 0 ) {
-				// Get from the end
-				State = History.savedStates[History.savedStates.length+index];
-			}
-			else {
-				// Get from the beginning
-				State = History.savedStates[index];
-			}
-
-			// Return State
-			return State;
-		};
-
-		// ----------------------------------------------------------------------
-		// Hash Helpers
-
-
-		/**
-		 * History.escapeString()
-		 * Escape a string
-		 * @param {String} str
-		 * @return {string}
-		 */
-		History.escapeString = function(str){
-			return encodeURI(url).replace(/%25/g, "%", "g");
-		};
-
-		/**
-		 * History.getHash()
-		 * @param {Location=} location
-		 * Gets the current document hash
-		 * Note: unlike location.hash, this is guaranteed to return the escaped hash in all browsers
-		 * @return {string}
-		 */
-		History.getHash = function(location){
-			if ( !location ) location = document.location;
-			var href = location.href.replace( /^[^#]*/, "" );
-			return href.substr(1);
-		};
-
-		/**
-		 * History.unescapeHash()
-		 * normalize and Unescape a Hash
-		 * @param {String} hash
-		 * @return {string}
-		 */
-		History.unescapeHash = function(hash){
-			// Prepare
-			var result = History.normalizeHash(hash);
-
-			// Unescape hash
-			result = decodeURIComponent(result);
-
-			// Return result
-			return result;
-		};
-
-		/**
-		 * History.normalizeHash()
-		 * normalize a hash across browsers
-		 * @return {string}
-		 */
-		History.normalizeHash = function(hash){
-			var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
-
-			// Return result
-			return result;
-		};
-
-		/**
-		 * History.setHash(hash)
-		 * Sets the document hash
-		 * @param {string} hash
-		 * @return {History}
-		 */
-		History.setHash = function(hash,queue){
-			// Handle Queueing
-			if ( queue !== false && History.busy() ) {
-				// Wait + Push to Queue
-				//History.debug('History.setHash: we must wait', arguments);
-				History.pushQueue({
-					scope: History,
-					callback: History.setHash,
-					args: arguments,
-					queue: queue
-				});
-				return false;
-			}
-
-			// Log
-			//History.debug('History.setHash: called',hash);
-
-			// Make Busy + Continue
-			History.busy(true);
-
-			// Check if hash is a state
-			var State = History.extractState(hash,true);
-			if ( State && !History.emulated.pushState ) {
-				// Hash is a state so skip the setHash
-				//History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
-
-				// PushState
-				History.pushState(State.data,State.title,State.url,false);
-			}
-			else if ( History.getHash() !== hash ) {
-				// Hash is a proper hash, so apply it
-
-				// Handle browser bugs
-				if ( History.bugs.setHash ) {
-					// Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
-
-					// Fetch the base page
-					var pageUrl = History.getPageUrl();
-
-					// Safari hash apply
-					History.pushState(null,null,pageUrl+'#'+hash,false);
-				}
-				else {
-					// Normal hash apply
-					document.location.hash = hash;
-				}
-			}
-
-			// Chain
-			return History;
-		};
-
-		/**
-		 * History.escape()
-		 * normalize and Escape a Hash
-		 * @return {string}
-		 */
-		History.escapeHash = function(hash){
-			var result = History.normalizeHash(hash);
-
-			// Escape hash
-			result = window.encodeURIComponent(result);
-
-			// IE6 Escape Bug
-			if ( !History.bugs.hashEscape ) {
-				// Restore common parts
-				result = result
-					.replace(/\%21/g,'!')
-					.replace(/\%26/g,'&')
-					.replace(/\%3D/g,'=')
-					.replace(/\%3F/g,'?');
-			}
-
-			// Return result
-			return result;
-		};
-
-		/**
-		 * History.getHashByUrl(url)
-		 * Extracts the Hash from a URL
-		 * @param {string} url
-		 * @return {string} url
-		 */
-		History.getHashByUrl = function(url){
-			// Extract the hash
-			var hash = String(url)
-				.replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
-				;
-
-			// Unescape hash
-			hash = History.unescapeHash(hash);
-
-			// Return hash
-			return hash;
-		};
-
-		/**
-		 * History.setTitle(title)
-		 * Applies the title to the document
-		 * @param {State} newState
-		 * @return {Boolean}
-		 */
-		History.setTitle = function(newState){
-			// Prepare
-			var title = newState.title;
-
-			// Initial
-			if ( !title ) {
-				var firstState = History.getStateByIndex(0);
-				if ( firstState && firstState.url === newState.url ) {
-					title = firstState.title||History.options.initialTitle;
-				}
-			}
-
-			// Apply
-			try {
-				document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
-			}
-			catch ( Exception ) { }
-			document.title = title;
-
-			// Chain
-			return History;
-		};
-
-		// ----------------------------------------------------------------------
-		// Queueing
-
-		/**
-		 * History.queues
-		 * The list of queues to use
-		 * First In, First Out
-		 */
-		History.queues = [];
-
-		/**
-		 * History.busy(value)
-		 * @param {boolean} value [optional]
-		 * @return {boolean} busy
-		 */
-		History.busy = function(value){
-			// Apply
-			if ( typeof value !== 'undefined' ) {
-				//History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
-				History.busy.flag = value;
-			}
-			// Default
-			else if ( typeof History.busy.flag === 'undefined' ) {
-				History.busy.flag = false;
-			}
-
-			// Queue
-			if ( !History.busy.flag ) {
-				// Execute the next item in the queue
-				clearTimeout(History.busy.timeout);
-				var fireNext = function(){
-					if ( History.busy.flag ) return;
-					for ( var i=History.queues.length-1; i >= 0; --i ) {
-						var queue = History.queues[i];
-						if ( queue.length === 0 ) continue;
-						var item = queue.shift();
-						History.fireQueueItem(item);
-						History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
-					}
-				};
-				History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
-			}
-
-			// Return
-			return History.busy.flag;
-		};
-
-		/**
-		 * History.fireQueueItem(item)
-		 * Fire a Queue Item
-		 * @param {Object} item
-		 * @return {Mixed} result
-		 */
-		History.fireQueueItem = function(item){
-			return item.callback.apply(item.scope||History,item.args||[]);
-		};
-
-		/**
-		 * History.pushQueue(callback,args)
-		 * Add an item to the queue
-		 * @param {Object} item [scope,callback,args,queue]
-		 */
-		History.pushQueue = function(item){
-			// Prepare the queue
-			History.queues[item.queue||0] = History.queues[item.queue||0]||[];
-
-			// Add to the queue
-			History.queues[item.queue||0].push(item);
-
-			// Chain
-			return History;
-		};
-
-		/**
-		 * History.queue (item,queue), (func,queue), (func), (item)
-		 * Either firs the item now if not busy, or adds it to the queue
-		 */
-		History.queue = function(item,queue){
-			// Prepare
-			if ( typeof item === 'function' ) {
-				item = {
-					callback: item
-				};
-			}
-			if ( typeof queue !== 'undefined' ) {
-				item.queue = queue;
-			}
-
-			// Handle
-			if ( History.busy() ) {
-				History.pushQueue(item);
-			} else {
-				History.fireQueueItem(item);
-			}
-
-			// Chain
-			return History;
-		};
-
-		/**
-		 * History.clearQueue()
-		 * Clears the Queue
-		 */
-		History.clearQueue = function(){
-			History.busy.flag = false;
-			History.queues = [];
-			return History;
-		};
-
-
-		// ----------------------------------------------------------------------
-		// IE Bug Fix
-
-		/**
-		 * History.stateChanged
-		 * States whether or not the state has changed since the last double check was initialised
-		 */
-		History.stateChanged = false;
-
-		/**
-		 * History.doubleChecker
-		 * Contains the timeout used for the double checks
-		 */
-		History.doubleChecker = false;
-
-		/**
-		 * History.doubleCheckComplete()
-		 * Complete a double check
-		 * @return {History}
-		 */
-		History.doubleCheckComplete = function(){
-			// Update
-			History.stateChanged = true;
-
-			// Clear
-			History.doubleCheckClear();
-
-			// Chain
-			return History;
-		};
-
-		/**
-		 * History.doubleCheckClear()
-		 * Clear a double check
-		 * @return {History}
-		 */
-		History.doubleCheckClear = function(){
-			// Clear
-			if ( History.doubleChecker ) {
-				clearTimeout(History.doubleChecker);
-				History.doubleChecker = false;
-			}
-
-			// Chain
-			return History;
-		};
-
-		/**
-		 * History.doubleCheck()
-		 * Create a double check
-		 * @return {History}
-		 */
-		History.doubleCheck = function(tryAgain){
-			// Reset
-			History.stateChanged = false;
-			History.doubleCheckClear();
-
-			// Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
-			// Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
-			if ( History.bugs.ieDoubleCheck ) {
-				// Apply Check
-				History.doubleChecker = setTimeout(
-					function(){
-						History.doubleCheckClear();
-						if ( !History.stateChanged ) {
-							//History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
-							// Re-Attempt
-							tryAgain();
-						}
-						return true;
-					},
-					History.options.doubleCheckInterval
-				);
-			}
-
-			// Chain
-			return History;
-		};
-
-		// ----------------------------------------------------------------------
-		// Safari Bug Fix
-
-		/**
-		 * History.safariStatePoll()
-		 * Poll the current state
-		 * @return {History}
-		 */
-		History.safariStatePoll = function(){
-			// Poll the URL
-
-			// Get the Last State which has the new URL
-			var
-				urlState = History.extractState(document.URL||document.location.href),
-				newState;
-
-			// Check for a difference
-			if ( !History.isLastSavedState(urlState) ) {
-				newState = urlState;
-			}
-			else {
-				return;
-			}
-
-			// Check if we have a state with that url
-			// If not create it
-			if ( !newState ) {
-				//History.debug('History.safariStatePoll: new');
-				newState = History.createStateObject();
-			}
-
-			// Apply the New State
-			//History.debug('History.safariStatePoll: trigger');
-			History.Adapter.trigger(window,'popstate');
-
-			// Chain
-			return History;
-		};
-
-		// ----------------------------------------------------------------------
-		// State Aliases
-
-		/**
-		 * History.back(queue)
-		 * Send the browser history back one item
-		 * @param {Integer} queue [optional]
-		 */
-		History.back = function(queue){
-			//History.debug('History.back: called', arguments);
-
-			// Handle Queueing
-			if ( queue !== false && History.busy() ) {
-				// Wait + Push to Queue
-				//History.debug('History.back: we must wait', arguments);
-				History.pushQueue({
-					scope: History,
-					callback: History.back,
-					args: arguments,
-					queue: queue
-				});
-				return false;
-			}
-
-			// Make Busy + Continue
-			History.busy(true);
-
-			// Fix certain browser bugs that prevent the state from changing
-			History.doubleCheck(function(){
-				History.back(false);
-			});
-
-			// Go back
-			history.go(-1);
-
-			// End back closure
-			return true;
-		};
-
-		/**
-		 * History.forward(queue)
-		 * Send the browser history forward one item
-		 * @param {Integer} queue [optional]
-		 */
-		History.forward = function(queue){
-			//History.debug('History.forward: called', arguments);
-
-			// Handle Queueing
-			if ( queue !== false && History.busy() ) {
-				// Wait + Push to Queue
-				//History.debug('History.forward: we must wait', arguments);
-				History.pushQueue({
-					scope: History,
-					callback: History.forward,
-					args: arguments,
-					queue: queue
-				});
-				return false;
-			}
-
-			// Make Busy + Continue
-			History.busy(true);
-
-			// Fix certain browser bugs that prevent the state from changing
-			History.doubleCheck(function(){
-				History.forward(false);
-			});
-
-			// Go forward
-			history.go(1);
-
-			// End forward closure
-			return true;
-		};
-
-		/**
-		 * History.go(index,queue)
-		 * Send the browser history back or forward index times
-		 * @param {Integer} queue [optional]
-		 */
-		History.go = function(index,queue){
-			//History.debug('History.go: called', arguments);
-
-			// Prepare
-			var i;
-
-			// Handle
-			if ( index > 0 ) {
-				// Forward
-				for ( i=1; i<=index; ++i ) {
-					History.forward(queue);
-				}
-			}
-			else if ( index < 0 ) {
-				// Backward
-				for ( i=-1; i>=index; --i ) {
-					History.back(queue);
-				}
-			}
-			else {
-				throw new Error('History.go: History.go requires a positive or negative integer passed.');
-			}
-
-			// Chain
-			return History;
-		};
-
-
-		// ----------------------------------------------------------------------
-		// Initialise
-
-		/**
-		 * Create the initial State
-		 */
-		History.saveState(History.storeState(History.extractState(document.URL||document.location.href,true)));
-
-		/**
-		 * Bind for Saving Store
-		 */
-		if ( amplify ) {
-			History.onUnload = function(){
-				// Prepare
-				var
-					currentStore = amplify.store('History.store')||{},
-					item;
-
-				// Ensure
-				currentStore.idToState = currentStore.idToState || {};
-				currentStore.urlToId = currentStore.urlToId || {};
-				currentStore.stateToId = currentStore.stateToId || {};
-
-				// Sync
-				for ( item in History.idToState ) {
-					if ( !History.idToState.hasOwnProperty(item) ) {
-						continue;
-					}
-					currentStore.idToState[item] = History.idToState[item];
-				}
-				for ( item in History.urlToId ) {
-					if ( !History.urlToId.hasOwnProperty(item) ) {
-						continue;
-					}
-					currentStore.urlToId[item] = History.urlToId[item];
-				}
-				for ( item in History.stateToId ) {
-					if ( !History.stateToId.hasOwnProperty(item) ) {
-						continue;
-					}
-					currentStore.stateToId[item] = History.stateToId[item];
-				}
-
-				// Update
-				History.store = currentStore;
-
-				// Store
-				amplify.store('History.store',currentStore);
-			};
-			// For Internet Explorer
-			History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
-			// For Other Browsers
-			History.Adapter.bind(window,'beforeunload',History.onUnload);
-			History.Adapter.bind(window,'unload',History.onUnload);
-			// Both are enabled for consistency
-		}
-
-
-		// ----------------------------------------------------------------------
-		// HTML5 State Support
-
-		if ( History.emulated.pushState ) {
-			/*
-			 * Provide Skeleton for HTML4 Browsers
-			 */
-
-			// Prepare
-			var emptyFunction = function(){};
-			History.pushState = History.pushState||emptyFunction;
-			History.replaceState = History.replaceState||emptyFunction;
-		}
-		else {
-			/*
-			 * Use native HTML5 History API Implementation
-			 */
-
-			/**
-			 * History.onPopState(event,extra)
-			 * Refresh the Current State
-			 */
-			History.onPopState = function(event){
-				// Reset the double check
-				History.doubleCheckComplete();
-
-				// Check for a Hash, and handle apporiatly
-				var currentHash	= History.getHash();
-				if ( currentHash ) {
-					// Expand Hash
-					var currentState = History.extractState(currentHash||document.URL||document.location.href,true);
-					if ( currentState ) {
-						// We were able to parse it, it must be a State!
-						// Let's forward to replaceState
-						//History.debug('History.onPopState: state anchor', currentHash, currentState);
-						History.replaceState(currentState.data, currentState.title, currentState.url, false);
-					}
-					else {
-						// Traditional Anchor
-						//History.debug('History.onPopState: traditional anchor', currentHash);
-						History.Adapter.trigger(window,'anchorchange');
-						History.busy(false);
-					}
-
-					// We don't care for hashes
-					History.expectedStateId = false;
-					return false;
-				}
-
-				// Prepare
-				var newState = false;
-
-				// Prepare
-				event = event||{};
-				if ( typeof event.state === 'undefined' ) {
-					// jQuery
-					if ( typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined' ) {
-						event.state = event.originalEvent.state||false;
-					}
-					// MooTools
-					else if ( typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined' ) {
-						event.state = event.event.state||false;
-					}
-					// Ensure
-					event.state = (event.state||false);					
-				}
-
-				// Fetch State
-				if ( event.state ) {
-					// Vanilla: Back/forward button was used
-					newState = History.getStateById(event.state);
-				}
-				else if ( History.expectedStateId ) {
-					// Vanilla: A new state was pushed, and popstate was called manually
-					newState = History.getStateById(History.expectedStateId);
-				}
-				else {
-					// Initial State
-					newState = History.extractState(document.URL||document.location.href);
-				}
-
-				// The State did not exist in our store
-				if ( !newState ) {
-					// Regenerate the State
-					newState = History.createStateObject(null,null,document.URL||document.location.href);
-				}
-
-				// Clean
-				History.expectedStateId = false;
-
-				// Check if we are the same state
-				if ( History.isLastSavedState(newState) ) {
-					// There has been no change (just the page's hash has finally propagated)
-					//History.debug('History.onPopState: no change', newState, History.savedStates);
-					History.busy(false);
-					return false;
-				}
-
-				// Store the State
-				History.storeState(newState);
-				History.saveState(newState);
-
-				// Force update of the title
-				History.setTitle(newState);
-
-				// Fire Our Event
-				History.Adapter.trigger(window,'statechange');
-				History.busy(false);
-
-				// Return true
-				return true;
-			};
-			History.Adapter.bind(window,'popstate',History.onPopState);
-
-			/**
-			 * History.pushState(data,title,url)
-			 * Add a new State to the history object, become it, and trigger onpopstate
-			 * We have to trigger for HTML4 compatibility
-			 * @param {object} data
-			 * @param {string} title
-			 * @param {string} url
-			 * @return {true}
-			 */
-			History.pushState = function(data,title,url,queue){
-				//History.debug('History.pushState: called', arguments);
-
-				// Check the State
-				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
-					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
-				}
-
-				// Handle Queueing
-				if ( queue !== false && History.busy() ) {
-					// Wait + Push to Queue
-					//History.debug('History.pushState: we must wait', arguments);
-					History.pushQueue({
-						scope: History,
-						callback: History.pushState,
-						args: arguments,
-						queue: queue
-					});
-					return false;
-				}
-
-				// Make Busy + Continue
-				History.busy(true);
-
-				// Create the newState
-				var newState = History.createStateObject(data,title,url);
-
-				// Check it
-				if ( History.isLastSavedState(newState) ) {
-					// Won't be a change
-					History.busy(false);
-				}
-				else {
-					// Store the newState
-					History.storeState(newState);
-					History.expectedStateId = newState.id;
-
-					// Push the newState
-					history.pushState(newState.id,newState.title,newState.url);
-
-					// Fire HTML5 Event
-					History.Adapter.trigger(window,'popstate');
-				}
-
-				// End pushState closure
-				return true;
-			};
-
-			/**
-			 * History.replaceState(data,title,url)
-			 * Replace the State and trigger onpopstate
-			 * We have to trigger for HTML4 compatibility
-			 * @param {object} data
-			 * @param {string} title
-			 * @param {string} url
-			 * @return {true}
-			 */
-			History.replaceState = function(data,title,url,queue){
-				//History.debug('History.replaceState: called', arguments);
-
-				// Check the State
-				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
-					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
-				}
-
-				// Handle Queueing
-				if ( queue !== false && History.busy() ) {
-					// Wait + Push to Queue
-					//History.debug('History.replaceState: we must wait', arguments);
-					History.pushQueue({
-						scope: History,
-						callback: History.replaceState,
-						args: arguments,
-						queue: queue
-					});
-					return false;
-				}
-
-				// Make Busy + Continue
-				History.busy(true);
-
-				// Create the newState
-				var newState = History.createStateObject(data,title,url);
-
-				// Check it
-				if ( History.isLastSavedState(newState) ) {
-					// Won't be a change
-					History.busy(false);
-				}
-				else {
-					// Store the newState
-					History.storeState(newState);
-					History.expectedStateId = newState.id;
-
-					// Push the newState
-					history.replaceState(newState.id,newState.title,newState.url);
-
-					// Fire HTML5 Event
-					History.Adapter.trigger(window,'popstate');
-				}
-
-				// End replaceState closure
-				return true;
-			};
-
-			// Be aware, the following is only for native pushState implementations
-			// If you are wanting to include something for all browsers
-			// Then include it above this if block
-
-			/**
-			 * Setup Safari Fix
-			 */
-			if ( History.bugs.safariPoll ) {
-				History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
-			}
-
-			/**
-			 * Ensure Cross Browser Compatibility
-			 */
-			if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
-				/**
-				 * Fix Safari HashChange Issue
-				 */
-
-				// Setup Alias
-				History.Adapter.bind(window,'hashchange',function(){
-					History.Adapter.trigger(window,'popstate');
-				});
-
-				// Initialise Alias
-				if ( History.getHash() ) {
-					History.Adapter.onDomLoad(function(){
-						History.Adapter.trigger(window,'hashchange');
-					});
-				}
-			}
-
-		} // !History.emulated.pushState
-
-	}; // History.initCore
-
-	// Try and Initialise History
-	History.init();
-
-})(window);
--- a/kallithea/public/js/select2/select2-bootstrap.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/**
- * Select2 Bootstrap CSS 1.0
- * Compatible with select2 3.3.2 and bootstrap 2.3.1
- * MIT License
- */
-.select2-container {
-  vertical-align: middle;
-}
-.select2-container.input-mini {
-  width: 60px;
-}
-.select2-container.input-small {
-  width: 90px;
-}
-.select2-container.input-medium {
-  width: 150px;
-}
-.select2-container.input-large {
-  width: 210px;
-}
-.select2-container.input-xlarge {
-  width: 270px;
-}
-.select2-container.input-xxlarge {
-  width: 530px;
-}
-.select2-container.input-default {
-  width: 220px;
-}
-.select2-container[class*="span"] {
-  float: none;
-  margin-left: 0;
-}
-
-.select2-container .select2-choice,
-.select2-container-multi .select2-choices {
-  height: 28px;
-  line-height: 29px;
-  border: 1px solid #cccccc;
-  -webkit-border-radius: 4px;
-  -moz-border-radius: 4px;
-  border-radius: 4px;
-  background: none;
-  background-color: white;
-  filter: none;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.select2-container .select2-choice div, .select2-container .select2-choice .select2-arrow,
-.select2-container.select2-container-disabled .select2-choice div,
-.select2-container.select2-container-disabled .select2-choice .select2-arrow {
-  border-left: none;
-  background: none;
-  filter: none;
-}
-
-.control-group.error [class^="select2-choice"] {
-  border-color: #b94a48;
-}
-
-.select2-container-multi .select2-choices .select2-search-field {
-  height: 28px;
-  line-height: 27px;
-}
-
-.select2-drop.select2-drop-active,
-.select2-container-active .select2-choice,
-.select2-container-multi.select2-container-active .select2-choices {
-  border-color: rgba(82, 168, 236, 0.8);
-  border-color: #ccc\0;
-  outline: none;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-}
-
-[class^="input-"] .select2-container {
-  font-size: 14px;
-}
-
-.input-prepend [class^="select2-choice"] {
-  border-top-left-radius: 0;
-  border-bottom-left-radius: 0;
-}
-
-.input-append [class^="select2-choice"] {
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.select2-dropdown-open [class^="select2-choice"] {
-  border-bottom-left-radius: 0;
-  border-bottom-right-radius: 0;
-}
-
-.select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
-  border-top-left-radius: 0;
-  border-top-right-radius: 0;
-}
-
-[class^="input-"] .select2-offscreen {
-  position: absolute;
-}
-
-/**
- * This stops the quick flash when a native selectbox is shown and
- * then replaced by a select2 input when javascript kicks in. This can be
- * removed if javascript is not present
- */
-select.select2 {
-  height: 28px;
-  visibility: hidden;
-}
Binary file kallithea/public/js/select2/select2-spinner.gif has changed
--- a/kallithea/public/js/select2/select2.css	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,700 +0,0 @@
-/*
-Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 EDT 2014
-*/
-.select2-container {
-    margin: 0;
-    position: relative;
-    display: inline-block;
-    /* inline-block for ie7 */
-    zoom: 1;
-    *display: inline;
-    vertical-align: middle;
-}
-
-.select2-container,
-.select2-drop,
-.select2-search,
-.select2-search input {
-  /*
-    Force border-box so that % widths fit the parent
-    container without overlap because of margin/padding.
-    More Info : http://www.quirksmode.org/css/box.html
-  */
-  -webkit-box-sizing: border-box; /* webkit */
-     -moz-box-sizing: border-box; /* firefox */
-          box-sizing: border-box; /* css3 */
-}
-
-.select2-container .select2-choice {
-    display: block;
-    height: 26px;
-    padding: 0 0 0 8px;
-    overflow: hidden;
-    position: relative;
-
-    border: 1px solid #aaa;
-    white-space: nowrap;
-    line-height: 26px;
-    color: #444;
-    text-decoration: none;
-
-    border-radius: 4px;
-
-    background-clip: padding-box;
-
-    -webkit-touch-callout: none;
-      -webkit-user-select: none;
-         -moz-user-select: none;
-          -ms-user-select: none;
-              user-select: none;
-
-    background-color: #fff;
-    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
-    background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
-    background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
-    background-image: linear-gradient(to top, #eee 0%, #fff 50%);
-}
-
-html[dir="rtl"] .select2-container .select2-choice {
-    padding: 0 8px 0 0;
-}
-
-.select2-container.select2-drop-above .select2-choice {
-    border-bottom-color: #aaa;
-
-    border-radius: 0 0 4px 4px;
-
-    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
-    background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
-    background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
-    background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
-}
-
-.select2-container.select2-allowclear .select2-choice .select2-chosen {
-    margin-right: 42px;
-}
-
-.select2-container .select2-choice > .select2-chosen {
-    margin-right: 26px;
-    display: block;
-    overflow: hidden;
-
-    white-space: nowrap;
-
-    text-overflow: ellipsis;
-    float: none;
-    width: auto;
-}
-
-html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
-    margin-left: 26px;
-    margin-right: 0;
-}
-
-.select2-container .select2-choice abbr {
-    display: none;
-    width: 12px;
-    height: 12px;
-    position: absolute;
-    right: 24px;
-    top: 8px;
-
-    font-size: 1px;
-    text-decoration: none;
-
-    border: 0;
-    background: url('select2.png') right top no-repeat;
-    cursor: pointer;
-    outline: 0;
-}
-
-.select2-container.select2-allowclear .select2-choice abbr {
-    display: inline-block;
-}
-
-.select2-container .select2-choice abbr:hover {
-    background-position: right -11px;
-    cursor: pointer;
-}
-
-.select2-drop-mask {
-    border: 0;
-    margin: 0;
-    padding: 0;
-    position: fixed;
-    left: 0;
-    top: 0;
-    min-height: 100%;
-    min-width: 100%;
-    height: auto;
-    width: auto;
-    opacity: 0;
-    z-index: 9998;
-    /* styles required for IE to work */
-    background-color: #fff;
-    filter: alpha(opacity=0);
-}
-
-.select2-drop {
-    width: 100%;
-    margin-top: -1px;
-    position: absolute;
-    z-index: 9999;
-    top: 100%;
-
-    background: #fff;
-    color: #000;
-    border: 1px solid #aaa;
-    border-top: 0;
-
-    border-radius: 0 0 4px 4px;
-
-    -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
-            box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
-}
-
-.select2-drop.select2-drop-above {
-    margin-top: 1px;
-    border-top: 1px solid #aaa;
-    border-bottom: 0;
-
-    border-radius: 4px 4px 0 0;
-
-    -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
-            box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
-}
-
-.select2-drop-active {
-    border: 1px solid #5897fb;
-    border-top: none;
-}
-
-.select2-drop.select2-drop-above.select2-drop-active {
-    border-top: 1px solid #5897fb;
-}
-
-.select2-drop-auto-width {
-    border-top: 1px solid #aaa;
-    width: auto;
-}
-
-.select2-drop-auto-width .select2-search {
-    padding-top: 4px;
-}
-
-.select2-container .select2-choice .select2-arrow {
-    display: inline-block;
-    width: 18px;
-    height: 100%;
-    position: absolute;
-    right: 0;
-    top: 0;
-
-    border-left: 1px solid #aaa;
-    border-radius: 0 4px 4px 0;
-
-    background-clip: padding-box;
-
-    background: #ccc;
-    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
-    background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
-    background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
-    background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
-}
-
-html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
-    left: 0;
-    right: auto;
-
-    border-left: none;
-    border-right: 1px solid #aaa;
-    border-radius: 4px 0 0 4px;
-}
-
-.select2-container .select2-choice .select2-arrow b {
-    display: block;
-    width: 100%;
-    height: 100%;
-    background: url('select2.png') no-repeat 0 1px;
-}
-
-html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
-    background-position: 2px 1px;
-}
-
-.select2-search {
-    display: inline-block;
-    width: 100%;
-    min-height: 26px;
-    margin: 0;
-    padding-left: 4px;
-    padding-right: 4px;
-
-    position: relative;
-    z-index: 10000;
-
-    white-space: nowrap;
-}
-
-.select2-search input {
-    width: 100%;
-    height: auto !important;
-    min-height: 26px;
-    padding: 4px 20px 4px 5px;
-    margin: 0;
-
-    outline: 0;
-    font-family: sans-serif;
-    font-size: 1em;
-
-    border: 1px solid #aaa;
-    border-radius: 0;
-
-    -webkit-box-shadow: none;
-            box-shadow: none;
-
-    background: #fff url('select2.png') no-repeat 100% -22px;
-    background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
-    background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
-}
-
-html[dir="rtl"] .select2-search input {
-    padding: 4px 5px 4px 20px;
-
-    background: #fff url('select2.png') no-repeat -37px -22px;
-    background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
-    background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
-}
-
-.select2-drop.select2-drop-above .select2-search input {
-    margin-top: 4px;
-}
-
-.select2-search input.select2-active {
-    background: #fff url('select2-spinner.gif') no-repeat 100%;
-    background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
-    background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
-    background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
-}
-
-.select2-container-active .select2-choice,
-.select2-container-active .select2-choices {
-    border: 1px solid #5897fb;
-    outline: none;
-
-    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
-            box-shadow: 0 0 5px rgba(0, 0, 0, .3);
-}
-
-.select2-dropdown-open .select2-choice {
-    border-bottom-color: transparent;
-    -webkit-box-shadow: 0 1px 0 #fff inset;
-            box-shadow: 0 1px 0 #fff inset;
-
-    border-bottom-left-radius: 0;
-    border-bottom-right-radius: 0;
-
-    background-color: #eee;
-    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
-    background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
-    background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
-    background-image: linear-gradient(to top, #fff 0%, #eee 50%);
-}
-
-.select2-dropdown-open.select2-drop-above .select2-choice,
-.select2-dropdown-open.select2-drop-above .select2-choices {
-    border: 1px solid #5897fb;
-    border-top-color: transparent;
-
-    background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
-    background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
-    background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
-    background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
-}
-
-.select2-dropdown-open .select2-choice .select2-arrow {
-    background: transparent;
-    border-left: none;
-    filter: none;
-}
-html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
-    border-right: none;
-}
-
-.select2-dropdown-open .select2-choice .select2-arrow b {
-    background-position: -18px 1px;
-}
-
-html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
-    background-position: -16px 1px;
-}
-
-.select2-hidden-accessible {
-    border: 0;
-    clip: rect(0 0 0 0);
-    height: 1px;
-    margin: -1px;
-    overflow: hidden;
-    padding: 0;
-    position: absolute;
-    width: 1px;
-}
-
-/* results */
-.select2-results {
-    max-height: 200px;
-    padding: 0 0 0 4px;
-    margin: 4px 4px 4px 0;
-    position: relative;
-    overflow-x: hidden;
-    overflow-y: auto;
-    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-}
-
-html[dir="rtl"] .select2-results {
-    padding: 0 4px 0 0;
-    margin: 4px 0 4px 4px;
-}
-
-.select2-results ul.select2-result-sub {
-    margin: 0;
-    padding-left: 0;
-}
-
-.select2-results li {
-    list-style: none;
-    display: list-item;
-    background-image: none;
-}
-
-.select2-results li.select2-result-with-children > .select2-result-label {
-    font-weight: bold;
-}
-
-.select2-results .select2-result-label {
-    padding: 3px 7px 4px;
-    margin: 0;
-    cursor: pointer;
-
-    min-height: 1em;
-
-    -webkit-touch-callout: none;
-      -webkit-user-select: none;
-         -moz-user-select: none;
-          -ms-user-select: none;
-              user-select: none;
-}
-
-.select2-results-dept-1 .select2-result-label { padding-left: 20px }
-.select2-results-dept-2 .select2-result-label { padding-left: 40px }
-.select2-results-dept-3 .select2-result-label { padding-left: 60px }
-.select2-results-dept-4 .select2-result-label { padding-left: 80px }
-.select2-results-dept-5 .select2-result-label { padding-left: 100px }
-.select2-results-dept-6 .select2-result-label { padding-left: 110px }
-.select2-results-dept-7 .select2-result-label { padding-left: 120px }
-
-.select2-results .select2-highlighted {
-    background: #3875d7;
-    color: #fff;
-}
-
-.select2-results li em {
-    background: #feffde;
-    font-style: normal;
-}
-
-.select2-results .select2-highlighted em {
-    background: transparent;
-}
-
-.select2-results .select2-highlighted ul {
-    background: #fff;
-    color: #000;
-}
-
-
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-selection-limit {
-    background: #f4f4f4;
-    display: list-item;
-    padding-left: 5px;
-}
-
-/*
-disabled look for disabled choices in the results dropdown
-*/
-.select2-results .select2-disabled.select2-highlighted {
-    color: #666;
-    background: #f4f4f4;
-    display: list-item;
-    cursor: default;
-}
-.select2-results .select2-disabled {
-  background: #f4f4f4;
-  display: list-item;
-  cursor: default;
-}
-
-.select2-results .select2-selected {
-    display: none;
-}
-
-.select2-more-results.select2-active {
-    background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
-}
-
-.select2-more-results {
-    background: #f4f4f4;
-    display: list-item;
-}
-
-/* disabled styles */
-
-.select2-container.select2-container-disabled .select2-choice {
-    background-color: #f4f4f4;
-    background-image: none;
-    border: 1px solid #ddd;
-    cursor: default;
-}
-
-.select2-container.select2-container-disabled .select2-choice .select2-arrow {
-    background-color: #f4f4f4;
-    background-image: none;
-    border-left: 0;
-}
-
-.select2-container.select2-container-disabled .select2-choice abbr {
-    display: none;
-}
-
-
-/* multiselect */
-
-.select2-container-multi .select2-choices {
-    height: auto !important;
-    height: 1%;
-    margin: 0;
-    padding: 0 5px 0 0;
-    position: relative;
-
-    border: 1px solid #aaa;
-    cursor: text;
-    overflow: hidden;
-
-    background-color: #fff;
-    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
-    background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
-    background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
-    background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
-}
-
-html[dir="rtl"] .select2-container-multi .select2-choices {
-    padding: 0 0 0 5px;
-}
-
-.select2-locked {
-  padding: 3px 5px 3px 5px !important;
-}
-
-.select2-container-multi .select2-choices {
-    min-height: 26px;
-}
-
-.select2-container-multi.select2-container-active .select2-choices {
-    border: 1px solid #5897fb;
-    outline: none;
-
-    -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
-            box-shadow: 0 0 5px rgba(0, 0, 0, .3);
-}
-.select2-container-multi .select2-choices li {
-    float: left;
-    list-style: none;
-}
-html[dir="rtl"] .select2-container-multi .select2-choices li
-{
-    float: right;
-}
-.select2-container-multi .select2-choices .select2-search-field {
-    margin: 0;
-    padding: 0;
-    white-space: nowrap;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input {
-    padding: 5px;
-    margin: 1px 0;
-
-    font-family: sans-serif;
-    font-size: 100%;
-    color: #666;
-    outline: 0;
-    border: 0;
-    -webkit-box-shadow: none;
-            box-shadow: none;
-    background: transparent !important;
-}
-
-.select2-container-multi .select2-choices .select2-search-field input.select2-active {
-    background: #fff url('select2-spinner.gif') no-repeat 100% !important;
-}
-
-.select2-default {
-    color: #999 !important;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice {
-    padding: 3px 5px 3px 18px;
-    margin: 3px 0 3px 5px;
-    position: relative;
-
-    line-height: 13px;
-    color: #333;
-    cursor: default;
-    border: 1px solid #aaaaaa;
-
-    border-radius: 3px;
-
-    -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
-            box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
-
-    background-clip: padding-box;
-
-    -webkit-touch-callout: none;
-      -webkit-user-select: none;
-         -moz-user-select: none;
-          -ms-user-select: none;
-              user-select: none;
-
-    background-color: #e4e4e4;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
-    background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
-    background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
-    background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
-    background-image: linear-gradient(to top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
-}
-html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
-{
-    margin: 3px 5px 3px 0;
-    padding: 3px 18px 3px 5px;
-}
-.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
-    cursor: default;
-}
-.select2-container-multi .select2-choices .select2-search-choice-focus {
-    background: #d4d4d4;
-}
-
-.select2-search-choice-close {
-    display: block;
-    width: 12px;
-    height: 13px;
-    position: absolute;
-    right: 3px;
-    top: 4px;
-
-    font-size: 1px;
-    outline: none;
-    background: url('select2.png') right top no-repeat;
-}
-html[dir="rtl"] .select2-search-choice-close {
-    right: auto;
-    left: 3px;
-}
-
-.select2-container-multi .select2-search-choice-close {
-    left: 3px;
-}
-
-html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
-    left: auto;
-    right: 2px;
-}
-
-.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
-  background-position: right -11px;
-}
-.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
-    background-position: right -11px;
-}
-
-/* disabled styles */
-.select2-container-multi.select2-container-disabled .select2-choices {
-    background-color: #f4f4f4;
-    background-image: none;
-    border: 1px solid #ddd;
-    cursor: default;
-}
-
-.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
-    padding: 3px 5px 3px 5px;
-    border: 1px solid #ddd;
-    background-image: none;
-    background-color: #f4f4f4;
-}
-
-.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close {    display: none;
-    background: none;
-}
-/* end multiselect */
-
-
-.select2-result-selectable .select2-match,
-.select2-result-unselectable .select2-match {
-    text-decoration: underline;
-}
-
-.select2-offscreen, .select2-offscreen:focus {
-    clip: rect(0 0 0 0) !important;
-    width: 1px !important;
-    height: 1px !important;
-    border: 0 !important;
-    margin: 0 !important;
-    padding: 0 !important;
-    overflow: hidden !important;
-    position: absolute !important;
-    outline: 0 !important;
-    left: 0px !important;
-    top: 0px !important;
-}
-
-.select2-display-none {
-    display: none;
-}
-
-.select2-measure-scrollbar {
-    position: absolute;
-    top: -10000px;
-    left: -10000px;
-    width: 100px;
-    height: 100px;
-    overflow: scroll;
-}
-
-/* Retina-ize icons */
-
-@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx)  {
-    .select2-search input,
-    .select2-search-choice-close,
-    .select2-container .select2-choice abbr,
-    .select2-container .select2-choice .select2-arrow b {
-        background-image: url('select2x2.png') !important;
-        background-repeat: no-repeat !important;
-        background-size: 60px 40px !important;
-    }
-
-    .select2-search input {
-        background-position: 100% -21px !important;
-    }
-}
--- a/kallithea/public/js/select2/select2.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3481 +0,0 @@
-/*
-Copyright 2012 Igor Vaynberg
-
-Version: 3.5.0 Timestamp: Mon Jun 16 19:29:44 EDT 2014
-
-This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
-General Public License version 2 (the "GPL License"). You may choose either license to govern your
-use of this software only upon the condition that you accept all of the terms of either the Apache
-License or the GPL License.
-
-You may obtain a copy of the Apache License and the GPL License at:
-
-    http://www.apache.org/licenses/LICENSE-2.0
-    http://www.gnu.org/licenses/gpl-2.0.html
-
-Unless required by applicable law or agreed to in writing, software distributed under the
-Apache License or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for
-the specific language governing permissions and limitations under the Apache License and the GPL License.
-*/
-(function ($) {
-    if(typeof $.fn.each2 == "undefined") {
-        $.extend($.fn, {
-            /*
-            * 4-10 times faster .each replacement
-            * use it carefully, as it overrides jQuery context of element on each iteration
-            */
-            each2 : function (c) {
-                var j = $([0]), i = -1, l = this.length;
-                while (
-                    ++i < l
-                    && (j.context = j[0] = this[i])
-                    && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object
-                );
-                return this;
-            }
-        });
-    }
-})(jQuery);
-
-(function ($, undefined) {
-    "use strict";
-    /*global document, window, jQuery, console */
-
-    if (window.Select2 !== undefined) {
-        return;
-    }
-
-    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer,
-        lastMousePosition={x:0,y:0}, $document, scrollBarDimensions,
-
-    KEY = {
-        TAB: 9,
-        ENTER: 13,
-        ESC: 27,
-        SPACE: 32,
-        LEFT: 37,
-        UP: 38,
-        RIGHT: 39,
-        DOWN: 40,
-        SHIFT: 16,
-        CTRL: 17,
-        ALT: 18,
-        PAGE_UP: 33,
-        PAGE_DOWN: 34,
-        HOME: 36,
-        END: 35,
-        BACKSPACE: 8,
-        DELETE: 46,
-        isArrow: function (k) {
-            k = k.which ? k.which : k;
-            switch (k) {
-            case KEY.LEFT:
-            case KEY.RIGHT:
-            case KEY.UP:
-            case KEY.DOWN:
-                return true;
-            }
-            return false;
-        },
-        isControl: function (e) {
-            var k = e.which;
-            switch (k) {
-            case KEY.SHIFT:
-            case KEY.CTRL:
-            case KEY.ALT:
-                return true;
-            }
-
-            if (e.metaKey) return true;
-
-            return false;
-        },
-        isFunctionKey: function (k) {
-            k = k.which ? k.which : k;
-            return k >= 112 && k <= 123;
-        }
-    },
-    MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>",
-
-    DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z","\u0386":"\u0391","\u0388":"\u0395","\u0389":"\u0397","\u038A":"\u0399","\u03AA":"\u0399","\u038C":"\u039F","\u038E":"\u03A5","\u03AB":"\u03A5","\u038F":"\u03A9","\u03AC":"\u03B1","\u03AD":"\u03B5","\u03AE":"\u03B7","\u03AF":"\u03B9","\u03CA":"\u03B9","\u0390":"\u03B9","\u03CC":"\u03BF","\u03CD":"\u03C5","\u03CB":"\u03C5","\u03B0":"\u03C5","\u03C9":"\u03C9","\u03C2":"\u03C3"};
-
-    $document = $(document);
-
-    nextUid=(function() { var counter=1; return function() { return counter++; }; }());
-
-
-    function reinsertElement(element) {
-        var placeholder = $(document.createTextNode(''));
-
-        element.before(placeholder);
-        placeholder.before(element);
-        placeholder.remove();
-    }
-
-    function stripDiacritics(str) {
-        // Used 'uni range + named function' from http://jsperf.com/diacritics/18
-        function match(a) {
-            return DIACRITICS[a] || a;
-        }
-
-        return str.replace(/[^\u0000-\u007E]/g, match);
-    }
-
-    function indexOf(value, array) {
-        var i = 0, l = array.length;
-        for (; i < l; i = i + 1) {
-            if (equal(value, array[i])) return i;
-        }
-        return -1;
-    }
-
-    function measureScrollbar () {
-        var $template = $( MEASURE_SCROLLBAR_TEMPLATE );
-        $template.appendTo('body');
-
-        var dim = {
-            width: $template.width() - $template[0].clientWidth,
-            height: $template.height() - $template[0].clientHeight
-        };
-        $template.remove();
-
-        return dim;
-    }
-
-    /**
-     * Compares equality of a and b
-     * @param a
-     * @param b
-     */
-    function equal(a, b) {
-        if (a === b) return true;
-        if (a === undefined || b === undefined) return false;
-        if (a === null || b === null) return false;
-        // Check whether 'a' or 'b' is a string (primitive or object).
-        // The concatenation of an empty string (+'') converts its argument to a string's primitive.
-        if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object
-        if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object
-        return false;
-    }
-
-    /**
-     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
-     * strings
-     * @param string
-     * @param separator
-     */
-    function splitVal(string, separator) {
-        var val, i, l;
-        if (string === null || string.length < 1) return [];
-        val = string.split(separator);
-        for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]);
-        return val;
-    }
-
-    function getSideBorderPadding(element) {
-        return element.outerWidth(false) - element.width();
-    }
-
-    function installKeyUpChangeEvent(element) {
-        var key="keyup-change-value";
-        element.on("keydown", function () {
-            if ($.data(element, key) === undefined) {
-                $.data(element, key, element.val());
-            }
-        });
-        element.on("keyup", function () {
-            var val= $.data(element, key);
-            if (val !== undefined && element.val() !== val) {
-                $.removeData(element, key);
-                element.trigger("keyup-change");
-            }
-        });
-    }
-
-
-    /**
-     * filters mouse events so an event is fired only if the mouse moved.
-     *
-     * filters out mouse events that occur when mouse is stationary but
-     * the elements under the pointer are scrolled.
-     */
-    function installFilteredMouseMove(element) {
-        element.on("mousemove", function (e) {
-            var lastpos = lastMousePosition;
-            if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
-                $(e.target).trigger("mousemove-filtered", e);
-            }
-        });
-    }
-
-    /**
-     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
-     * within the last quietMillis milliseconds.
-     *
-     * @param quietMillis number of milliseconds to wait before invoking fn
-     * @param fn function to be debounced
-     * @param ctx object to be used as this reference within fn
-     * @return debounced version of fn
-     */
-    function debounce(quietMillis, fn, ctx) {
-        ctx = ctx || undefined;
-        var timeout;
-        return function () {
-            var args = arguments;
-            window.clearTimeout(timeout);
-            timeout = window.setTimeout(function() {
-                fn.apply(ctx, args);
-            }, quietMillis);
-        };
-    }
-
-    function installDebouncedScroll(threshold, element) {
-        var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);});
-        element.on("scroll", function (e) {
-            if (indexOf(e.target, element.get()) >= 0) notify(e);
-        });
-    }
-
-    function focus($el) {
-        if ($el[0] === document.activeElement) return;
-
-        /* set the focus in a 0 timeout - that way the focus is set after the processing
-            of the current event has finished - which seems like the only reliable way
-            to set focus */
-        window.setTimeout(function() {
-            var el=$el[0], pos=$el.val().length, range;
-
-            $el.focus();
-
-            /* make sure el received focus so we do not error out when trying to manipulate the caret.
-                sometimes modals or others listeners may steal it after its set */
-            var isVisible = (el.offsetWidth > 0 || el.offsetHeight > 0);
-            if (isVisible && el === document.activeElement) {
-
-                /* after the focus is set move the caret to the end, necessary when we val()
-                    just before setting focus */
-                if(el.setSelectionRange)
-                {
-                    el.setSelectionRange(pos, pos);
-                }
-                else if (el.createTextRange) {
-                    range = el.createTextRange();
-                    range.collapse(false);
-                    range.select();
-                }
-            }
-        }, 0);
-    }
-
-    function getCursorInfo(el) {
-        el = $(el)[0];
-        var offset = 0;
-        var length = 0;
-        if ('selectionStart' in el) {
-            offset = el.selectionStart;
-            length = el.selectionEnd - offset;
-        } else if ('selection' in document) {
-            el.focus();
-            var sel = document.selection.createRange();
-            length = document.selection.createRange().text.length;
-            sel.moveStart('character', -el.value.length);
-            offset = sel.text.length - length;
-        }
-        return { offset: offset, length: length };
-    }
-
-    function killEvent(event) {
-        event.preventDefault();
-        event.stopPropagation();
-    }
-    function killEventImmediately(event) {
-        event.preventDefault();
-        event.stopImmediatePropagation();
-    }
-
-    function measureTextWidth(e) {
-        if (!sizer){
-            var style = e[0].currentStyle || window.getComputedStyle(e[0], null);
-            sizer = $(document.createElement("div")).css({
-                position: "absolute",
-                left: "-10000px",
-                top: "-10000px",
-                display: "none",
-                fontSize: style.fontSize,
-                fontFamily: style.fontFamily,
-                fontStyle: style.fontStyle,
-                fontWeight: style.fontWeight,
-                letterSpacing: style.letterSpacing,
-                textTransform: style.textTransform,
-                whiteSpace: "nowrap"
-            });
-            sizer.attr("class","select2-sizer");
-            $("body").append(sizer);
-        }
-        sizer.text(e.val());
-        return sizer.width();
-    }
-
-    function syncCssClasses(dest, src, adapter) {
-        var classes, replacements = [], adapted;
-
-        classes = $.trim(dest.attr("class"));
-
-        if (classes) {
-            classes = '' + classes; // for IE which returns object
-
-            $(classes.split(/\s+/)).each2(function() {
-                if (this.indexOf("select2-") === 0) {
-                    replacements.push(this);
-                }
-            });
-        }
-
-        classes = $.trim(src.attr("class"));
-
-        if (classes) {
-            classes = '' + classes; // for IE which returns object
-
-            $(classes.split(/\s+/)).each2(function() {
-                if (this.indexOf("select2-") !== 0) {
-                    adapted = adapter(this);
-
-                    if (adapted) {
-                        replacements.push(adapted);
-                    }
-                }
-            });
-        }
-
-        dest.attr("class", replacements.join(" "));
-    }
-
-
-    function markMatch(text, term, markup, escapeMarkup) {
-        var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())),
-            tl=term.length;
-
-        if (match<0) {
-            markup.push(escapeMarkup(text));
-            return;
-        }
-
-        markup.push(escapeMarkup(text.substring(0, match)));
-        markup.push("<span class='select2-match'>");
-        markup.push(escapeMarkup(text.substring(match, match + tl)));
-        markup.push("</span>");
-        markup.push(escapeMarkup(text.substring(match + tl, text.length)));
-    }
-
-    function defaultEscapeMarkup(markup) {
-        var replace_map = {
-            '\\': '&#92;',
-            '&': '&amp;',
-            '<': '&lt;',
-            '>': '&gt;',
-            '"': '&quot;',
-            "'": '&#39;',
-            "/": '&#47;'
-        };
-
-        return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
-            return replace_map[match];
-        });
-    }
-
-    /**
-     * Produces an ajax-based query function
-     *
-     * @param options object containing configuration parameters
-     * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax
-     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
-     * @param options.url url for the data
-     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
-     * @param options.dataType request data type: ajax, jsonp, other datatypes supported by jQuery's $.ajax function or the transport function if specified
-     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
-     * @param options.results a function(remoteData, pageNumber, query) that converts data returned form the remote request to the format expected by Select2.
-     *      The expected format is an object containing the following keys:
-     *      results array of objects that will be used as choices
-     *      more (optional) boolean indicating whether there are more results available
-     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
-     */
-    function ajax(options) {
-        var timeout, // current scheduled but not yet executed request
-            handler = null,
-            quietMillis = options.quietMillis || 100,
-            ajaxUrl = options.url,
-            self = this;
-
-        return function (query) {
-            window.clearTimeout(timeout);
-            timeout = window.setTimeout(function () {
-                var data = options.data, // ajax data function
-                    url = ajaxUrl, // ajax url string or function
-                    transport = options.transport || $.fn.select2.ajaxDefaults.transport,
-                    // deprecated - to be removed in 4.0  - use params instead
-                    deprecated = {
-                        type: options.type || 'GET', // set type of request (GET or POST)
-                        cache: options.cache || false,
-                        jsonpCallback: options.jsonpCallback||undefined,
-                        dataType: options.dataType||"json"
-                    },
-                    params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated);
-
-                data = data ? data.call(self, query.term, query.page, query.context) : null;
-                url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url;
-
-                if (handler && typeof handler.abort === "function") { handler.abort(); }
-
-                if (options.params) {
-                    if ($.isFunction(options.params)) {
-                        $.extend(params, options.params.call(self));
-                    } else {
-                        $.extend(params, options.params);
-                    }
-                }
-
-                $.extend(params, {
-                    url: url,
-                    dataType: options.dataType,
-                    data: data,
-                    success: function (data) {
-                        // TODO - replace query.page with query so users have access to term, page, etc.
-                        // added query as third paramter to keep backwards compatibility
-                        var results = options.results(data, query.page, query);
-                        query.callback(results);
-                    }
-                });
-                handler = transport.call(self, params);
-            }, quietMillis);
-        };
-    }
-
-    /**
-     * Produces a query function that works with a local array
-     *
-     * @param options object containing configuration parameters. The options parameter can either be an array or an
-     * object.
-     *
-     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
-     *
-     * If the object form is used it is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
-     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
-     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
-     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
-     * the text.
-     */
-    function local(options) {
-        var data = options, // data elements
-            dataText,
-            tmp,
-            text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search
-
-         if ($.isArray(data)) {
-            tmp = data;
-            data = { results: tmp };
-        }
-
-         if ($.isFunction(data) === false) {
-            tmp = data;
-            data = function() { return tmp; };
-        }
-
-        var dataItem = data();
-        if (dataItem.text) {
-            text = dataItem.text;
-            // if text is not a function we assume it to be a key name
-            if (!$.isFunction(text)) {
-                dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
-                text = function (item) { return item[dataText]; };
-            }
-        }
-
-        return function (query) {
-            var t = query.term, filtered = { results: [] }, process;
-            if (t === "") {
-                query.callback(data());
-                return;
-            }
-
-            process = function(datum, collection) {
-                var group, attr;
-                datum = datum[0];
-                if (datum.children) {
-                    group = {};
-                    for (attr in datum) {
-                        if (datum.hasOwnProperty(attr)) group[attr]=datum[attr];
-                    }
-                    group.children=[];
-                    $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); });
-                    if (group.children.length || query.matcher(t, text(group), datum)) {
-                        collection.push(group);
-                    }
-                } else {
-                    if (query.matcher(t, text(datum), datum)) {
-                        collection.push(datum);
-                    }
-                }
-            };
-
-            $(data().results).each2(function(i, datum) { process(datum, filtered.results); });
-            query.callback(filtered);
-        };
-    }
-
-    // TODO javadoc
-    function tags(data) {
-        var isFunc = $.isFunction(data);
-        return function (query) {
-            var t = query.term, filtered = {results: []};
-            var result = isFunc ? data(query) : data;
-            if ($.isArray(result)) {
-                $(result).each(function () {
-                    var isObject = this.text !== undefined,
-                        text = isObject ? this.text : this;
-                    if (t === "" || query.matcher(t, text)) {
-                        filtered.results.push(isObject ? this : {id: this, text: this});
-                    }
-                });
-                query.callback(filtered);
-            }
-        };
-    }
-
-    /**
-     * Checks if the formatter function should be used.
-     *
-     * Throws an error if it is not a function. Returns true if it should be used,
-     * false if no formatting should be performed.
-     *
-     * @param formatter
-     */
-    function checkFormatter(formatter, formatterName) {
-        if ($.isFunction(formatter)) return true;
-        if (!formatter) return false;
-        if (typeof(formatter) === 'string') return true;
-        throw new Error(formatterName +" must be a string, function, or falsy value");
-    }
-
-  /**
-   * Returns a given value
-   * If given a function, returns its output
-   *
-   * @param val string|function
-   * @param context value of "this" to be passed to function
-   * @returns {*}
-   */
-    function evaluate(val, context) {
-        if ($.isFunction(val)) {
-            var args = Array.prototype.slice.call(arguments, 2);
-            return val.apply(context, args);
-        }
-        return val;
-    }
-
-    function countResults(results) {
-        var count = 0;
-        $.each(results, function(i, item) {
-            if (item.children) {
-                count += countResults(item.children);
-            } else {
-                count++;
-            }
-        });
-        return count;
-    }
-
-    /**
-     * Default tokenizer. This function uses breaks the input on substring match of any string from the
-     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
-     * two options have to be defined in order for the tokenizer to work.
-     *
-     * @param input text user has typed so far or pasted into the search field
-     * @param selection currently selected choices
-     * @param selectCallback function(choice) callback tho add the choice to selection
-     * @param opts select2's opts
-     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
-     */
-    function defaultTokenizer(input, selection, selectCallback, opts) {
-        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
-            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
-            token, // token
-            index, // position at which the separator was found
-            i, l, // looping variables
-            separator; // the matched separator
-
-        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
-
-        while (true) {
-            index = -1;
-
-            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
-                separator = opts.tokenSeparators[i];
-                index = input.indexOf(separator);
-                if (index >= 0) break;
-            }
-
-            if (index < 0) break; // did not find any token separator in the input string, bail
-
-            token = input.substring(0, index);
-            input = input.substring(index + separator.length);
-
-            if (token.length > 0) {
-                token = opts.createSearchChoice.call(this, token, selection);
-                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
-                    dupe = false;
-                    for (i = 0, l = selection.length; i < l; i++) {
-                        if (equal(opts.id(token), opts.id(selection[i]))) {
-                            dupe = true; break;
-                        }
-                    }
-
-                    if (!dupe) selectCallback(token);
-                }
-            }
-        }
-
-        if (original!==input) return input;
-    }
-
-    function cleanupJQueryElements() {
-        var self = this;
-
-        $.each(arguments, function (i, element) {
-            self[element].remove();
-            self[element] = null;
-        });
-    }
-
-    /**
-     * Creates a new class
-     *
-     * @param superClass
-     * @param methods
-     */
-    function clazz(SuperClass, methods) {
-        var constructor = function () {};
-        constructor.prototype = new SuperClass;
-        constructor.prototype.constructor = constructor;
-        constructor.prototype.parent = SuperClass.prototype;
-        constructor.prototype = $.extend(constructor.prototype, methods);
-        return constructor;
-    }
-
-    AbstractSelect2 = clazz(Object, {
-
-        // abstract
-        bind: function (func) {
-            var self = this;
-            return function () {
-                func.apply(self, arguments);
-            };
-        },
-
-        // abstract
-        init: function (opts) {
-            var results, search, resultsSelector = ".select2-results";
-
-            // prepare options
-            this.opts = opts = this.prepareOpts(opts);
-
-            this.id=opts.id;
-
-            // destroy if called on an existing component
-            if (opts.element.data("select2") !== undefined &&
-                opts.element.data("select2") !== null) {
-                opts.element.data("select2").destroy();
-            }
-
-            this.container = this.createContainer();
-
-            this.liveRegion = $("<span>", {
-                    role: "status",
-                    "aria-live": "polite"
-                })
-                .addClass("select2-hidden-accessible")
-                .appendTo(document.body);
-
-            this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
-            this.containerEventName= this.containerId
-                .replace(/([.])/g, '_')
-                .replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1');
-            this.container.attr("id", this.containerId);
-
-            this.container.attr("title", opts.element.attr("title"));
-
-            this.body = $("body");
-
-            syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
-
-            this.container.attr("style", opts.element.attr("style"));
-            this.container.css(evaluate(opts.containerCss, this.opts.element));
-            this.container.addClass(evaluate(opts.containerCssClass, this.opts.element));
-
-            this.elementTabIndex = this.opts.element.attr("tabindex");
-
-            // swap container for the element
-            this.opts.element
-                .data("select2", this)
-                .attr("tabindex", "-1")
-                .before(this.container)
-                .on("click.select2", killEvent); // do not leak click events
-
-            this.container.data("select2", this);
-
-            this.dropdown = this.container.find(".select2-drop");
-
-            syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
-
-            this.dropdown.addClass(evaluate(opts.dropdownCssClass, this.opts.element));
-            this.dropdown.data("select2", this);
-            this.dropdown.on("click", killEvent);
-
-            this.results = results = this.container.find(resultsSelector);
-            this.search = search = this.container.find("input.select2-input");
-
-            this.queryCount = 0;
-            this.resultsPage = 0;
-            this.context = null;
-
-            // initialize the container
-            this.initContainer();
-
-            this.container.on("click", killEvent);
-
-            installFilteredMouseMove(this.results);
-
-            this.dropdown.on("mousemove-filtered", resultsSelector, this.bind(this.highlightUnderEvent));
-            this.dropdown.on("touchstart touchmove touchend", resultsSelector, this.bind(function (event) {
-                this._touchEvent = true;
-                this.highlightUnderEvent(event);
-            }));
-            this.dropdown.on("touchmove", resultsSelector, this.bind(this.touchMoved));
-            this.dropdown.on("touchstart touchend", resultsSelector, this.bind(this.clearTouchMoved));
-
-            // Waiting for a click event on touch devices to select option and hide dropdown
-            // otherwise click will be triggered on an underlying element
-            this.dropdown.on('click', this.bind(function (event) {
-                if (this._touchEvent) {
-                    this._touchEvent = false;
-                    this.selectHighlighted();
-                }
-            }));
-
-            installDebouncedScroll(80, this.results);
-            this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded));
-
-            // do not propagate change event from the search field out of the component
-            $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();});
-            $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();});
-
-            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
-            if ($.fn.mousewheel) {
-                results.mousewheel(function (e, delta, deltaX, deltaY) {
-                    var top = results.scrollTop();
-                    if (deltaY > 0 && top - deltaY <= 0) {
-                        results.scrollTop(0);
-                        killEvent(e);
-                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
-                        results.scrollTop(results.get(0).scrollHeight - results.height());
-                        killEvent(e);
-                    }
-                });
-            }
-
-            installKeyUpChangeEvent(search);
-            search.on("keyup-change input paste", this.bind(this.updateResults));
-            search.on("focus", function () { search.addClass("select2-focused"); });
-            search.on("blur", function () { search.removeClass("select2-focused");});
-
-            this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) {
-                if ($(e.target).closest(".select2-result-selectable").length > 0) {
-                    this.highlightUnderEvent(e);
-                    this.selectHighlighted(e);
-                }
-            }));
-
-            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
-            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's
-            // dom it will trigger the popup close, which is not what we want
-            // focusin can cause focus wars between modals and select2 since the dropdown is outside the modal.
-            this.dropdown.on("click mouseup mousedown touchstart touchend focusin", function (e) { e.stopPropagation(); });
-
-            this.nextSearchTerm = undefined;
-
-            if ($.isFunction(this.opts.initSelection)) {
-                // initialize selection based on the current value of the source element
-                this.initSelection();
-
-                // if the user has provided a function that can set selection based on the value of the source element
-                // we monitor the change event on the element and trigger it, allowing for two way synchronization
-                this.monitorSource();
-            }
-
-            if (opts.maximumInputLength !== null) {
-                this.search.attr("maxlength", opts.maximumInputLength);
-            }
-
-            var disabled = opts.element.prop("disabled");
-            if (disabled === undefined) disabled = false;
-            this.enable(!disabled);
-
-            var readonly = opts.element.prop("readonly");
-            if (readonly === undefined) readonly = false;
-            this.readonly(readonly);
-
-            // Calculate size of scrollbar
-            scrollBarDimensions = scrollBarDimensions || measureScrollbar();
-
-            this.autofocus = opts.element.prop("autofocus");
-            opts.element.prop("autofocus", false);
-            if (this.autofocus) this.focus();
-
-            this.search.attr("placeholder", opts.searchInputPlaceholder);
-        },
-
-        // abstract
-        destroy: function () {
-            var element=this.opts.element, select2 = element.data("select2");
-
-            this.close();
-
-            if (element.length && element[0].detachEvent) {
-                element.each(function () {
-                    this.detachEvent("onpropertychange", this._sync);
-                });
-            }
-            if (this.propertyObserver) {
-                this.propertyObserver.disconnect();
-                this.propertyObserver = null;
-            }
-            this._sync = null;
-
-            if (select2 !== undefined) {
-                select2.container.remove();
-                select2.liveRegion.remove();
-                select2.dropdown.remove();
-                element
-                    .removeClass("select2-offscreen")
-                    .removeData("select2")
-                    .off(".select2")
-                    .prop("autofocus", this.autofocus || false);
-                if (this.elementTabIndex) {
-                    element.attr({tabindex: this.elementTabIndex});
-                } else {
-                    element.removeAttr("tabindex");
-                }
-                element.show();
-            }
-
-            cleanupJQueryElements.call(this,
-                "container",
-                "liveRegion",
-                "dropdown",
-                "results",
-                "search"
-            );
-        },
-
-        // abstract
-        optionToData: function(element) {
-            if (element.is("option")) {
-                return {
-                    id:element.prop("value"),
-                    text:element.text(),
-                    element: element.get(),
-                    css: element.attr("class"),
-                    disabled: element.prop("disabled"),
-                    locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true)
-                };
-            } else if (element.is("optgroup")) {
-                return {
-                    text:element.attr("label"),
-                    children:[],
-                    element: element.get(),
-                    css: element.attr("class")
-                };
-            }
-        },
-
-        // abstract
-        prepareOpts: function (opts) {
-            var element, select, idKey, ajaxUrl, self = this;
-
-            element = opts.element;
-
-            if (element.get(0).tagName.toLowerCase() === "select") {
-                this.select = select = opts.element;
-            }
-
-            if (select) {
-                // these options are not allowed when attached to a select because they are picked up off the element itself
-                $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
-                    if (this in opts) {
-                        throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element.");
-                    }
-                });
-            }
-
-            opts = $.extend({}, {
-                populateResults: function(container, results, query) {
-                    var populate, id=this.opts.id, liveRegion=this.liveRegion;
-
-                    populate=function(results, container, depth) {
-
-                        var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
-
-                        results = opts.sortResults(results, container, query);
-
-                        // collect the created nodes for bulk append
-                        var nodes = [];
-                        for (i = 0, l = results.length; i < l; i = i + 1) {
-
-                            result=results[i];
-
-                            disabled = (result.disabled === true);
-                            selectable = (!disabled) && (id(result) !== undefined);
-
-                            compound=result.children && result.children.length > 0;
-
-                            node=$("<li></li>");
-                            node.addClass("select2-results-dept-"+depth);
-                            node.addClass("select2-result");
-                            node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
-                            if (disabled) { node.addClass("select2-disabled"); }
-                            if (compound) { node.addClass("select2-result-with-children"); }
-                            node.addClass(self.opts.formatResultCssClass(result));
-                            node.attr("role", "presentation");
-
-                            label=$(document.createElement("div"));
-                            label.addClass("select2-result-label");
-                            label.attr("id", "select2-result-label-" + nextUid());
-                            label.attr("role", "option");
-
-                            formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup);
-                            if (formatted!==undefined) {
-                                label.html(formatted);
-                                node.append(label);
-                            }
-
-
-                            if (compound) {
-
-                                innerContainer=$("<ul></ul>");
-                                innerContainer.addClass("select2-result-sub");
-                                populate(result.children, innerContainer, depth+1);
-                                node.append(innerContainer);
-                            }
-
-                            node.data("select2-data", result);
-                            nodes.push(node[0]);
-                        }
-
-                        // bulk append the created nodes
-                        container.append(nodes);
-                        liveRegion.text(opts.formatMatches(results.length));
-                    };
-
-                    populate(results, container, 0);
-                }
-            }, $.fn.select2.defaults, opts);
-
-            if (typeof(opts.id) !== "function") {
-                idKey = opts.id;
-                opts.id = function (e) { return e[idKey]; };
-            }
-
-            if ($.isArray(opts.element.data("select2Tags"))) {
-                if ("tags" in opts) {
-                    throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id");
-                }
-                opts.tags=opts.element.data("select2Tags");
-            }
-
-            if (select) {
-                opts.query = this.bind(function (query) {
-                    var data = { results: [], more: false },
-                        term = query.term,
-                        children, placeholderOption, process;
-
-                    process=function(element, collection) {
-                        var group;
-                        if (element.is("option")) {
-                            if (query.matcher(term, element.text(), element)) {
-                                collection.push(self.optionToData(element));
-                            }
-                        } else if (element.is("optgroup")) {
-                            group=self.optionToData(element);
-                            element.children().each2(function(i, elm) { process(elm, group.children); });
-                            if (group.children.length>0) {
-                                collection.push(group);
-                            }
-                        }
-                    };
-
-                    children=element.children();
-
-                    // ignore the placeholder option if there is one
-                    if (this.getPlaceholder() !== undefined && children.length > 0) {
-                        placeholderOption = this.getPlaceholderOption();
-                        if (placeholderOption) {
-                            children=children.not(placeholderOption);
-                        }
-                    }
-
-                    children.each2(function(i, elm) { process(elm, data.results); });
-
-                    query.callback(data);
-                });
-                // this is needed because inside val() we construct choices from options and there id is hardcoded
-                opts.id=function(e) { return e.id; };
-            } else {
-                if (!("query" in opts)) {
-
-                    if ("ajax" in opts) {
-                        ajaxUrl = opts.element.data("ajax-url");
-                        if (ajaxUrl && ajaxUrl.length > 0) {
-                            opts.ajax.url = ajaxUrl;
-                        }
-                        opts.query = ajax.call(opts.element, opts.ajax);
-                    } else if ("data" in opts) {
-                        opts.query = local(opts.data);
-                    } else if ("tags" in opts) {
-                        opts.query = tags(opts.tags);
-                        if (opts.createSearchChoice === undefined) {
-                            opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; };
-                        }
-                        if (opts.initSelection === undefined) {
-                            opts.initSelection = function (element, callback) {
-                                var data = [];
-                                $(splitVal(element.val(), opts.separator)).each(function () {
-                                    var obj = { id: this, text: this },
-                                        tags = opts.tags;
-                                    if ($.isFunction(tags)) tags=tags();
-                                    $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } });
-                                    data.push(obj);
-                                });
-
-                                callback(data);
-                            };
-                        }
-                    }
-                }
-            }
-            if (typeof(opts.query) !== "function") {
-                throw "query function not defined for Select2 " + opts.element.attr("id");
-            }
-
-            if (opts.createSearchChoicePosition === 'top') {
-                opts.createSearchChoicePosition = function(list, item) { list.unshift(item); };
-            }
-            else if (opts.createSearchChoicePosition === 'bottom') {
-                opts.createSearchChoicePosition = function(list, item) { list.push(item); };
-            }
-            else if (typeof(opts.createSearchChoicePosition) !== "function")  {
-                throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function";
-            }
-
-            return opts;
-        },
-
-        /**
-         * Monitor the original element for changes and update select2 accordingly
-         */
-        // abstract
-        monitorSource: function () {
-            var el = this.opts.element, observer, self = this;
-
-            el.on("change.select2", this.bind(function (e) {
-                if (this.opts.element.data("select2-change-triggered") !== true) {
-                    this.initSelection();
-                }
-            }));
-
-            this._sync = this.bind(function () {
-
-                // sync enabled state
-                var disabled = el.prop("disabled");
-                if (disabled === undefined) disabled = false;
-                this.enable(!disabled);
-
-                var readonly = el.prop("readonly");
-                if (readonly === undefined) readonly = false;
-                this.readonly(readonly);
-
-                syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass);
-                this.container.addClass(evaluate(this.opts.containerCssClass, this.opts.element));
-
-                syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass);
-                this.dropdown.addClass(evaluate(this.opts.dropdownCssClass, this.opts.element));
-
-            });
-
-            // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener)
-            if (el.length && el[0].attachEvent) {
-                el.each(function() {
-                    this.attachEvent("onpropertychange", self._sync);
-                });
-            }
-
-            // safari, chrome, firefox, IE11
-            observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver;
-            if (observer !== undefined) {
-                if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; }
-                this.propertyObserver = new observer(function (mutations) {
-                    $.each(mutations, self._sync);
-                });
-                this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false });
-            }
-        },
-
-        // abstract
-        triggerSelect: function(data) {
-            var evt = $.Event("select2-selecting", { val: this.id(data), object: data, choice: data });
-            this.opts.element.trigger(evt);
-            return !evt.isDefaultPrevented();
-        },
-
-        /**
-         * Triggers the change event on the source element
-         */
-        // abstract
-        triggerChange: function (details) {
-
-            details = details || {};
-            details= $.extend({}, details, { type: "change", val: this.val() });
-            // prevents recursive triggering
-            this.opts.element.data("select2-change-triggered", true);
-            this.opts.element.trigger(details);
-            this.opts.element.data("select2-change-triggered", false);
-
-            // some validation frameworks ignore the change event and listen instead to keyup, click for selects
-            // so here we trigger the click event manually
-            this.opts.element.click();
-
-            // ValidationEngine ignores the change event and listens instead to blur
-            // so here we trigger the blur event manually if so desired
-            if (this.opts.blurOnChange)
-                this.opts.element.blur();
-        },
-
-        //abstract
-        isInterfaceEnabled: function()
-        {
-            return this.enabledInterface === true;
-        },
-
-        // abstract
-        enableInterface: function() {
-            var enabled = this._enabled && !this._readonly,
-                disabled = !enabled;
-
-            if (enabled === this.enabledInterface) return false;
-
-            this.container.toggleClass("select2-container-disabled", disabled);
-            this.close();
-            this.enabledInterface = enabled;
-
-            return true;
-        },
-
-        // abstract
-        enable: function(enabled) {
-            if (enabled === undefined) enabled = true;
-            if (this._enabled === enabled) return;
-            this._enabled = enabled;
-
-            this.opts.element.prop("disabled", !enabled);
-            this.enableInterface();
-        },
-
-        // abstract
-        disable: function() {
-            this.enable(false);
-        },
-
-        // abstract
-        readonly: function(enabled) {
-            if (enabled === undefined) enabled = false;
-            if (this._readonly === enabled) return;
-            this._readonly = enabled;
-
-            this.opts.element.prop("readonly", enabled);
-            this.enableInterface();
-        },
-
-        // abstract
-        opened: function () {
-            return (this.container) ? this.container.hasClass("select2-dropdown-open") : false;
-        },
-
-        // abstract
-        positionDropdown: function() {
-            var $dropdown = this.dropdown,
-                offset = this.container.offset(),
-                height = this.container.outerHeight(false),
-                width = this.container.outerWidth(false),
-                dropHeight = $dropdown.outerHeight(false),
-                $window = $(window),
-                windowWidth = $window.width(),
-                windowHeight = $window.height(),
-                viewPortRight = $window.scrollLeft() + windowWidth,
-                viewportBottom = $window.scrollTop() + windowHeight,
-                dropTop = offset.top + height,
-                dropLeft = offset.left,
-                enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
-                enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(),
-                dropWidth = $dropdown.outerWidth(false),
-                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight,
-                aboveNow = $dropdown.hasClass("select2-drop-above"),
-                bodyOffset,
-                above,
-                changeDirection,
-                css,
-                resultsListNode;
-
-            // always prefer the current above/below alignment, unless there is not enough room
-            if (aboveNow) {
-                above = true;
-                if (!enoughRoomAbove && enoughRoomBelow) {
-                    changeDirection = true;
-                    above = false;
-                }
-            } else {
-                above = false;
-                if (!enoughRoomBelow && enoughRoomAbove) {
-                    changeDirection = true;
-                    above = true;
-                }
-            }
-
-            //if we are changing direction we need to get positions when dropdown is hidden;
-            if (changeDirection) {
-                $dropdown.hide();
-                offset = this.container.offset();
-                height = this.container.outerHeight(false);
-                width = this.container.outerWidth(false);
-                dropHeight = $dropdown.outerHeight(false);
-                viewPortRight = $window.scrollLeft() + windowWidth;
-                viewportBottom = $window.scrollTop() + windowHeight;
-                dropTop = offset.top + height;
-                dropLeft = offset.left;
-                dropWidth = $dropdown.outerWidth(false);
-                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
-                $dropdown.show();
-
-                // fix so the cursor does not move to the left within the search-textbox in IE
-                this.focusSearch();
-            }
-
-            if (this.opts.dropdownAutoWidth) {
-                resultsListNode = $('.select2-results', $dropdown)[0];
-                $dropdown.addClass('select2-drop-auto-width');
-                $dropdown.css('width', '');
-                // Add scrollbar width to dropdown if vertical scrollbar is present
-                dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width);
-                dropWidth > width ? width = dropWidth : dropWidth = width;
-                dropHeight = $dropdown.outerHeight(false);
-                enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight;
-            }
-            else {
-                this.container.removeClass('select2-drop-auto-width');
-            }
-
-            //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
-            //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove);
-
-            // fix positioning when body has an offset and is not position: static
-            if (this.body.css('position') !== 'static') {
-                bodyOffset = this.body.offset();
-                dropTop -= bodyOffset.top;
-                dropLeft -= bodyOffset.left;
-            }
-
-            if (!enoughRoomOnRight) {
-                dropLeft = offset.left + this.container.outerWidth(false) - dropWidth;
-            }
-
-            css =  {
-                left: dropLeft,
-                width: width
-            };
-
-            if (above) {
-                css.top = offset.top - dropHeight;
-                css.bottom = 'auto';
-                this.container.addClass("select2-drop-above");
-                $dropdown.addClass("select2-drop-above");
-            }
-            else {
-                css.top = dropTop;
-                css.bottom = 'auto';
-                this.container.removeClass("select2-drop-above");
-                $dropdown.removeClass("select2-drop-above");
-            }
-            css = $.extend(css, evaluate(this.opts.dropdownCss, this.opts.element));
-
-            $dropdown.css(css);
-        },
-
-        // abstract
-        shouldOpen: function() {
-            var event;
-
-            if (this.opened()) return false;
-
-            if (this._enabled === false || this._readonly === true) return false;
-
-            event = $.Event("select2-opening");
-            this.opts.element.trigger(event);
-            return !event.isDefaultPrevented();
-        },
-
-        // abstract
-        clearDropdownAlignmentPreference: function() {
-            // clear the classes used to figure out the preference of where the dropdown should be opened
-            this.container.removeClass("select2-drop-above");
-            this.dropdown.removeClass("select2-drop-above");
-        },
-
-        /**
-         * Opens the dropdown
-         *
-         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
-         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
-         */
-        // abstract
-        open: function () {
-
-            if (!this.shouldOpen()) return false;
-
-            this.opening();
-
-            // Only bind the document mousemove when the dropdown is visible
-            $document.on("mousemove.select2Event", function (e) {
-                lastMousePosition.x = e.pageX;
-                lastMousePosition.y = e.pageY;
-            });
-
-            return true;
-        },
-
-        /**
-         * Performs the opening of the dropdown
-         */
-        // abstract
-        opening: function() {
-            var cid = this.containerEventName,
-                scroll = "scroll." + cid,
-                resize = "resize."+cid,
-                orient = "orientationchange."+cid,
-                mask;
-
-            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
-
-            this.clearDropdownAlignmentPreference();
-
-            if(this.dropdown[0] !== this.body.children().last()[0]) {
-                this.dropdown.detach().appendTo(this.body);
-            }
-
-            // create the dropdown mask if doesn't already exist
-            mask = $("#select2-drop-mask");
-            if (mask.length == 0) {
-                mask = $(document.createElement("div"));
-                mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask");
-                mask.hide();
-                mask.appendTo(this.body);
-                mask.on("mousedown touchstart click", function (e) {
-                    // Prevent IE from generating a click event on the body
-                    reinsertElement(mask);
-
-                    var dropdown = $("#select2-drop"), self;
-                    if (dropdown.length > 0) {
-                        self=dropdown.data("select2");
-                        if (self.opts.selectOnBlur) {
-                            self.selectHighlighted({noFocus: true});
-                        }
-                        self.close();
-                        e.preventDefault();
-                        e.stopPropagation();
-                    }
-                });
-            }
-
-            // ensure the mask is always right before the dropdown
-            if (this.dropdown.prev()[0] !== mask[0]) {
-                this.dropdown.before(mask);
-            }
-
-            // move the global id to the correct dropdown
-            $("#select2-drop").removeAttr("id");
-            this.dropdown.attr("id", "select2-drop");
-
-            // show the elements
-            mask.show();
-
-            this.positionDropdown();
-            this.dropdown.show();
-            this.positionDropdown();
-
-            this.dropdown.addClass("select2-drop-active");
-
-            // attach listeners to events that can change the position of the container and thus require
-            // the position of the dropdown to be updated as well so it does not come unglued from the container
-            var that = this;
-            this.container.parents().add(window).each(function () {
-                $(this).on(resize+" "+scroll+" "+orient, function (e) {
-                    if (that.opened()) that.positionDropdown();
-                });
-            });
-
-
-        },
-
-        // abstract
-        close: function () {
-            if (!this.opened()) return;
-
-            var cid = this.containerEventName,
-                scroll = "scroll." + cid,
-                resize = "resize."+cid,
-                orient = "orientationchange."+cid;
-
-            // unbind event listeners
-            this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); });
-
-            this.clearDropdownAlignmentPreference();
-
-            $("#select2-drop-mask").hide();
-            this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id
-            this.dropdown.hide();
-            this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");
-            this.results.empty();
-
-            // Now that the dropdown is closed, unbind the global document mousemove event
-            $document.off("mousemove.select2Event");
-
-            this.clearSearch();
-            this.search.removeClass("select2-active");
-            this.opts.element.trigger($.Event("select2-close"));
-        },
-
-        /**
-         * Opens control, sets input value, and updates results.
-         */
-        // abstract
-        externalSearch: function (term) {
-            this.open();
-            this.search.val(term);
-            this.updateResults(false);
-        },
-
-        // abstract
-        clearSearch: function () {
-
-        },
-
-        //abstract
-        getMaximumSelectionSize: function() {
-            return evaluate(this.opts.maximumSelectionSize, this.opts.element);
-        },
-
-        // abstract
-        ensureHighlightVisible: function () {
-            var results = this.results, children, index, child, hb, rb, y, more, topOffset;
-
-            index = this.highlight();
-
-            if (index < 0) return;
-
-            if (index == 0) {
-
-                // if the first element is highlighted scroll all the way to the top,
-                // that way any unselectable headers above it will also be scrolled
-                // into view
-
-                results.scrollTop(0);
-                return;
-            }
-
-            children = this.findHighlightableChoices().find('.select2-result-label');
-
-            child = $(children[index]);
-
-            topOffset = (child.offset() || {}).top || 0;
-
-            hb = topOffset + child.outerHeight(true);
-
-            // if this is the last child lets also make sure select2-more-results is visible
-            if (index === children.length - 1) {
-                more = results.find("li.select2-more-results");
-                if (more.length > 0) {
-                    hb = more.offset().top + more.outerHeight(true);
-                }
-            }
-
-            rb = results.offset().top + results.outerHeight(true);
-            if (hb > rb) {
-                results.scrollTop(results.scrollTop() + (hb - rb));
-            }
-            y = topOffset - results.offset().top;
-
-            // make sure the top of the element is visible
-            if (y < 0 && child.css('display') != 'none' ) {
-                results.scrollTop(results.scrollTop() + y); // y is negative
-            }
-        },
-
-        // abstract
-        findHighlightableChoices: function() {
-            return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)");
-        },
-
-        // abstract
-        moveHighlight: function (delta) {
-            var choices = this.findHighlightableChoices(),
-                index = this.highlight();
-
-            while (index > -1 && index < choices.length) {
-                index += delta;
-                var choice = $(choices[index]);
-                if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
-                    this.highlight(index);
-                    break;
-                }
-            }
-        },
-
-        // abstract
-        highlight: function (index) {
-            var choices = this.findHighlightableChoices(),
-                choice,
-                data;
-
-            if (arguments.length === 0) {
-                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
-            }
-
-            if (index >= choices.length) index = choices.length - 1;
-            if (index < 0) index = 0;
-
-            this.removeHighlight();
-
-            choice = $(choices[index]);
-            choice.addClass("select2-highlighted");
-
-            // ensure assistive technology can determine the active choice
-            this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id"));
-
-            this.ensureHighlightVisible();
-
-            this.liveRegion.text(choice.text());
-
-            data = choice.data("select2-data");
-            if (data) {
-                this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data });
-            }
-        },
-
-        removeHighlight: function() {
-            this.results.find(".select2-highlighted").removeClass("select2-highlighted");
-        },
-
-        touchMoved: function() {
-            this._touchMoved = true;
-        },
-
-        clearTouchMoved: function() {
-          this._touchMoved = false;
-        },
-
-        // abstract
-        countSelectableResults: function() {
-            return this.findHighlightableChoices().length;
-        },
-
-        // abstract
-        highlightUnderEvent: function (event) {
-            var el = $(event.target).closest(".select2-result-selectable");
-            if (el.length > 0 && !el.is(".select2-highlighted")) {
-                var choices = this.findHighlightableChoices();
-                this.highlight(choices.index(el));
-            } else if (el.length == 0) {
-                // if we are over an unselectable item remove all highlights
-                this.removeHighlight();
-            }
-        },
-
-        // abstract
-        loadMoreIfNeeded: function () {
-            var results = this.results,
-                more = results.find("li.select2-more-results"),
-                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
-                page = this.resultsPage + 1,
-                self=this,
-                term=this.search.val(),
-                context=this.context;
-
-            if (more.length === 0) return;
-            below = more.offset().top - results.offset().top - results.height();
-
-            if (below <= this.opts.loadMorePadding) {
-                more.addClass("select2-active");
-                this.opts.query({
-                        element: this.opts.element,
-                        term: term,
-                        page: page,
-                        context: context,
-                        matcher: this.opts.matcher,
-                        callback: this.bind(function (data) {
-
-                    // ignore a response if the select2 has been closed before it was received
-                    if (!self.opened()) return;
-
-
-                    self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context});
-                    self.postprocessResults(data, false, false);
-
-                    if (data.more===true) {
-                        more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, self.opts.element, page+1));
-                        window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
-                    } else {
-                        more.remove();
-                    }
-                    self.positionDropdown();
-                    self.resultsPage = page;
-                    self.context = data.context;
-                    this.opts.element.trigger({ type: "select2-loaded", items: data });
-                })});
-            }
-        },
-
-        /**
-         * Default tokenizer function which does nothing
-         */
-        tokenize: function() {
-
-        },
-
-        /**
-         * @param initial whether or not this is the call to this method right after the dropdown has been opened
-         */
-        // abstract
-        updateResults: function (initial) {
-            var search = this.search,
-                results = this.results,
-                opts = this.opts,
-                data,
-                self = this,
-                input,
-                term = search.val(),
-                lastTerm = $.data(this.container, "select2-last-term"),
-                // sequence number used to drop out-of-order responses
-                queryNumber;
-
-            // prevent duplicate queries against the same term
-            if (initial !== true && lastTerm && equal(term, lastTerm)) return;
-
-            $.data(this.container, "select2-last-term", term);
-
-            // if the search is currently hidden we do not alter the results
-            if (initial !== true && (this.showSearchInput === false || !this.opened())) {
-                return;
-            }
-
-            function postRender() {
-                search.removeClass("select2-active");
-                self.positionDropdown();
-                if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) {
-                    self.liveRegion.text(results.text());
-                }
-                else {
-                    self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length));
-                }
-            }
-
-            function render(html) {
-                results.html(html);
-                postRender();
-            }
-
-            queryNumber = ++this.queryCount;
-
-            var maxSelSize = this.getMaximumSelectionSize();
-            if (maxSelSize >=1) {
-                data = this.data();
-                if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) {
-                    render("<li class='select2-selection-limit'>" + evaluate(opts.formatSelectionTooBig, opts.element, maxSelSize) + "</li>");
-                    return;
-                }
-            }
-
-            if (search.val().length < opts.minimumInputLength) {
-                if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) {
-                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooShort, opts.element, search.val(), opts.minimumInputLength) + "</li>");
-                } else {
-                    render("");
-                }
-                if (initial && this.showSearch) this.showSearch(true);
-                return;
-            }
-
-            if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) {
-                if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) {
-                    render("<li class='select2-no-results'>" + evaluate(opts.formatInputTooLong, opts.element, search.val(), opts.maximumInputLength) + "</li>");
-                } else {
-                    render("");
-                }
-                return;
-            }
-
-            if (opts.formatSearching && this.findHighlightableChoices().length === 0) {
-                render("<li class='select2-searching'>" + evaluate(opts.formatSearching, opts.element) + "</li>");
-            }
-
-            search.addClass("select2-active");
-
-            this.removeHighlight();
-
-            // give the tokenizer a chance to pre-process the input
-            input = this.tokenize();
-            if (input != undefined && input != null) {
-                search.val(input);
-            }
-
-            this.resultsPage = 1;
-
-            opts.query({
-                element: opts.element,
-                    term: search.val(),
-                    page: this.resultsPage,
-                    context: null,
-                    matcher: opts.matcher,
-                    callback: this.bind(function (data) {
-                var def; // default choice
-
-                // ignore old responses
-                if (queryNumber != this.queryCount) {
-                  return;
-                }
-
-                // ignore a response if the select2 has been closed before it was received
-                if (!this.opened()) {
-                    this.search.removeClass("select2-active");
-                    return;
-                }
-
-                // save context, if any
-                this.context = (data.context===undefined) ? null : data.context;
-                // create a default choice and prepend it to the list
-                if (this.opts.createSearchChoice && search.val() !== "") {
-                    def = this.opts.createSearchChoice.call(self, search.val(), data.results);
-                    if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
-                        if ($(data.results).filter(
-                            function () {
-                                return equal(self.id(this), self.id(def));
-                            }).length === 0) {
-                            this.opts.createSearchChoicePosition(data.results, def);
-                        }
-                    }
-                }
-
-                if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) {
-                    render("<li class='select2-no-results'>" + evaluate(opts.formatNoMatches, opts.element, search.val()) + "</li>");
-                    return;
-                }
-
-                results.empty();
-                self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null});
-
-                if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) {
-                    results.append("<li class='select2-more-results'>" + opts.escapeMarkup(evaluate(opts.formatLoadMore, opts.element, this.resultsPage)) + "</li>");
-                    window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
-                }
-
-                this.postprocessResults(data, initial);
-
-                postRender();
-
-                this.opts.element.trigger({ type: "select2-loaded", items: data });
-            })});
-        },
-
-        // abstract
-        cancel: function () {
-            this.close();
-        },
-
-        // abstract
-        blur: function () {
-            // if selectOnBlur == true, select the currently highlighted option
-            if (this.opts.selectOnBlur)
-                this.selectHighlighted({noFocus: true});
-
-            this.close();
-            this.container.removeClass("select2-container-active");
-            // synonymous to .is(':focus'), which is available in jquery >= 1.6
-            if (this.search[0] === document.activeElement) { this.search.blur(); }
-            this.clearSearch();
-            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-        },
-
-        // abstract
-        focusSearch: function () {
-            focus(this.search);
-        },
-
-        // abstract
-        selectHighlighted: function (options) {
-            if (this._touchMoved) {
-              this.clearTouchMoved();
-              return;
-            }
-            var index=this.highlight(),
-                highlighted=this.results.find(".select2-highlighted"),
-                data = highlighted.closest('.select2-result').data("select2-data");
-
-            if (data) {
-                this.highlight(index);
-                this.onSelect(data, options);
-            } else if (options && options.noFocus) {
-                this.close();
-            }
-        },
-
-        // abstract
-        getPlaceholder: function () {
-            var placeholderOption;
-            return this.opts.element.attr("placeholder") ||
-                this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
-                this.opts.element.data("placeholder") ||
-                this.opts.placeholder ||
-                ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined);
-        },
-
-        // abstract
-        getPlaceholderOption: function() {
-            if (this.select) {
-                var firstOption = this.select.children('option').first();
-                if (this.opts.placeholderOption !== undefined ) {
-                    //Determine the placeholder option based on the specified placeholderOption setting
-                    return (this.opts.placeholderOption === "first" && firstOption) ||
-                           (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select));
-                } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") {
-                    //No explicit placeholder option specified, use the first if it's blank
-                    return firstOption;
-                }
-            }
-        },
-
-        /**
-         * Get the desired width for the container element.  This is
-         * derived first from option `width` passed to select2, then
-         * the inline 'style' on the original element, and finally
-         * falls back to the jQuery calculated element width.
-         */
-        // abstract
-        initContainerWidth: function () {
-            function resolveContainerWidth() {
-                var style, attrs, matches, i, l, attr;
-
-                if (this.opts.width === "off") {
-                    return null;
-                } else if (this.opts.width === "element"){
-                    return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px';
-                } else if (this.opts.width === "copy" || this.opts.width === "resolve") {
-                    // check if there is inline style on the element that contains width
-                    style = this.opts.element.attr('style');
-                    if (style !== undefined) {
-                        attrs = style.split(';');
-                        for (i = 0, l = attrs.length; i < l; i = i + 1) {
-                            attr = attrs[i].replace(/\s/g, '');
-                            matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i);
-                            if (matches !== null && matches.length >= 1)
-                                return matches[1];
-                        }
-                    }
-
-                    if (this.opts.width === "resolve") {
-                        // next check if css('width') can resolve a width that is percent based, this is sometimes possible
-                        // when attached to input type=hidden or elements hidden via css
-                        style = this.opts.element.css('width');
-                        if (style.indexOf("%") > 0) return style;
-
-                        // finally, fallback on the calculated width of the element
-                        return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px');
-                    }
-
-                    return null;
-                } else if ($.isFunction(this.opts.width)) {
-                    return this.opts.width();
-                } else {
-                    return this.opts.width;
-               }
-            };
-
-            var width = resolveContainerWidth.call(this);
-            if (width !== null) {
-                this.container.css("width", width);
-            }
-        }
-    });
-
-    SingleSelect2 = clazz(AbstractSelect2, {
-
-        // single
-
-        createContainer: function () {
-            var container = $(document.createElement("div")).attr({
-                "class": "select2-container"
-            }).html([
-                "<a href='javascript:void(0)' class='select2-choice' tabindex='-1'>",
-                "   <span class='select2-chosen'>&#160;</span><abbr class='select2-search-choice-close'></abbr>",
-                "   <span class='select2-arrow' role='presentation'><b role='presentation'></b></span>",
-                "</a>",
-                "<label for='' class='select2-offscreen'></label>",
-                "<input class='select2-focusser select2-offscreen' type='text' aria-haspopup='true' role='button' />",
-                "<div class='select2-drop select2-display-none'>",
-                "   <div class='select2-search'>",
-                "       <label for='' class='select2-offscreen'></label>",
-                "       <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input' role='combobox' aria-expanded='true'",
-                "       aria-autocomplete='list' />",
-                "   </div>",
-                "   <ul class='select2-results' role='listbox'>",
-                "   </ul>",
-                "</div>"].join(""));
-            return container;
-        },
-
-        // single
-        enableInterface: function() {
-            if (this.parent.enableInterface.apply(this, arguments)) {
-                this.focusser.prop("disabled", !this.isInterfaceEnabled());
-            }
-        },
-
-        // single
-        opening: function () {
-            var el, range, len;
-
-            if (this.opts.minimumResultsForSearch >= 0) {
-                this.showSearch(true);
-            }
-
-            this.parent.opening.apply(this, arguments);
-
-            if (this.showSearchInput !== false) {
-                // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range
-                // all other browsers handle this just fine
-
-                this.search.val(this.focusser.val());
-            }
-            if (this.opts.shouldFocusInput(this)) {
-                this.search.focus();
-                // move the cursor to the end after focussing, otherwise it will be at the beginning and
-                // new text will appear *before* focusser.val()
-                el = this.search.get(0);
-                if (el.createTextRange) {
-                    range = el.createTextRange();
-                    range.collapse(false);
-                    range.select();
-                } else if (el.setSelectionRange) {
-                    len = this.search.val().length;
-                    el.setSelectionRange(len, len);
-                }
-            }
-
-            // initializes search's value with nextSearchTerm (if defined by user)
-            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
-            if(this.search.val() === "") {
-                if(this.nextSearchTerm != undefined){
-                    this.search.val(this.nextSearchTerm);
-                    this.search.select();
-                }
-            }
-
-            this.focusser.prop("disabled", true).val("");
-            this.updateResults(true);
-            this.opts.element.trigger($.Event("select2-open"));
-        },
-
-        // single
-        close: function () {
-            if (!this.opened()) return;
-            this.parent.close.apply(this, arguments);
-
-            this.focusser.prop("disabled", false);
-
-            if (this.opts.shouldFocusInput(this)) {
-                this.focusser.focus();
-            }
-        },
-
-        // single
-        focus: function () {
-            if (this.opened()) {
-                this.close();
-            } else {
-                this.focusser.prop("disabled", false);
-                if (this.opts.shouldFocusInput(this)) {
-                    this.focusser.focus();
-                }
-            }
-        },
-
-        // single
-        isFocused: function () {
-            return this.container.hasClass("select2-container-active");
-        },
-
-        // single
-        cancel: function () {
-            this.parent.cancel.apply(this, arguments);
-            this.focusser.prop("disabled", false);
-
-            if (this.opts.shouldFocusInput(this)) {
-                this.focusser.focus();
-            }
-        },
-
-        // single
-        destroy: function() {
-            $("label[for='" + this.focusser.attr('id') + "']")
-                .attr('for', this.opts.element.attr("id"));
-            this.parent.destroy.apply(this, arguments);
-
-            cleanupJQueryElements.call(this,
-                "selection",
-                "focusser"
-            );
-        },
-
-        // single
-        initContainer: function () {
-
-            var selection,
-                container = this.container,
-                dropdown = this.dropdown,
-                idSuffix = nextUid(),
-                elementLabel;
-
-            if (this.opts.minimumResultsForSearch < 0) {
-                this.showSearch(false);
-            } else {
-                this.showSearch(true);
-            }
-
-            this.selection = selection = container.find(".select2-choice");
-
-            this.focusser = container.find(".select2-focusser");
-
-            // add aria associations
-            selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix);
-            this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix);
-            this.results.attr("id", "select2-results-"+idSuffix);
-            this.search.attr("aria-owns", "select2-results-"+idSuffix);
-
-            // rewrite labels from original element to focusser
-            this.focusser.attr("id", "s2id_autogen"+idSuffix);
-
-            elementLabel = $("label[for='" + this.opts.element.attr("id") + "']");
-
-            this.focusser.prev()
-                .text(elementLabel.text())
-                .attr('for', this.focusser.attr('id'));
-
-            // Ensure the original element retains an accessible name
-            var originalTitle = this.opts.element.attr("title");
-            this.opts.element.attr("title", (originalTitle || elementLabel.text()));
-
-            this.focusser.attr("tabindex", this.elementTabIndex);
-
-            // write label for search field using the label from the focusser element
-            this.search.attr("id", this.focusser.attr('id') + '_search');
-
-            this.search.prev()
-                .text($("label[for='" + this.focusser.attr('id') + "']").text())
-                .attr('for', this.search.attr('id'));
-
-            this.search.on("keydown", this.bind(function (e) {
-                if (!this.isInterfaceEnabled()) return;
-
-                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                    // prevent the page from scrolling
-                    killEvent(e);
-                    return;
-                }
-
-                switch (e.which) {
-                    case KEY.UP:
-                    case KEY.DOWN:
-                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                        killEvent(e);
-                        return;
-                    case KEY.ENTER:
-                        this.selectHighlighted();
-                        killEvent(e);
-                        return;
-                    case KEY.TAB:
-                        this.selectHighlighted({noFocus: true});
-                        return;
-                    case KEY.ESC:
-                        this.cancel(e);
-                        killEvent(e);
-                        return;
-                }
-            }));
-
-            this.search.on("blur", this.bind(function(e) {
-                // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
-                // without this the search field loses focus which is annoying
-                if (document.activeElement === this.body.get(0)) {
-                    window.setTimeout(this.bind(function() {
-                        if (this.opened()) {
-                            this.search.focus();
-                        }
-                    }), 0);
-                }
-            }));
-
-            this.focusser.on("keydown", this.bind(function (e) {
-                if (!this.isInterfaceEnabled()) return;
-
-                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
-                    return;
-                }
-
-                if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
-                    killEvent(e);
-                    return;
-                }
-
-                if (e.which == KEY.DOWN || e.which == KEY.UP
-                    || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
-
-                    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
-
-                    this.open();
-                    killEvent(e);
-                    return;
-                }
-
-                if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
-                    if (this.opts.allowClear) {
-                        this.clear();
-                    }
-                    killEvent(e);
-                    return;
-                }
-            }));
-
-
-            installKeyUpChangeEvent(this.focusser);
-            this.focusser.on("keyup-change input", this.bind(function(e) {
-                if (this.opts.minimumResultsForSearch >= 0) {
-                    e.stopPropagation();
-                    if (this.opened()) return;
-                    this.open();
-                }
-            }));
-
-            selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
-                if (!this.isInterfaceEnabled()) return;
-                this.clear();
-                killEventImmediately(e);
-                this.close();
-                this.selection.focus();
-            }));
-
-            selection.on("mousedown touchstart", this.bind(function (e) {
-                // Prevent IE from generating a click event on the body
-                reinsertElement(selection);
-
-                if (!this.container.hasClass("select2-container-active")) {
-                    this.opts.element.trigger($.Event("select2-focus"));
-                }
-
-                if (this.opened()) {
-                    this.close();
-                } else if (this.isInterfaceEnabled()) {
-                    this.open();
-                }
-
-                killEvent(e);
-            }));
-
-            dropdown.on("mousedown touchstart", this.bind(function() {
-                if (this.opts.shouldFocusInput(this)) {
-                    this.search.focus();
-                }
-            }));
-
-            selection.on("focus", this.bind(function(e) {
-                killEvent(e);
-            }));
-
-            this.focusser.on("focus", this.bind(function(){
-                if (!this.container.hasClass("select2-container-active")) {
-                    this.opts.element.trigger($.Event("select2-focus"));
-                }
-                this.container.addClass("select2-container-active");
-            })).on("blur", this.bind(function() {
-                if (!this.opened()) {
-                    this.container.removeClass("select2-container-active");
-                    this.opts.element.trigger($.Event("select2-blur"));
-                }
-            }));
-            this.search.on("focus", this.bind(function(){
-                if (!this.container.hasClass("select2-container-active")) {
-                    this.opts.element.trigger($.Event("select2-focus"));
-                }
-                this.container.addClass("select2-container-active");
-            }));
-
-            this.initContainerWidth();
-            this.opts.element.addClass("select2-offscreen");
-            this.setPlaceholder();
-
-        },
-
-        // single
-        clear: function(triggerChange) {
-            var data=this.selection.data("select2-data");
-            if (data) { // guard against queued quick consecutive clicks
-                var evt = $.Event("select2-clearing");
-                this.opts.element.trigger(evt);
-                if (evt.isDefaultPrevented()) {
-                    return;
-                }
-                var placeholderOption = this.getPlaceholderOption();
-                this.opts.element.val(placeholderOption ? placeholderOption.val() : "");
-                this.selection.find(".select2-chosen").empty();
-                this.selection.removeData("select2-data");
-                this.setPlaceholder();
-
-                if (triggerChange !== false){
-                    this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
-                    this.triggerChange({removed:data});
-                }
-            }
-        },
-
-        /**
-         * Sets selection based on source element's value
-         */
-        // single
-        initSelection: function () {
-            var selected;
-            if (this.isPlaceholderOptionSelected()) {
-                this.updateSelection(null);
-                this.close();
-                this.setPlaceholder();
-            } else {
-                var self = this;
-                this.opts.initSelection.call(null, this.opts.element, function(selected){
-                    if (selected !== undefined && selected !== null) {
-                        self.updateSelection(selected);
-                        self.close();
-                        self.setPlaceholder();
-                        self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val());
-                    }
-                });
-            }
-        },
-
-        isPlaceholderOptionSelected: function() {
-            var placeholderOption;
-            if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered
-            return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected"))
-                || (this.opts.element.val() === "")
-                || (this.opts.element.val() === undefined)
-                || (this.opts.element.val() === null);
-        },
-
-        // single
-        prepareOpts: function () {
-            var opts = this.parent.prepareOpts.apply(this, arguments),
-                self=this;
-
-            if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                // install the selection initializer
-                opts.initSelection = function (element, callback) {
-                    var selected = element.find("option").filter(function() { return this.selected && !this.disabled });
-                    // a single select box always has a value, no need to null check 'selected'
-                    callback(self.optionToData(selected));
-                };
-            } else if ("data" in opts) {
-                // install default initSelection when applied to hidden input and data is local
-                opts.initSelection = opts.initSelection || function (element, callback) {
-                    var id = element.val();
-                    //search in data by id, storing the actual matching item
-                    var match = null;
-                    opts.query({
-                        matcher: function(term, text, el){
-                            var is_match = equal(id, opts.id(el));
-                            if (is_match) {
-                                match = el;
-                            }
-                            return is_match;
-                        },
-                        callback: !$.isFunction(callback) ? $.noop : function() {
-                            callback(match);
-                        }
-                    });
-                };
-            }
-
-            return opts;
-        },
-
-        // single
-        getPlaceholder: function() {
-            // if a placeholder is specified on a single select without a valid placeholder option ignore it
-            if (this.select) {
-                if (this.getPlaceholderOption() === undefined) {
-                    return undefined;
-                }
-            }
-
-            return this.parent.getPlaceholder.apply(this, arguments);
-        },
-
-        // single
-        setPlaceholder: function () {
-            var placeholder = this.getPlaceholder();
-
-            if (this.isPlaceholderOptionSelected() && placeholder !== undefined) {
-
-                // check for a placeholder option if attached to a select
-                if (this.select && this.getPlaceholderOption() === undefined) return;
-
-                this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder));
-
-                this.selection.addClass("select2-default");
-
-                this.container.removeClass("select2-allowclear");
-            }
-        },
-
-        // single
-        postprocessResults: function (data, initial, noHighlightUpdate) {
-            var selected = 0, self = this, showSearchInput = true;
-
-            // find the selected element in the result list
-
-            this.findHighlightableChoices().each2(function (i, elm) {
-                if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) {
-                    selected = i;
-                    return false;
-                }
-            });
-
-            // and highlight it
-            if (noHighlightUpdate !== false) {
-                if (initial === true && selected >= 0) {
-                    this.highlight(selected);
-                } else {
-                    this.highlight(0);
-                }
-            }
-
-            // hide the search box if this is the first we got the results and there are enough of them for search
-
-            if (initial === true) {
-                var min = this.opts.minimumResultsForSearch;
-                if (min >= 0) {
-                    this.showSearch(countResults(data.results) >= min);
-                }
-            }
-        },
-
-        // single
-        showSearch: function(showSearchInput) {
-            if (this.showSearchInput === showSearchInput) return;
-
-            this.showSearchInput = showSearchInput;
-
-            this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput);
-            this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput);
-            //add "select2-with-searchbox" to the container if search box is shown
-            $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput);
-        },
-
-        // single
-        onSelect: function (data, options) {
-
-            if (!this.triggerSelect(data)) { return; }
-
-            var old = this.opts.element.val(),
-                oldData = this.data();
-
-            this.opts.element.val(this.id(data));
-            this.updateSelection(data);
-
-            this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data });
-
-            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
-            this.close();
-
-            if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) {
-                this.focusser.focus();
-            }
-
-            if (!equal(old, this.id(data))) {
-                this.triggerChange({ added: data, removed: oldData });
-            }
-        },
-
-        // single
-        updateSelection: function (data) {
-
-            var container=this.selection.find(".select2-chosen"), formatted, cssClass;
-
-            this.selection.data("select2-data", data);
-
-            container.empty();
-            if (data !== null) {
-                formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup);
-            }
-            if (formatted !== undefined) {
-                container.append(formatted);
-            }
-            cssClass=this.opts.formatSelectionCssClass(data, container);
-            if (cssClass !== undefined) {
-                container.addClass(cssClass);
-            }
-
-            this.selection.removeClass("select2-default");
-
-            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
-                this.container.addClass("select2-allowclear");
-            }
-        },
-
-        // single
-        val: function () {
-            var val,
-                triggerChange = false,
-                data = null,
-                self = this,
-                oldData = this.data();
-
-            if (arguments.length === 0) {
-                return this.opts.element.val();
-            }
-
-            val = arguments[0];
-
-            if (arguments.length > 1) {
-                triggerChange = arguments[1];
-            }
-
-            if (this.select) {
-                this.select
-                    .val(val)
-                    .find("option").filter(function() { return this.selected }).each2(function (i, elm) {
-                        data = self.optionToData(elm);
-                        return false;
-                    });
-                this.updateSelection(data);
-                this.setPlaceholder();
-                if (triggerChange) {
-                    this.triggerChange({added: data, removed:oldData});
-                }
-            } else {
-                // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
-                if (!val && val !== 0) {
-                    this.clear(triggerChange);
-                    return;
-                }
-                if (this.opts.initSelection === undefined) {
-                    throw new Error("cannot call val() if initSelection() is not defined");
-                }
-                this.opts.element.val(val);
-                this.opts.initSelection(this.opts.element, function(data){
-                    self.opts.element.val(!data ? "" : self.id(data));
-                    self.updateSelection(data);
-                    self.setPlaceholder();
-                    if (triggerChange) {
-                        self.triggerChange({added: data, removed:oldData});
-                    }
-                });
-            }
-        },
-
-        // single
-        clearSearch: function () {
-            this.search.val("");
-            this.focusser.val("");
-        },
-
-        // single
-        data: function(value) {
-            var data,
-                triggerChange = false;
-
-            if (arguments.length === 0) {
-                data = this.selection.data("select2-data");
-                if (data == undefined) data = null;
-                return data;
-            } else {
-                if (arguments.length > 1) {
-                    triggerChange = arguments[1];
-                }
-                if (!value) {
-                    this.clear(triggerChange);
-                } else {
-                    data = this.data();
-                    this.opts.element.val(!value ? "" : this.id(value));
-                    this.updateSelection(value);
-                    if (triggerChange) {
-                        this.triggerChange({added: value, removed:data});
-                    }
-                }
-            }
-        }
-    });
-
-    MultiSelect2 = clazz(AbstractSelect2, {
-
-        // multi
-        createContainer: function () {
-            var container = $(document.createElement("div")).attr({
-                "class": "select2-container select2-container-multi"
-            }).html([
-                "<ul class='select2-choices'>",
-                "  <li class='select2-search-field'>",
-                "    <label for='' class='select2-offscreen'></label>",
-                "    <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>",
-                "  </li>",
-                "</ul>",
-                "<div class='select2-drop select2-drop-multi select2-display-none'>",
-                "   <ul class='select2-results'>",
-                "   </ul>",
-                "</div>"].join(""));
-            return container;
-        },
-
-        // multi
-        prepareOpts: function () {
-            var opts = this.parent.prepareOpts.apply(this, arguments),
-                self=this;
-
-            // TODO validate placeholder is a string if specified
-
-            if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                // install the selection initializer
-                opts.initSelection = function (element, callback) {
-
-                    var data = [];
-
-                    element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) {
-                        data.push(self.optionToData(elm));
-                    });
-                    callback(data);
-                };
-            } else if ("data" in opts) {
-                // install default initSelection when applied to hidden input and data is local
-                opts.initSelection = opts.initSelection || function (element, callback) {
-                    var ids = splitVal(element.val(), opts.separator);
-                    //search in data by array of ids, storing matching items in a list
-                    var matches = [];
-                    opts.query({
-                        matcher: function(term, text, el){
-                            var is_match = $.grep(ids, function(id) {
-                                return equal(id, opts.id(el));
-                            }).length;
-                            if (is_match) {
-                                matches.push(el);
-                            }
-                            return is_match;
-                        },
-                        callback: !$.isFunction(callback) ? $.noop : function() {
-                            // reorder matches based on the order they appear in the ids array because right now
-                            // they are in the order in which they appear in data array
-                            var ordered = [];
-                            for (var i = 0; i < ids.length; i++) {
-                                var id = ids[i];
-                                for (var j = 0; j < matches.length; j++) {
-                                    var match = matches[j];
-                                    if (equal(id, opts.id(match))) {
-                                        ordered.push(match);
-                                        matches.splice(j, 1);
-                                        break;
-                                    }
-                                }
-                            }
-                            callback(ordered);
-                        }
-                    });
-                };
-            }
-
-            return opts;
-        },
-
-        // multi
-        selectChoice: function (choice) {
-
-            var selected = this.container.find(".select2-search-choice-focus");
-            if (selected.length && choice && choice[0] == selected[0]) {
-
-            } else {
-                if (selected.length) {
-                    this.opts.element.trigger("choice-deselected", selected);
-                }
-                selected.removeClass("select2-search-choice-focus");
-                if (choice && choice.length) {
-                    this.close();
-                    choice.addClass("select2-search-choice-focus");
-                    this.opts.element.trigger("choice-selected", choice);
-                }
-            }
-        },
-
-        // multi
-        destroy: function() {
-            $("label[for='" + this.search.attr('id') + "']")
-                .attr('for', this.opts.element.attr("id"));
-            this.parent.destroy.apply(this, arguments);
-
-            cleanupJQueryElements.call(this,
-                "searchContainer",
-                "selection"
-            );
-        },
-
-        // multi
-        initContainer: function () {
-
-            var selector = ".select2-choices", selection;
-
-            this.searchContainer = this.container.find(".select2-search-field");
-            this.selection = selection = this.container.find(selector);
-
-            var _this = this;
-            this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) {
-                //killEvent(e);
-                _this.search[0].focus();
-                _this.selectChoice($(this));
-            });
-
-            // rewrite labels from original element to focusser
-            this.search.attr("id", "s2id_autogen"+nextUid());
-
-            this.search.prev()
-                .text($("label[for='" + this.opts.element.attr("id") + "']").text())
-                .attr('for', this.search.attr('id'));
-
-            this.search.on("input paste", this.bind(function() {
-                if (this.search.attr('placeholder') && this.search.val().length == 0) return;
-                if (!this.isInterfaceEnabled()) return;
-                if (!this.opened()) {
-                    this.open();
-                }
-            }));
-
-            this.search.attr("tabindex", this.elementTabIndex);
-
-            this.keydowns = 0;
-            this.search.on("keydown", this.bind(function (e) {
-                if (!this.isInterfaceEnabled()) return;
-
-                ++this.keydowns;
-                var selected = selection.find(".select2-search-choice-focus");
-                var prev = selected.prev(".select2-search-choice:not(.select2-locked)");
-                var next = selected.next(".select2-search-choice:not(.select2-locked)");
-                var pos = getCursorInfo(this.search);
-
-                if (selected.length &&
-                    (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) {
-                    var selectedChoice = selected;
-                    if (e.which == KEY.LEFT && prev.length) {
-                        selectedChoice = prev;
-                    }
-                    else if (e.which == KEY.RIGHT) {
-                        selectedChoice = next.length ? next : null;
-                    }
-                    else if (e.which === KEY.BACKSPACE) {
-                        if (this.unselect(selected.first())) {
-                            this.search.width(10);
-                            selectedChoice = prev.length ? prev : next;
-                        }
-                    } else if (e.which == KEY.DELETE) {
-                        if (this.unselect(selected.first())) {
-                            this.search.width(10);
-                            selectedChoice = next.length ? next : null;
-                        }
-                    } else if (e.which == KEY.ENTER) {
-                        selectedChoice = null;
-                    }
-
-                    this.selectChoice(selectedChoice);
-                    killEvent(e);
-                    if (!selectedChoice || !selectedChoice.length) {
-                        this.open();
-                    }
-                    return;
-                } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1)
-                    || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) {
-
-                    this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last());
-                    killEvent(e);
-                    return;
-                } else {
-                    this.selectChoice(null);
-                }
-
-                if (this.opened()) {
-                    switch (e.which) {
-                    case KEY.UP:
-                    case KEY.DOWN:
-                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                        killEvent(e);
-                        return;
-                    case KEY.ENTER:
-                        this.selectHighlighted();
-                        killEvent(e);
-                        return;
-                    case KEY.TAB:
-                        this.selectHighlighted({noFocus:true});
-                        this.close();
-                        return;
-                    case KEY.ESC:
-                        this.cancel(e);
-                        killEvent(e);
-                        return;
-                    }
-                }
-
-                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
-                 || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
-                    return;
-                }
-
-                if (e.which === KEY.ENTER) {
-                    if (this.opts.openOnEnter === false) {
-                        return;
-                    } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
-                        return;
-                    }
-                }
-
-                this.open();
-
-                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                    // prevent the page from scrolling
-                    killEvent(e);
-                }
-
-                if (e.which === KEY.ENTER) {
-                    // prevent form from being submitted
-                    killEvent(e);
-                }
-
-            }));
-
-            this.search.on("keyup", this.bind(function (e) {
-                this.keydowns = 0;
-                this.resizeSearch();
-            })
-            );
-
-            this.search.on("blur", this.bind(function(e) {
-                this.container.removeClass("select2-container-active");
-                this.search.removeClass("select2-focused");
-                this.selectChoice(null);
-                if (!this.opened()) this.clearSearch();
-                e.stopImmediatePropagation();
-                this.opts.element.trigger($.Event("select2-blur"));
-            }));
-
-            this.container.on("click", selector, this.bind(function (e) {
-                if (!this.isInterfaceEnabled()) return;
-                if ($(e.target).closest(".select2-search-choice").length > 0) {
-                    // clicked inside a select2 search choice, do not open
-                    return;
-                }
-                this.selectChoice(null);
-                this.clearPlaceholder();
-                if (!this.container.hasClass("select2-container-active")) {
-                    this.opts.element.trigger($.Event("select2-focus"));
-                }
-                this.open();
-                this.focusSearch();
-                e.preventDefault();
-            }));
-
-            this.container.on("focus", selector, this.bind(function () {
-                if (!this.isInterfaceEnabled()) return;
-                if (!this.container.hasClass("select2-container-active")) {
-                    this.opts.element.trigger($.Event("select2-focus"));
-                }
-                this.container.addClass("select2-container-active");
-                this.dropdown.addClass("select2-drop-active");
-                this.clearPlaceholder();
-            }));
-
-            this.initContainerWidth();
-            this.opts.element.addClass("select2-offscreen");
-
-            // set the placeholder if necessary
-            this.clearSearch();
-        },
-
-        // multi
-        enableInterface: function() {
-            if (this.parent.enableInterface.apply(this, arguments)) {
-                this.search.prop("disabled", !this.isInterfaceEnabled());
-            }
-        },
-
-        // multi
-        initSelection: function () {
-            var data;
-            if (this.opts.element.val() === "" && this.opts.element.text() === "") {
-                this.updateSelection([]);
-                this.close();
-                // set the placeholder if necessary
-                this.clearSearch();
-            }
-            if (this.select || this.opts.element.val() !== "") {
-                var self = this;
-                this.opts.initSelection.call(null, this.opts.element, function(data){
-                    if (data !== undefined && data !== null) {
-                        self.updateSelection(data);
-                        self.close();
-                        // set the placeholder if necessary
-                        self.clearSearch();
-                    }
-                });
-            }
-        },
-
-        // multi
-        clearSearch: function () {
-            var placeholder = this.getPlaceholder(),
-                maxWidth = this.getMaxSearchWidth();
-
-            if (placeholder !== undefined  && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) {
-                this.search.val(placeholder).addClass("select2-default");
-                // stretch the search box to full width of the container so as much of the placeholder is visible as possible
-                // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944
-                this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width"));
-            } else {
-                this.search.val("").width(10);
-            }
-        },
-
-        // multi
-        clearPlaceholder: function () {
-            if (this.search.hasClass("select2-default")) {
-                this.search.val("").removeClass("select2-default");
-            }
-        },
-
-        // multi
-        opening: function () {
-            this.clearPlaceholder(); // should be done before super so placeholder is not used to search
-            this.resizeSearch();
-
-            this.parent.opening.apply(this, arguments);
-
-            this.focusSearch();
-
-            // initializes search's value with nextSearchTerm (if defined by user)
-            // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter
-            if(this.search.val() === "") {
-                if(this.nextSearchTerm != undefined){
-                    this.search.val(this.nextSearchTerm);
-                    this.search.select();
-                }
-            }
-
-            this.updateResults(true);
-            if (this.opts.shouldFocusInput(this)) {
-                this.search.focus();
-            }
-            this.opts.element.trigger($.Event("select2-open"));
-        },
-
-        // multi
-        close: function () {
-            if (!this.opened()) return;
-            this.parent.close.apply(this, arguments);
-        },
-
-        // multi
-        focus: function () {
-            this.close();
-            this.search.focus();
-        },
-
-        // multi
-        isFocused: function () {
-            return this.search.hasClass("select2-focused");
-        },
-
-        // multi
-        updateSelection: function (data) {
-            var ids = [], filtered = [], self = this;
-
-            // filter out duplicates
-            $(data).each(function () {
-                if (indexOf(self.id(this), ids) < 0) {
-                    ids.push(self.id(this));
-                    filtered.push(this);
-                }
-            });
-            data = filtered;
-
-            this.selection.find(".select2-search-choice").remove();
-            $(data).each(function () {
-                self.addSelectedChoice(this);
-            });
-            self.postprocessResults();
-        },
-
-        // multi
-        tokenize: function() {
-            var input = this.search.val();
-            input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts);
-            if (input != null && input != undefined) {
-                this.search.val(input);
-                if (input.length > 0) {
-                    this.open();
-                }
-            }
-
-        },
-
-        // multi
-        onSelect: function (data, options) {
-
-            if (!this.triggerSelect(data)) { return; }
-
-            this.addSelectedChoice(data);
-
-            this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data });
-
-            // keep track of the search's value before it gets cleared
-            this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val());
-
-            this.clearSearch();
-            this.updateResults();
-
-            if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true);
-
-            if (this.opts.closeOnSelect) {
-                this.close();
-                this.search.width(10);
-            } else {
-                if (this.countSelectableResults()>0) {
-                    this.search.width(10);
-                    this.resizeSearch();
-                    if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) {
-                        // if we reached max selection size repaint the results so choices
-                        // are replaced with the max selection reached message
-                        this.updateResults(true);
-                    } else {
-                        // initializes search's value with nextSearchTerm and update search result
-                        if(this.nextSearchTerm != undefined){
-                            this.search.val(this.nextSearchTerm);
-                            this.updateResults();
-                            this.search.select();
-                        }
-                    }
-                    this.positionDropdown();
-                } else {
-                    // if nothing left to select close
-                    this.close();
-                    this.search.width(10);
-                }
-            }
-
-            // since its not possible to select an element that has already been
-            // added we do not need to check if this is a new element before firing change
-            this.triggerChange({ added: data });
-
-            if (!options || !options.noFocus)
-                this.focusSearch();
-        },
-
-        // multi
-        cancel: function () {
-            this.close();
-            this.focusSearch();
-        },
-
-        addSelectedChoice: function (data) {
-            var enableChoice = !data.locked,
-                enabledItem = $(
-                    "<li class='select2-search-choice'>" +
-                    "    <div></div>" +
-                    "    <a href='#' class='select2-search-choice-close' tabindex='-1'></a>" +
-                    "</li>"),
-                disabledItem = $(
-                    "<li class='select2-search-choice select2-locked'>" +
-                    "<div></div>" +
-                    "</li>");
-            var choice = enableChoice ? enabledItem : disabledItem,
-                id = this.id(data),
-                val = this.getVal(),
-                formatted,
-                cssClass;
-
-            formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup);
-            if (formatted != undefined) {
-                choice.find("div").replaceWith("<div>"+formatted+"</div>");
-            }
-            cssClass=this.opts.formatSelectionCssClass(data, choice.find("div"));
-            if (cssClass != undefined) {
-                choice.addClass(cssClass);
-            }
-
-            if(enableChoice){
-              choice.find(".select2-search-choice-close")
-                  .on("mousedown", killEvent)
-                  .on("click dblclick", this.bind(function (e) {
-                  if (!this.isInterfaceEnabled()) return;
-
-                  this.unselect($(e.target));
-                  this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-                  killEvent(e);
-                  this.close();
-                  this.focusSearch();
-              })).on("focus", this.bind(function () {
-                  if (!this.isInterfaceEnabled()) return;
-                  this.container.addClass("select2-container-active");
-                  this.dropdown.addClass("select2-drop-active");
-              }));
-            }
-
-            choice.data("select2-data", data);
-            choice.insertBefore(this.searchContainer);
-
-            val.push(id);
-            this.setVal(val);
-        },
-
-        // multi
-        unselect: function (selected) {
-            var val = this.getVal(),
-                data,
-                index;
-            selected = selected.closest(".select2-search-choice");
-
-            if (selected.length === 0) {
-                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
-            }
-
-            data = selected.data("select2-data");
-
-            if (!data) {
-                // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued
-                // and invoked on an element already removed
-                return;
-            }
-
-            var evt = $.Event("select2-removing");
-            evt.val = this.id(data);
-            evt.choice = data;
-            this.opts.element.trigger(evt);
-
-            if (evt.isDefaultPrevented()) {
-                return false;
-            }
-
-            while((index = indexOf(this.id(data), val)) >= 0) {
-                val.splice(index, 1);
-                this.setVal(val);
-                if (this.select) this.postprocessResults();
-            }
-
-            selected.remove();
-
-            this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data });
-            this.triggerChange({ removed: data });
-
-            return true;
-        },
-
-        // multi
-        postprocessResults: function (data, initial, noHighlightUpdate) {
-            var val = this.getVal(),
-                choices = this.results.find(".select2-result"),
-                compound = this.results.find(".select2-result-with-children"),
-                self = this;
-
-            choices.each2(function (i, choice) {
-                var id = self.id(choice.data("select2-data"));
-                if (indexOf(id, val) >= 0) {
-                    choice.addClass("select2-selected");
-                    // mark all children of the selected parent as selected
-                    choice.find(".select2-result-selectable").addClass("select2-selected");
-                }
-            });
-
-            compound.each2(function(i, choice) {
-                // hide an optgroup if it doesn't have any selectable children
-                if (!choice.is('.select2-result-selectable')
-                    && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) {
-                    choice.addClass("select2-selected");
-                }
-            });
-
-            if (this.highlight() == -1 && noHighlightUpdate !== false){
-                self.highlight(0);
-            }
-
-            //If all results are chosen render formatNoMatches
-            if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){
-                if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) {
-                    if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) {
-                        this.results.append("<li class='select2-no-results'>" + evaluate(self.opts.formatNoMatches, self.opts.element, self.search.val()) + "</li>");
-                    }
-                }
-            }
-
-        },
-
-        // multi
-        getMaxSearchWidth: function() {
-            return this.selection.width() - getSideBorderPadding(this.search);
-        },
-
-        // multi
-        resizeSearch: function () {
-            var minimumWidth, left, maxWidth, containerLeft, searchWidth,
-                sideBorderPadding = getSideBorderPadding(this.search);
-
-            minimumWidth = measureTextWidth(this.search) + 10;
-
-            left = this.search.offset().left;
-
-            maxWidth = this.selection.width();
-            containerLeft = this.selection.offset().left;
-
-            searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
-
-            if (searchWidth < minimumWidth) {
-                searchWidth = maxWidth - sideBorderPadding;
-            }
-
-            if (searchWidth < 40) {
-                searchWidth = maxWidth - sideBorderPadding;
-            }
-
-            if (searchWidth <= 0) {
-              searchWidth = minimumWidth;
-            }
-
-            this.search.width(Math.floor(searchWidth));
-        },
-
-        // multi
-        getVal: function () {
-            var val;
-            if (this.select) {
-                val = this.select.val();
-                return val === null ? [] : val;
-            } else {
-                val = this.opts.element.val();
-                return splitVal(val, this.opts.separator);
-            }
-        },
-
-        // multi
-        setVal: function (val) {
-            var unique;
-            if (this.select) {
-                this.select.val(val);
-            } else {
-                unique = [];
-                // filter out duplicates
-                $(val).each(function () {
-                    if (indexOf(this, unique) < 0) unique.push(this);
-                });
-                this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator));
-            }
-        },
-
-        // multi
-        buildChangeDetails: function (old, current) {
-            var current = current.slice(0),
-                old = old.slice(0);
-
-            // remove intersection from each array
-            for (var i = 0; i < current.length; i++) {
-                for (var j = 0; j < old.length; j++) {
-                    if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) {
-                        current.splice(i, 1);
-                        if(i>0){
-                        	i--;
-                        }
-                        old.splice(j, 1);
-                        j--;
-                    }
-                }
-            }
-
-            return {added: current, removed: old};
-        },
-
-
-        // multi
-        val: function (val, triggerChange) {
-            var oldData, self=this;
-
-            if (arguments.length === 0) {
-                return this.getVal();
-            }
-
-            oldData=this.data();
-            if (!oldData.length) oldData=[];
-
-            // val is an id. !val is true for [undefined,null,'',0] - 0 is legal
-            if (!val && val !== 0) {
-                this.opts.element.val("");
-                this.updateSelection([]);
-                this.clearSearch();
-                if (triggerChange) {
-                    this.triggerChange({added: this.data(), removed: oldData});
-                }
-                return;
-            }
-
-            // val is a list of ids
-            this.setVal(val);
-
-            if (this.select) {
-                this.opts.initSelection(this.select, this.bind(this.updateSelection));
-                if (triggerChange) {
-                    this.triggerChange(this.buildChangeDetails(oldData, this.data()));
-                }
-            } else {
-                if (this.opts.initSelection === undefined) {
-                    throw new Error("val() cannot be called if initSelection() is not defined");
-                }
-
-                this.opts.initSelection(this.opts.element, function(data){
-                    var ids=$.map(data, self.id);
-                    self.setVal(ids);
-                    self.updateSelection(data);
-                    self.clearSearch();
-                    if (triggerChange) {
-                        self.triggerChange(self.buildChangeDetails(oldData, self.data()));
-                    }
-                });
-            }
-            this.clearSearch();
-        },
-
-        // multi
-        onSortStart: function() {
-            if (this.select) {
-                throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
-            }
-
-            // collapse search field into 0 width so its container can be collapsed as well
-            this.search.width(0);
-            // hide the container
-            this.searchContainer.hide();
-        },
-
-        // multi
-        onSortEnd:function() {
-
-            var val=[], self=this;
-
-            // show search and move it to the end of the list
-            this.searchContainer.show();
-            // make sure the search container is the last item in the list
-            this.searchContainer.appendTo(this.searchContainer.parent());
-            // since we collapsed the width in dragStarted, we resize it here
-            this.resizeSearch();
-
-            // update selection
-            this.selection.find(".select2-search-choice").each(function() {
-                val.push(self.opts.id($(this).data("select2-data")));
-            });
-            this.setVal(val);
-            this.triggerChange();
-        },
-
-        // multi
-        data: function(values, triggerChange) {
-            var self=this, ids, old;
-            if (arguments.length === 0) {
-                 return this.selection
-                     .children(".select2-search-choice")
-                     .map(function() { return $(this).data("select2-data"); })
-                     .get();
-            } else {
-                old = this.data();
-                if (!values) { values = []; }
-                ids = $.map(values, function(e) { return self.opts.id(e); });
-                this.setVal(ids);
-                this.updateSelection(values);
-                this.clearSearch();
-                if (triggerChange) {
-                    this.triggerChange(this.buildChangeDetails(old, this.data()));
-                }
-            }
-        }
-    });
-
-    $.fn.select2 = function () {
-
-        var args = Array.prototype.slice.call(arguments, 0),
-            opts,
-            select2,
-            method, value, multiple,
-            allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"],
-            valueMethods = ["opened", "isFocused", "container", "dropdown"],
-            propertyMethods = ["val", "data"],
-            methodsMap = { search: "externalSearch" };
-
-        this.each(function () {
-            if (args.length === 0 || typeof(args[0]) === "object") {
-                opts = args.length === 0 ? {} : $.extend({}, args[0]);
-                opts.element = $(this);
-
-                if (opts.element.get(0).tagName.toLowerCase() === "select") {
-                    multiple = opts.element.prop("multiple");
-                } else {
-                    multiple = opts.multiple || false;
-                    if ("tags" in opts) {opts.multiple = multiple = true;}
-                }
-
-                select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single();
-                select2.init(opts);
-            } else if (typeof(args[0]) === "string") {
-
-                if (indexOf(args[0], allowedMethods) < 0) {
-                    throw "Unknown method: " + args[0];
-                }
-
-                value = undefined;
-                select2 = $(this).data("select2");
-                if (select2 === undefined) return;
-
-                method=args[0];
-
-                if (method === "container") {
-                    value = select2.container;
-                } else if (method === "dropdown") {
-                    value = select2.dropdown;
-                } else {
-                    if (methodsMap[method]) method = methodsMap[method];
-
-                    value = select2[method].apply(select2, args.slice(1));
-                }
-                if (indexOf(args[0], valueMethods) >= 0
-                    || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) {
-                    return false; // abort the iteration, ready to return first matched value
-                }
-            } else {
-                throw "Invalid arguments to select2 plugin: " + args;
-            }
-        });
-        return (value === undefined) ? this : value;
-    };
-
-    // plugin defaults, accessible to users
-    $.fn.select2.defaults = {
-        width: "copy",
-        loadMorePadding: 0,
-        closeOnSelect: true,
-        openOnEnter: true,
-        containerCss: {},
-        dropdownCss: {},
-        containerCssClass: "",
-        dropdownCssClass: "",
-        formatResult: function(result, container, query, escapeMarkup) {
-            var markup=[];
-            markMatch(result.text, query.term, markup, escapeMarkup);
-            return markup.join("");
-        },
-        formatSelection: function (data, container, escapeMarkup) {
-            return data ? escapeMarkup(data.text) : undefined;
-        },
-        sortResults: function (results, container, query) {
-            return results;
-        },
-        formatResultCssClass: function(data) {return data.css;},
-        formatSelectionCssClass: function(data, container) {return undefined;},
-        formatMatches: function (matches) { if (matches === 1) { return "One result is available, press enter to select it."; } return matches + " results are available, use up and down arrow keys to navigate."; },
-        formatNoMatches: function () { return "No matches found"; },
-        formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1? "" : "s"); },
-        formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); },
-        formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); },
-        formatLoadMore: function (pageNumber) { return "Loading more results…"; },
-        formatSearching: function () { return "Searching…"; },
-        minimumResultsForSearch: 0,
-        minimumInputLength: 0,
-        maximumInputLength: null,
-        maximumSelectionSize: 0,
-        id: function (e) { return e == undefined ? null : e.id; },
-        matcher: function(term, text) {
-            return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0;
-        },
-        separator: ",",
-        tokenSeparators: [],
-        tokenizer: defaultTokenizer,
-        escapeMarkup: defaultEscapeMarkup,
-        blurOnChange: false,
-        selectOnBlur: false,
-        adaptContainerCssClass: function(c) { return c; },
-        adaptDropdownCssClass: function(c) { return null; },
-        nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; },
-        searchInputPlaceholder: '',
-        createSearchChoicePosition: 'top',
-        shouldFocusInput: function (instance) {
-            // Attempt to detect touch devices
-            var supportsTouchEvents = (('ontouchstart' in window) ||
-                                       (navigator.msMaxTouchPoints > 0));
-
-            // Only devices which support touch events should be special cased
-            if (!supportsTouchEvents) {
-                return true;
-            }
-
-            // Never focus the input if search is disabled
-            if (instance.opts.minimumResultsForSearch < 0) {
-                return false;
-            }
-
-            return true;
-        }
-    };
-
-    $.fn.select2.ajaxDefaults = {
-        transport: $.ajax,
-        params: {
-            type: "GET",
-            cache: false,
-            dataType: "json"
-        }
-    };
-
-    // exports
-    window.Select2 = {
-        query: {
-            ajax: ajax,
-            local: local,
-            tags: tags
-        }, util: {
-            debounce: debounce,
-            markMatch: markMatch,
-            escapeMarkup: defaultEscapeMarkup,
-            stripDiacritics: stripDiacritics
-        }, "class": {
-            "abstract": AbstractSelect2,
-            "single": SingleSelect2,
-            "multi": MultiSelect2
-        }
-    };
-
-}(jQuery));
Binary file kallithea/public/js/select2/select2.png has changed
Binary file kallithea/public/js/select2/select2x2.png has changed
--- a/kallithea/public/js/yui.2.9.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={}}YAHOO.namespace=function(){var b=arguments,g=null,e,c,f;for(e=0;e<b.length;e=e+1){f=(""+b[e]).split(".");g=YAHOO;for(c=(f[0]=="YAHOO")?1:0;c<f.length;c=c+1){g[f[c]]=g[f[c]]||{};g=g[f[c]]}}return g};YAHOO.log=function(d,a,c){var b=YAHOO.widget.Logger;if(b&&b.log){return b.log(d,a,c)}else{return false}};YAHOO.register=function(a,f,e){var k=YAHOO.env.modules,c,j,h,g,d;if(!k[a]){k[a]={versions:[],builds:[]}}c=k[a];j=e.version;h=e.build;g=YAHOO.env.listeners;c.name=a;c.version=j;c.build=h;c.versions.push(j);c.builds.push(h);c.mainClass=f;for(d=0;d<g.length;d=d+1){g[d](c)}if(f){f.VERSION=j;f.BUILD=h}else{YAHOO.log("mainClass is undefined for module "+a,"warn")}};YAHOO.env=YAHOO.env||{modules:[],listeners:[]};YAHOO.env.getVersion=function(a){return YAHOO.env.modules[a]||null};YAHOO.env.parseUA=function(d){var e=function(i){var j=0;return parseFloat(i.replace(/\./g,function(){return(j++==1)?"":"."}))},h=navigator,g={ie:0,opera:0,gecko:0,webkit:0,chrome:0,mobile:null,air:0,ipad:0,iphone:0,ipod:0,ios:null,android:0,webos:0,caja:h&&h.cajaVersion,secure:false,os:null},c=d||(navigator&&navigator.userAgent),f=window&&window.location,b=f&&f.href,a;g.secure=b&&(b.toLowerCase().indexOf("https")===0);if(c){if((/windows|win32/i).test(c)){g.os="windows"}else{if((/macintosh/i).test(c)){g.os="macintosh"}else{if((/rhino/i).test(c)){g.os="rhino"}}}if((/KHTML/).test(c)){g.webkit=1}a=c.match(/AppleWebKit\/([^\s]*)/);if(a&&a[1]){g.webkit=e(a[1]);if(/ Mobile\//.test(c)){g.mobile="Apple";a=c.match(/OS ([^\s]*)/);if(a&&a[1]){a=e(a[1].replace("_","."))}g.ios=a;g.ipad=g.ipod=g.iphone=0;a=c.match(/iPad|iPod|iPhone/);if(a&&a[0]){g[a[0].toLowerCase()]=g.ios}}else{a=c.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/);if(a){g.mobile=a[0]}if(/webOS/.test(c)){g.mobile="WebOS";a=c.match(/webOS\/([^\s]*);/);if(a&&a[1]){g.webos=e(a[1])}}if(/ Android/.test(c)){g.mobile="Android";a=c.match(/Android ([^\s]*);/);if(a&&a[1]){g.android=e(a[1])}}}a=c.match(/Chrome\/([^\s]*)/);if(a&&a[1]){g.chrome=e(a[1])}else{a=c.match(/AdobeAIR\/([^\s]*)/);if(a){g.air=a[0]}}}if(!g.webkit){a=c.match(/Opera[\s\/]([^\s]*)/);if(a&&a[1]){g.opera=e(a[1]);a=c.match(/Version\/([^\s]*)/);if(a&&a[1]){g.opera=e(a[1])}a=c.match(/Opera Mini[^;]*/);if(a){g.mobile=a[0]}}else{a=c.match(/MSIE\s([^;]*)/);if(a&&a[1]){g.ie=e(a[1])}else{a=c.match(/Gecko\/([^\s]*)/);if(a){g.gecko=1;a=c.match(/rv:([^\s\)]*)/);if(a&&a[1]){g.gecko=e(a[1])}}}}}}return g};YAHOO.env.ua=YAHOO.env.parseUA();(function(){YAHOO.namespace("util","widget","example");if("undefined"!==typeof YAHOO_config){var b=YAHOO_config.listener,a=YAHOO.env.listeners,d=true,c;if(b){for(c=0;c<a.length;c++){if(a[c]==b){d=false;break}}if(d){a.push(b)}}}})();YAHOO.lang=YAHOO.lang||{};(function(){var f=YAHOO.lang,a=Object.prototype,c="[object Array]",h="[object Function]",i="[object Object]",b=[],g={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;","`":"&#x60;"},d=["toString","valueOf"],e={isArray:function(j){return a.toString.apply(j)===c},isBoolean:function(j){return typeof j==="boolean"},isFunction:function(j){return(typeof j==="function")||a.toString.apply(j)===h},isNull:function(j){return j===null},isNumber:function(j){return typeof j==="number"&&isFinite(j)},isObject:function(j){return(j&&(typeof j==="object"||f.isFunction(j)))||false},isString:function(j){return typeof j==="string"},isUndefined:function(j){return typeof j==="undefined"},_IEEnumFix:(YAHOO.env.ua.ie)?function(l,k){var j,n,m;for(j=0;j<d.length;j=j+1){n=d[j];m=k[n];if(f.isFunction(m)&&m!=a[n]){l[n]=m}}}:function(){},escapeHTML:function(j){return j.replace(/[&<>"'\/`]/g,function(k){return g[k]})},extend:function(m,n,l){if(!n||!m){throw new Error("extend failed, please check that all dependencies are included.")}var k=function(){},j;k.prototype=n.prototype;m.prototype=new k();m.prototype.constructor=m;m.superclass=n.prototype;if(n.prototype.constructor==a.constructor){n.prototype.constructor=n}if(l){for(j in l){if(f.hasOwnProperty(l,j)){m.prototype[j]=l[j]}}f._IEEnumFix(m.prototype,l)}},augmentObject:function(n,m){if(!m||!n){throw new Error("Absorb failed, verify dependencies.")}var j=arguments,l,o,k=j[2];if(k&&k!==true){for(l=2;l<j.length;l=l+1){n[j[l]]=m[j[l]]}}else{for(o in m){if(k||!(o in n)){n[o]=m[o]}}f._IEEnumFix(n,m)}return n},augmentProto:function(m,l){if(!l||!m){throw new Error("Augment failed, verify dependencies.")}var j=[m.prototype,l.prototype],k;for(k=2;k<arguments.length;k=k+1){j.push(arguments[k])}f.augmentObject.apply(this,j);return m},dump:function(j,p){var l,n,r=[],t="{...}",k="f(){...}",q=", ",m=" => ";if(!f.isObject(j)){return j+""}else{if(j instanceof Date||("nodeType" in j&&"tagName" in j)){return j}else{if(f.isFunction(j)){return k}}}p=(f.isNumber(p))?p:3;if(f.isArray(j)){r.push("[");for(l=0,n=j.length;l<n;l=l+1){if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t)}else{r.push(j[l])}r.push(q)}if(r.length>1){r.pop()}r.push("]")}else{r.push("{");for(l in j){if(f.hasOwnProperty(j,l)){r.push(l+m);if(f.isObject(j[l])){r.push((p>0)?f.dump(j[l],p-1):t)}else{r.push(j[l])}r.push(q)}}if(r.length>1){r.pop()}r.push("}")}return r.join("")},substitute:function(x,y,E,l){var D,C,B,G,t,u,F=[],p,z=x.length,A="dump",r=" ",q="{",m="}",n,w;for(;;){D=x.lastIndexOf(q,z);if(D<0){break}C=x.indexOf(m,D);if(D+1>C){break}p=x.substring(D+1,C);G=p;u=null;B=G.indexOf(r);if(B>-1){u=G.substring(B+1);G=G.substring(0,B)}t=y[G];if(E){t=E(G,t,u)}if(f.isObject(t)){if(f.isArray(t)){t=f.dump(t,parseInt(u,10))}else{u=u||"";n=u.indexOf(A);if(n>-1){u=u.substring(4)}w=t.toString();if(w===i||n>-1){t=f.dump(t,parseInt(u,10))}else{t=w}}}else{if(!f.isString(t)&&!f.isNumber(t)){t="~-"+F.length+"-~";F[F.length]=p}}x=x.substring(0,D)+t+x.substring(C+1);if(l===false){z=D-1}}for(D=F.length-1;D>=0;D=D-1){x=x.replace(new RegExp("~-"+D+"-~"),"{"+F[D]+"}","g")}return x},trim:function(j){try{return j.replace(/^\s+|\s+$/g,"")}catch(k){return j}},merge:function(){var n={},k=arguments,j=k.length,m;for(m=0;m<j;m=m+1){f.augmentObject(n,k[m],true)}return n},later:function(t,k,u,n,p){t=t||0;k=k||{};var l=u,s=n,q,j;if(f.isString(u)){l=k[u]}if(!l){throw new TypeError("method undefined")}if(!f.isUndefined(n)&&!f.isArray(s)){s=[n]}q=function(){l.apply(k,s||b)};j=(p)?setInterval(q,t):setTimeout(q,t);return{interval:p,cancel:function(){if(this.interval){clearInterval(j)}else{clearTimeout(j)}}}},isValue:function(j){return(f.isObject(j)||f.isString(j)||f.isNumber(j)||f.isBoolean(j))}};f.hasOwnProperty=(a.hasOwnProperty)?function(j,k){return j&&j.hasOwnProperty&&j.hasOwnProperty(k)}:function(j,k){return !f.isUndefined(j[k])&&j.constructor.prototype[k]!==j[k]};e.augmentObject(f,e,true);YAHOO.util.Lang=f;f.augment=f.augmentProto;YAHOO.augment=f.augmentProto;YAHOO.extend=f.extend})();YAHOO.register("yahoo",YAHOO,{version:"2.9.0",build:"2800"});YAHOO.util.CustomEvent=function(d,c,b,a,e){this.type=d;this.scope=c||window;this.silent=b;this.fireOnce=e;this.fired=false;this.firedWith=null;this.signature=a||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var f="_YUICEOnSubscribe";if(d!==f){this.subscribeEvent=new YAHOO.util.CustomEvent(f,this,true)}this.lastError=null};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(b,c,d){if(!b){throw new Error("Invalid callback for subscriber to '"+this.type+"'")}if(this.subscribeEvent){this.subscribeEvent.fire(b,c,d)}var a=new YAHOO.util.Subscriber(b,c,d);if(this.fireOnce&&this.fired){this.notify(a,this.firedWith)}else{this.subscribers.push(a)}},unsubscribe:function(d,f){if(!d){return this.unsubscribeAll()}var e=false;for(var b=0,a=this.subscribers.length;b<a;++b){var c=this.subscribers[b];if(c&&c.contains(d,f)){this._delete(b);e=true}}return e},fire:function(){this.lastError=null;var h=[],a=this.subscribers.length;var d=[].slice.call(arguments,0),c=true,f,b=false;if(this.fireOnce){if(this.fired){return true}else{this.firedWith=d}}this.fired=true;if(!a&&this.silent){return true}if(!this.silent){}var e=this.subscribers.slice();for(f=0;f<a;++f){var g=e[f];if(!g||!g.fn){b=true}else{c=this.notify(g,d);if(false===c){if(!this.silent){}break}}}return(c!==false)},notify:function(g,c){var b,i=null,f=g.getScope(this.scope),a=YAHOO.util.Event.throwErrors;if(!this.silent){}if(this.signature==YAHOO.util.CustomEvent.FLAT){if(c.length>0){i=c[0]}try{b=g.fn.call(f,i,g.obj)}catch(h){this.lastError=h;if(a){throw h}}}else{try{b=g.fn.call(f,this.type,c,g.obj)}catch(d){this.lastError=d;if(a){throw d}}}return b},unsubscribeAll:function(){var a=this.subscribers.length,b;for(b=a-1;b>-1;b--){this._delete(b)}this.subscribers=[];return a},_delete:function(a){var b=this.subscribers[a];if(b){delete b.fn;delete b.obj}this.subscribers.splice(a,1)},toString:function(){return"CustomEvent: '"+this.type+"', context: "+this.scope}};YAHOO.util.Subscriber=function(a,b,c){this.fn=a;this.obj=YAHOO.lang.isUndefined(b)?null:b;this.overrideContext=c};YAHOO.util.Subscriber.prototype.getScope=function(a){if(this.overrideContext){if(this.overrideContext===true){return this.obj}else{return this.overrideContext}}return a};YAHOO.util.Subscriber.prototype.contains=function(a,b){if(b){return(this.fn==a&&this.obj==b)}else{return(this.fn==a)}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", overrideContext: "+(this.overrideContext||"no")+" }"};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var g=false,h=[],j=[],a=0,e=[],b=0,c={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9},d=YAHOO.env.ua.ie,f="focusin",i="focusout";return{POLL_RETRYS:500,POLL_INTERVAL:40,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:d,_interval:null,_dri:null,_specialTypes:{focusin:(d?"focusin":"focus"),focusout:(d?"focusout":"blur")},DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){this._interval=YAHOO.lang.later(this.POLL_INTERVAL,this,this._tryPreloadAttach,null,true)}},onAvailable:function(q,m,o,p,n){var k=(YAHOO.lang.isString(q))?[q]:q;for(var l=0;l<k.length;l=l+1){e.push({id:k[l],fn:m,obj:o,overrideContext:p,checkReady:n})}a=this.POLL_RETRYS;this.startInterval()},onContentReady:function(n,k,l,m){this.onAvailable(n,k,l,m,true)},onDOMReady:function(){this.DOMReadyEvent.subscribe.apply(this.DOMReadyEvent,arguments)},_addListener:function(m,k,v,p,t,y){if(!v||!v.call){return false}if(this._isValidCollection(m)){var w=true;for(var q=0,s=m.length;q<s;++q){w=this.on(m[q],k,v,p,t)&&w}return w}else{if(YAHOO.lang.isString(m)){var o=this.getEl(m);if(o){m=o}else{this.onAvailable(m,function(){YAHOO.util.Event._addListener(m,k,v,p,t,y)});return true}}}if(!m){return false}if("unload"==k&&p!==this){j[j.length]=[m,k,v,p,t];return true}var l=m;if(t){if(t===true){l=p}else{l=t}}var n=function(z){return v.call(l,YAHOO.util.Event.getEvent(z,m),p)};var x=[m,k,v,n,l,p,t,y];var r=h.length;h[r]=x;try{this._simpleAdd(m,k,n,y)}catch(u){this.lastError=u;this.removeListener(m,k,v);return false}return true},_getType:function(k){return this._specialTypes[k]||k},addListener:function(m,p,l,n,o){var k=((p==f||p==i)&&!YAHOO.env.ua.ie)?true:false;return this._addListener(m,this._getType(p),l,n,o,k)},addFocusListener:function(l,k,m,n){return this.on(l,f,k,m,n)},removeFocusListener:function(l,k){return this.removeListener(l,f,k)},addBlurListener:function(l,k,m,n){return this.on(l,i,k,m,n)},removeBlurListener:function(l,k){return this.removeListener(l,i,k)},removeListener:function(l,k,r){var m,p,u;k=this._getType(k);if(typeof l=="string"){l=this.getEl(l)}else{if(this._isValidCollection(l)){var s=true;for(m=l.length-1;m>-1;m--){s=(this.removeListener(l[m],k,r)&&s)}return s}}if(!r||!r.call){return this.purgeElement(l,false,k)}if("unload"==k){for(m=j.length-1;m>-1;m--){u=j[m];if(u&&u[0]==l&&u[1]==k&&u[2]==r){j.splice(m,1);return true}}return false}var n=null;var o=arguments[3];if("undefined"===typeof o){o=this._getCacheIndex(h,l,k,r)}if(o>=0){n=h[o]}if(!l||!n){return false}var t=n[this.CAPTURE]===true?true:false;try{this._simpleRemove(l,k,n[this.WFN],t)}catch(q){this.lastError=q;return false}delete h[o][this.WFN];delete h[o][this.FN];h.splice(o,1);return true},getTarget:function(m,l){var k=m.target||m.srcElement;return this.resolveTextNode(k)},resolveTextNode:function(l){try{if(l&&3==l.nodeType){return l.parentNode}}catch(k){return null}return l},getPageX:function(l){var k=l.pageX;if(!k&&0!==k){k=l.clientX||0;if(this.isIE){k+=this._getScrollLeft()}}return k},getPageY:function(k){var l=k.pageY;if(!l&&0!==l){l=k.clientY||0;if(this.isIE){l+=this._getScrollTop()}}return l},getXY:function(k){return[this.getPageX(k),this.getPageY(k)]},getRelatedTarget:function(l){var k=l.relatedTarget;if(!k){if(l.type=="mouseout"){k=l.toElement}else{if(l.type=="mouseover"){k=l.fromElement}}}return this.resolveTextNode(k)},getTime:function(m){if(!m.time){var l=new Date().getTime();try{m.time=l}catch(k){this.lastError=k;return l}}return m.time},stopEvent:function(k){this.stopPropagation(k);this.preventDefault(k)},stopPropagation:function(k){if(k.stopPropagation){k.stopPropagation()}else{k.cancelBubble=true}},preventDefault:function(k){if(k.preventDefault){k.preventDefault()}else{k.returnValue=false}},getEvent:function(m,k){var l=m||window.event;if(!l){var n=this.getEvent.caller;while(n){l=n.arguments[0];if(l&&Event==l.constructor){break}n=n.caller}}return l},getCharCode:function(l){var k=l.keyCode||l.charCode||0;if(YAHOO.env.ua.webkit&&(k in c)){k=c[k]}return k},_getCacheIndex:function(n,q,r,p){for(var o=0,m=n.length;o<m;o=o+1){var k=n[o];if(k&&k[this.FN]==p&&k[this.EL]==q&&k[this.TYPE]==r){return o}}return -1},generateId:function(k){var l=k.id;if(!l){l="yuievtautoid-"+b;++b;k.id=l}return l},_isValidCollection:function(l){try{return(l&&typeof l!=="string"&&l.length&&!l.tagName&&!l.alert&&typeof l[0]!=="undefined")}catch(k){return false}},elCache:{},getEl:function(k){return(typeof k==="string")?document.getElementById(k):k},clearCache:function(){},DOMReadyEvent:new YAHOO.util.CustomEvent("DOMReady",YAHOO,0,0,1),_load:function(l){if(!g){g=true;var k=YAHOO.util.Event;k._ready();k._tryPreloadAttach()}},_ready:function(l){var k=YAHOO.util.Event;if(!k.DOMReady){k.DOMReady=true;k.DOMReadyEvent.fire();k._simpleRemove(document,"DOMContentLoaded",k._ready)}},_tryPreloadAttach:function(){if(e.length===0){a=0;if(this._interval){this._interval.cancel();this._interval=null}return}if(this.locked){return}if(this.isIE){if(!this.DOMReady){this.startInterval();return}}this.locked=true;var q=!g;if(!q){q=(a>0&&e.length>0)}var p=[];var r=function(t,u){var s=t;if(u.overrideContext){if(u.overrideContext===true){s=u.obj}else{s=u.overrideContext}}u.fn.call(s,u.obj)};var l,k,o,n,m=[];for(l=0,k=e.length;l<k;l=l+1){o=e[l];if(o){n=this.getEl(o.id);if(n){if(o.checkReady){if(g||n.nextSibling||!q){m.push(o);e[l]=null}}else{r(n,o);e[l]=null}}else{p.push(o)}}}for(l=0,k=m.length;l<k;l=l+1){o=m[l];r(this.getEl(o.id),o)}a--;if(q){for(l=e.length-1;l>-1;l--){o=e[l];if(!o||!o.id){e.splice(l,1)}}this.startInterval()}else{if(this._interval){this._interval.cancel();this._interval=null}}this.locked=false},purgeElement:function(p,q,s){var n=(YAHOO.lang.isString(p))?this.getEl(p):p;var r=this.getListeners(n,s),o,k;if(r){for(o=r.length-1;o>-1;o--){var m=r[o];this.removeListener(n,m.type,m.fn)}}if(q&&n&&n.childNodes){for(o=0,k=n.childNodes.length;o<k;++o){this.purgeElement(n.childNodes[o],q,s)}}},getListeners:function(n,k){var q=[],m;if(!k){m=[h,j]}else{if(k==="unload"){m=[j]}else{k=this._getType(k);m=[h]}}var s=(YAHOO.lang.isString(n))?this.getEl(n):n;for(var p=0;p<m.length;p=p+1){var u=m[p];if(u){for(var r=0,t=u.length;r<t;++r){var o=u[r];if(o&&o[this.EL]===s&&(!k||k===o[this.TYPE])){q.push({type:o[this.TYPE],fn:o[this.FN],obj:o[this.OBJ],adjust:o[this.OVERRIDE],scope:o[this.ADJ_SCOPE],index:r})}}}}return(q.length)?q:null},_unload:function(s){var m=YAHOO.util.Event,p,o,n,r,q,t=j.slice(),k;for(p=0,r=j.length;p<r;++p){n=t[p];if(n){try{k=window;if(n[m.ADJ_SCOPE]){if(n[m.ADJ_SCOPE]===true){k=n[m.UNLOAD_OBJ]}else{k=n[m.ADJ_SCOPE]}}n[m.FN].call(k,m.getEvent(s,n[m.EL]),n[m.UNLOAD_OBJ])}catch(w){}t[p]=null}}n=null;k=null;j=null;if(h){for(o=h.length-1;o>-1;o--){n=h[o];if(n){try{m.removeListener(n[m.EL],n[m.TYPE],n[m.FN],o)}catch(v){}}}n=null}try{m._simpleRemove(window,"unload",m._unload);m._simpleRemove(window,"load",m._load)}catch(u){}},_getScrollLeft:function(){return this._getScroll()[1]},_getScrollTop:function(){return this._getScroll()[0]},_getScroll:function(){var k=document.documentElement,l=document.body;if(k&&(k.scrollTop||k.scrollLeft)){return[k.scrollTop,k.scrollLeft]}else{if(l){return[l.scrollTop,l.scrollLeft]}else{return[0,0]}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(m,n,l,k){m.addEventListener(n,l,(k))}}else{if(window.attachEvent){return function(m,n,l,k){m.attachEvent("on"+n,l)}}else{return function(){}}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(m,n,l,k){m.removeEventListener(n,l,(k))}}else{if(window.detachEvent){return function(l,m,k){l.detachEvent("on"+m,k)}}else{return function(){}}}}()}}();(function(){var a=YAHOO.util.Event;a.on=a.addListener;a.onFocus=a.addFocusListener;a.onBlur=a.addBlurListener;
-/*! DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller/Diego Perini */
-if(a.isIE){if(self!==self.top){document.onreadystatechange=function(){if(document.readyState=="complete"){document.onreadystatechange=null;a._ready()}}}else{YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var b=document.createElement("p");a._dri=setInterval(function(){try{b.doScroll("left");clearInterval(a._dri);a._dri=null;a._ready();b=null}catch(c){}},a.POLL_INTERVAL)}}else{if(a.webkit&&a.webkit<525){a._dri=setInterval(function(){var c=document.readyState;if("loaded"==c||"complete"==c){clearInterval(a._dri);a._dri=null;a._ready()}},a.POLL_INTERVAL)}else{a._simpleAdd(document,"DOMContentLoaded",a._ready)}}a._simpleAdd(window,"load",a._load);a._simpleAdd(window,"unload",a._unload);a._tryPreloadAttach()})()}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(a,c,f,e){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[a];if(d){d.subscribe(c,f,e)}else{this.__yui_subscribers=this.__yui_subscribers||{};var b=this.__yui_subscribers;if(!b[a]){b[a]=[]}b[a].push({fn:c,obj:f,overrideContext:e})}},unsubscribe:function(c,e,g){this.__yui_events=this.__yui_events||{};var a=this.__yui_events;if(c){var f=a[c];if(f){return f.unsubscribe(e,g)}}else{var b=true;for(var d in a){if(YAHOO.lang.hasOwnProperty(a,d)){b=b&&a[d].unsubscribe(e,g)}}return b}return false},unsubscribeAll:function(a){return this.unsubscribe(a)},createEvent:function(b,g){this.__yui_events=this.__yui_events||{};var e=g||{},d=this.__yui_events,f;if(d[b]){}else{f=new YAHOO.util.CustomEvent(b,e.scope||this,e.silent,YAHOO.util.CustomEvent.FLAT,e.fireOnce);d[b]=f;if(e.onSubscribeCallback){f.subscribeEvent.subscribe(e.onSubscribeCallback)}this.__yui_subscribers=this.__yui_subscribers||{};var a=this.__yui_subscribers[b];if(a){for(var c=0;c<a.length;++c){f.subscribe(a[c].fn,a[c].obj,a[c].overrideContext)}}}return d[b]},fireEvent:function(b){this.__yui_events=this.__yui_events||{};var d=this.__yui_events[b];if(!d){return null}var a=[];for(var c=1;c<arguments.length;++c){a.push(arguments[c])}return d.fire.apply(d,a)},hasEvent:function(a){if(this.__yui_events){if(this.__yui_events[a]){return true}}return false}};(function(){var a=YAHOO.util.Event,c=YAHOO.lang;YAHOO.util.KeyListener=function(d,i,e,f){if(!d){}else{if(!i){}else{if(!e){}}}if(!f){f=YAHOO.util.KeyListener.KEYDOWN}var g=new YAHOO.util.CustomEvent("keyPressed");this.enabledEvent=new YAHOO.util.CustomEvent("enabled");this.disabledEvent=new YAHOO.util.CustomEvent("disabled");if(c.isString(d)){d=document.getElementById(d)}if(c.isFunction(e)){g.subscribe(e)}else{g.subscribe(e.fn,e.scope,e.correctScope)}function h(o,n){if(!i.shift){i.shift=false}if(!i.alt){i.alt=false}if(!i.ctrl){i.ctrl=false}if(o.shiftKey==i.shift&&o.altKey==i.alt&&o.ctrlKey==i.ctrl){var j,m=i.keys,l;if(YAHOO.lang.isArray(m)){for(var k=0;k<m.length;k++){j=m[k];l=a.getCharCode(o);if(j==l){g.fire(l,o);break}}}else{l=a.getCharCode(o);if(m==l){g.fire(l,o)}}}}this.enable=function(){if(!this.enabled){a.on(d,f,h);this.enabledEvent.fire(i)}this.enabled=true};this.disable=function(){if(this.enabled){a.removeListener(d,f,h);this.disabledEvent.fire(i)}this.enabled=false};this.toString=function(){return"KeyListener ["+i.keys+"] "+d.tagName+(d.id?"["+d.id+"]":"")}};var b=YAHOO.util.KeyListener;b.KEYDOWN="keydown";b.KEYUP="keyup";b.KEY={ALT:18,BACK_SPACE:8,CAPS_LOCK:20,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,META:224,NUM_LOCK:144,PAGE_DOWN:34,PAGE_UP:33,PAUSE:19,PRINTSCREEN:44,RIGHT:39,SCROLL_LOCK:145,SHIFT:16,SPACE:32,TAB:9,UP:38}})();YAHOO.register("event",YAHOO.util.Event,{version:"2.9.0",build:"2800"});(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var e=YAHOO.util,k=YAHOO.lang,L=YAHOO.env.ua,a=YAHOO.lang.trim,B={},F={},m=/^t(?:able|d|h)$/i,w=/color$/i,j=window.document,v=j.documentElement,C="ownerDocument",M="defaultView",U="documentElement",S="compatMode",z="offsetLeft",o="offsetTop",T="offsetParent",x="parentNode",K="nodeType",c="tagName",n="scrollLeft",H="scrollTop",p="getBoundingClientRect",V="getComputedStyle",y="currentStyle",l="CSS1Compat",A="BackCompat",E="class",f="className",i="",b=" ",R="(?:^|\\s)",J="(?= |$)",t="g",O="position",D="fixed",u="relative",I="left",N="top",Q="medium",P="borderLeftWidth",q="borderTopWidth",d=L.opera,h=L.webkit,g=L.gecko,s=L.ie;e.Dom={CUSTOM_ATTRIBUTES:(!v.hasAttribute)?{"for":"htmlFor","class":f}:{htmlFor:"for",className:E},DOT_ATTRIBUTES:{checked:true},get:function(ab){var ad,X,ac,aa,W,G,Z=null;if(ab){if(typeof ab=="string"||typeof ab=="number"){ad=ab+"";ab=j.getElementById(ab);G=(ab)?ab.attributes:null;if(ab&&G&&G.id&&G.id.value===ad){return ab}else{if(ab&&j.all){ab=null;X=j.all[ad];if(X&&X.length){for(aa=0,W=X.length;aa<W;++aa){if(X[aa].id===ad){return X[aa]}}}}}}else{if(e.Element&&ab instanceof e.Element){ab=ab.get("element")}else{if(!ab.nodeType&&"length" in ab){ac=[];for(aa=0,W=ab.length;aa<W;++aa){ac[ac.length]=e.Dom.get(ab[aa])}ab=ac}}}Z=ab}return Z},getComputedStyle:function(G,W){if(window[V]){return G[C][M][V](G,null)[W]}else{if(G[y]){return e.Dom.IE_ComputedStyle.get(G,W)}}},getStyle:function(G,W){return e.Dom.batch(G,e.Dom._getStyle,W)},_getStyle:function(){if(window[V]){return function(G,Z){Z=(Z==="float")?Z="cssFloat":e.Dom._toCamel(Z);var X=G.style[Z],W;if(!X){W=G[C][M][V](G,null);if(W){X=W[Z]}}return X}}else{if(v[y]){return function(G,Z){var X;switch(Z){case"opacity":X=100;try{X=G.filters["DXImageTransform.Microsoft.Alpha"].opacity}catch(aa){try{X=G.filters("alpha").opacity}catch(W){}}return X/100;case"float":Z="styleFloat";default:Z=e.Dom._toCamel(Z);X=G[y]?G[y][Z]:null;return(G.style[Z]||X)}}}}}(),setStyle:function(G,W,X){e.Dom.batch(G,e.Dom._setStyle,{prop:W,val:X})},_setStyle:function(){if(!window.getComputedStyle&&j.documentElement.currentStyle){return function(W,G){var X=e.Dom._toCamel(G.prop),Z=G.val;if(W){switch(X){case"opacity":if(Z===""||Z===null||Z===1){W.style.removeAttribute("filter")}else{if(k.isString(W.style.filter)){W.style.filter="alpha(opacity="+Z*100+")";if(!W[y]||!W[y].hasLayout){W.style.zoom=1}}}break;case"float":X="styleFloat";default:W.style[X]=Z}}else{}}}else{return function(W,G){var X=e.Dom._toCamel(G.prop),Z=G.val;if(W){if(X=="float"){X="cssFloat"}W.style[X]=Z}else{}}}}(),getXY:function(G){return e.Dom.batch(G,e.Dom._getXY)},_canPosition:function(G){return(e.Dom._getStyle(G,"display")!=="none"&&e.Dom._inDoc(G))},_getXY:function(W){var X,G,aa,ac,Z,ab,ad=Math.round,ae=false;if(e.Dom._canPosition(W)){aa=W[p]();ac=W[C];X=e.Dom.getDocumentScrollLeft(ac);G=e.Dom.getDocumentScrollTop(ac);ae=[aa[I],aa[N]];if(Z||ab){ae[0]-=ab;ae[1]-=Z}if((G||X)){ae[0]+=X;ae[1]+=G}ae[0]=ad(ae[0]);ae[1]=ad(ae[1])}else{}return ae},getX:function(G){var W=function(X){return e.Dom.getXY(X)[0]};return e.Dom.batch(G,W,e.Dom,true)},getY:function(G){var W=function(X){return e.Dom.getXY(X)[1]};return e.Dom.batch(G,W,e.Dom,true)},setXY:function(G,X,W){e.Dom.batch(G,e.Dom._setXY,{pos:X,noRetry:W})},_setXY:function(G,aa){var ab=e.Dom._getStyle(G,O),Z=e.Dom.setStyle,ae=aa.pos,W=aa.noRetry,ac=[parseInt(e.Dom.getComputedStyle(G,I),10),parseInt(e.Dom.getComputedStyle(G,N),10)],ad,X;ad=e.Dom._getXY(G);if(!ae||ad===false){return false}if(ab=="static"){ab=u;Z(G,O,ab)}if(isNaN(ac[0])){ac[0]=(ab==u)?0:G[z]}if(isNaN(ac[1])){ac[1]=(ab==u)?0:G[o]}if(ae[0]!==null){Z(G,I,ae[0]-ad[0]+ac[0]+"px")}if(ae[1]!==null){Z(G,N,ae[1]-ad[1]+ac[1]+"px")}if(!W){X=e.Dom._getXY(G);if((ae[0]!==null&&X[0]!=ae[0])||(ae[1]!==null&&X[1]!=ae[1])){e.Dom._setXY(G,{pos:ae,noRetry:true})}}},setX:function(W,G){e.Dom.setXY(W,[G,null])},setY:function(G,W){e.Dom.setXY(G,[null,W])},getRegion:function(G){var W=function(X){var Z=false;if(e.Dom._canPosition(X)){Z=e.Region.getRegion(X)}else{}return Z};return e.Dom.batch(G,W,e.Dom,true)},getClientWidth:function(){return e.Dom.getViewportWidth()},getClientHeight:function(){return e.Dom.getViewportHeight()},getElementsByClassName:function(ac,ag,ad,af,X,ae){ag=ag||"*";ad=(ad)?e.Dom.get(ad):null||j;if(!ad){return[]}var W=[],G=ad.getElementsByTagName(ag),aa=e.Dom.hasClass;for(var Z=0,ab=G.length;Z<ab;++Z){if(aa(G[Z],ac)){W[W.length]=G[Z]}}if(af){e.Dom.batch(W,af,X,ae)}return W},hasClass:function(W,G){return e.Dom.batch(W,e.Dom._hasClass,G)},_hasClass:function(X,W){var G=false,Z;if(X&&W){Z=e.Dom._getAttribute(X,f)||i;if(Z){Z=Z.replace(/\s+/g,b)}if(W.exec){G=W.test(Z)}else{G=W&&(b+Z+b).indexOf(b+W+b)>-1}}else{}return G},addClass:function(W,G){return e.Dom.batch(W,e.Dom._addClass,G)},_addClass:function(X,W){var G=false,Z;if(X&&W){Z=e.Dom._getAttribute(X,f)||i;if(!e.Dom._hasClass(X,W)){e.Dom.setAttribute(X,f,a(Z+b+W));G=true}}else{}return G},removeClass:function(W,G){return e.Dom.batch(W,e.Dom._removeClass,G)},_removeClass:function(Z,X){var W=false,ab,aa,G;if(Z&&X){ab=e.Dom._getAttribute(Z,f)||i;e.Dom.setAttribute(Z,f,ab.replace(e.Dom._getClassRegex(X),i));aa=e.Dom._getAttribute(Z,f);if(ab!==aa){e.Dom.setAttribute(Z,f,a(aa));W=true;if(e.Dom._getAttribute(Z,f)===""){G=(Z.hasAttribute&&Z.hasAttribute(E))?E:f;Z.removeAttribute(G)}}}else{}return W},replaceClass:function(X,W,G){return e.Dom.batch(X,e.Dom._replaceClass,{from:W,to:G})},_replaceClass:function(Z,X){var W,ac,ab,G=false,aa;if(Z&&X){ac=X.from;ab=X.to;if(!ab){G=false}else{if(!ac){G=e.Dom._addClass(Z,X.to)}else{if(ac!==ab){aa=e.Dom._getAttribute(Z,f)||i;W=(b+aa.replace(e.Dom._getClassRegex(ac),b+ab).replace(/\s+/g,b)).split(e.Dom._getClassRegex(ab));W.splice(1,0,b+ab);e.Dom.setAttribute(Z,f,a(W.join(i)));G=true}}}}else{}return G},generateId:function(G,X){X=X||"yui-gen";var W=function(Z){if(Z&&Z.id){return Z.id}var aa=X+YAHOO.env._id_counter++;if(Z){if(Z[C]&&Z[C].getElementById(aa)){return e.Dom.generateId(Z,aa+X)}Z.id=aa}return aa};return e.Dom.batch(G,W,e.Dom,true)||W.apply(e.Dom,arguments)},isAncestor:function(W,X){W=e.Dom.get(W);X=e.Dom.get(X);var G=false;if((W&&X)&&(W[K]&&X[K])){if(W.contains&&W!==X){G=W.contains(X)}else{if(W.compareDocumentPosition){G=!!(W.compareDocumentPosition(X)&16)}}}else{}return G},inDocument:function(G,W){return e.Dom._inDoc(e.Dom.get(G),W)},_inDoc:function(W,X){var G=false;if(W&&W[c]){X=X||W[C];G=e.Dom.isAncestor(X[U],W)}else{}return G},getElementsBy:function(W,ag,ac,ae,X,ad,af){ag=ag||"*";ac=(ac)?e.Dom.get(ac):null||j;var ab=(af)?null:[],G;if(ac){G=ac.getElementsByTagName(ag);for(var Z=0,aa=G.length;Z<aa;++Z){if(W(G[Z])){if(af){ab=G[Z];break}else{ab[ab.length]=G[Z]}}}if(ae){e.Dom.batch(ab,ae,X,ad)}}return ab},getElementBy:function(X,G,W){return e.Dom.getElementsBy(X,G,W,null,null,null,true)},batch:function(X,ac,ab,aa){var Z=[],W=(aa)?ab:null;X=(X&&(X[c]||X.item))?X:e.Dom.get(X);if(X&&ac){if(X[c]||X.length===undefined){return ac.call(W,X,ab)}for(var G=0;G<X.length;++G){Z[Z.length]=ac.call(W||X[G],X[G],ab)}}else{return false}return Z},getDocumentHeight:function(){var W=(j[S]!=l||h)?j.body.scrollHeight:v.scrollHeight,G=Math.max(W,e.Dom.getViewportHeight());return G},getDocumentWidth:function(){var W=(j[S]!=l||h)?j.body.scrollWidth:v.scrollWidth,G=Math.max(W,e.Dom.getViewportWidth());return G},getViewportHeight:function(){var G=self.innerHeight,W=j[S];if((W||s)&&!d){G=(W==l)?v.clientHeight:j.body.clientHeight}return G},getViewportWidth:function(){var G=self.innerWidth,W=j[S];if(W||s){G=(W==l)?v.clientWidth:j.body.clientWidth}return G},getAncestorBy:function(G,W){while((G=G[x])){if(e.Dom._testElement(G,W)){return G}}return null},getAncestorByClassName:function(W,G){W=e.Dom.get(W);if(!W){return null}var X=function(Z){return e.Dom.hasClass(Z,G)};return e.Dom.getAncestorBy(W,X)},getAncestorByTagName:function(W,G){W=e.Dom.get(W);if(!W){return null}var X=function(Z){return Z[c]&&Z[c].toUpperCase()==G.toUpperCase()};return e.Dom.getAncestorBy(W,X)},getPreviousSiblingBy:function(G,W){while(G){G=G.previousSibling;if(e.Dom._testElement(G,W)){return G}}return null},getPreviousSibling:function(G){G=e.Dom.get(G);if(!G){return null}return e.Dom.getPreviousSiblingBy(G)},getNextSiblingBy:function(G,W){while(G){G=G.nextSibling;if(e.Dom._testElement(G,W)){return G}}return null},getNextSibling:function(G){G=e.Dom.get(G);if(!G){return null}return e.Dom.getNextSiblingBy(G)},getFirstChildBy:function(G,X){var W=(e.Dom._testElement(G.firstChild,X))?G.firstChild:null;return W||e.Dom.getNextSiblingBy(G.firstChild,X)},getFirstChild:function(G,W){G=e.Dom.get(G);if(!G){return null}return e.Dom.getFirstChildBy(G)},getLastChildBy:function(G,X){if(!G){return null}var W=(e.Dom._testElement(G.lastChild,X))?G.lastChild:null;return W||e.Dom.getPreviousSiblingBy(G.lastChild,X)},getLastChild:function(G){G=e.Dom.get(G);return e.Dom.getLastChildBy(G)},getChildrenBy:function(W,Z){var X=e.Dom.getFirstChildBy(W,Z),G=X?[X]:[];e.Dom.getNextSiblingBy(X,function(aa){if(!Z||Z(aa)){G[G.length]=aa}return false});return G},getChildren:function(G){G=e.Dom.get(G);if(!G){}return e.Dom.getChildrenBy(G)},getDocumentScrollLeft:function(G){G=G||j;return Math.max(G[U].scrollLeft,G.body.scrollLeft)},getDocumentScrollTop:function(G){G=G||j;return Math.max(G[U].scrollTop,G.body.scrollTop)},insertBefore:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null}return G[x].insertBefore(W,G)},insertAfter:function(W,G){W=e.Dom.get(W);G=e.Dom.get(G);if(!W||!G||!G[x]){return null}if(G.nextSibling){return G[x].insertBefore(W,G.nextSibling)}else{return G[x].appendChild(W)}},getClientRegion:function(){var X=e.Dom.getDocumentScrollTop(),W=e.Dom.getDocumentScrollLeft(),Z=e.Dom.getViewportWidth()+W,G=e.Dom.getViewportHeight()+X;return new e.Region(X,Z,G,W)},setAttribute:function(W,G,X){e.Dom.batch(W,e.Dom._setAttribute,{attr:G,val:X})},_setAttribute:function(X,W){var G=e.Dom._toCamel(W.attr),Z=W.val;if(X&&X.setAttribute){if(e.Dom.DOT_ATTRIBUTES[G]&&X.tagName&&X.tagName!="BUTTON"){X[G]=Z}else{G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;X.setAttribute(G,Z)}}else{}},getAttribute:function(W,G){return e.Dom.batch(W,e.Dom._getAttribute,G)},_getAttribute:function(W,G){var X;G=e.Dom.CUSTOM_ATTRIBUTES[G]||G;if(e.Dom.DOT_ATTRIBUTES[G]){X=W[G]}else{if(W&&"getAttribute" in W){if(/^(?:href|src)$/.test(G)){X=W.getAttribute(G,2)}else{X=W.getAttribute(G)}}else{}}return X},_toCamel:function(W){var X=B;function G(Z,aa){return aa.toUpperCase()}return X[W]||(X[W]=W.indexOf("-")===-1?W:W.replace(/-([a-z])/gi,G))},_getClassRegex:function(W){var G;if(W!==undefined){if(W.exec){G=W}else{G=F[W];if(!G){W=W.replace(e.Dom._patterns.CLASS_RE_TOKENS,"\\$1");W=W.replace(/\s+/g,b);G=F[W]=new RegExp(R+W+J,t)}}}return G},_patterns:{ROOT_TAG:/^body|html$/i,CLASS_RE_TOKENS:/([\.\(\)\^\$\*\+\?\|\[\]\{\}\\])/g},_testElement:function(G,W){return G&&G[K]==1&&(!W||W(G))},_calcBorders:function(X,Z){var W=parseInt(e.Dom[V](X,q),10)||0,G=parseInt(e.Dom[V](X,P),10)||0;if(g){if(m.test(X[c])){W=0;G=0}}Z[0]+=G;Z[1]+=W;return Z}};var r=e.Dom[V];if(L.opera){e.Dom[V]=function(W,G){var X=r(W,G);if(w.test(G)){X=e.Dom.Color.toRGB(X)}return X}}if(L.webkit){e.Dom[V]=function(W,G){var X=r(W,G);if(X==="rgba(0, 0, 0, 0)"){X="transparent"}return X}}if(L.ie&&L.ie>=8){e.Dom.DOT_ATTRIBUTES.type=true}})();YAHOO.util.Region=function(d,e,a,c){this.top=d;this.y=d;this[1]=d;this.right=e;this.bottom=a;this.left=c;this.x=c;this[0]=c;this.width=this.right-this.left;this.height=this.bottom-this.top};YAHOO.util.Region.prototype.contains=function(a){return(a.left>=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom)};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left))};YAHOO.util.Region.prototype.intersect=function(f){var d=Math.max(this.top,f.top),e=Math.min(this.right,f.right),a=Math.min(this.bottom,f.bottom),c=Math.max(this.left,f.left);if(a>=d&&e>=c){return new YAHOO.util.Region(d,e,a,c)}else{return null}};YAHOO.util.Region.prototype.union=function(f){var d=Math.min(this.top,f.top),e=Math.max(this.right,f.right),a=Math.max(this.bottom,f.bottom),c=Math.min(this.left,f.left);return new YAHOO.util.Region(d,e,a,c)};YAHOO.util.Region.prototype.toString=function(){return("Region {top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}")};YAHOO.util.Region.getRegion=function(e){var g=YAHOO.util.Dom.getXY(e),d=g[1],f=g[0]+e.offsetWidth,a=g[1]+e.offsetHeight,c=g[0];return new YAHOO.util.Region(d,f,a,c)};YAHOO.util.Point=function(a,b){if(YAHOO.lang.isArray(a)){b=a[1];a=a[0]}YAHOO.util.Point.superclass.constructor.call(this,b,a,b,a)};YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region);(function(){var b=YAHOO.util,a="clientTop",f="clientLeft",j="parentNode",k="right",w="hasLayout",i="px",u="opacity",l="auto",d="borderLeftWidth",g="borderTopWidth",p="borderRightWidth",v="borderBottomWidth",s="visible",q="transparent",n="height",e="width",h="style",t="currentStyle",r=/^width|height$/,o=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,m={get:function(x,z){var y="",A=x[t][z];if(z===u){y=b.Dom.getStyle(x,u)}else{if(!A||(A.indexOf&&A.indexOf(i)>-1)){y=A}else{if(b.Dom.IE_COMPUTED[z]){y=b.Dom.IE_COMPUTED[z](x,z)}else{if(o.test(A)){y=b.Dom.IE.ComputedStyle.getPixel(x,z)}else{y=A}}}}return y},getOffset:function(z,E){var B=z[t][E],x=E.charAt(0).toUpperCase()+E.substr(1),C="offset"+x,y="pixel"+x,A="",D;if(B==l){D=z[C];if(D===undefined){A=0}A=D;if(r.test(E)){z[h][E]=D;if(z[C]>D){A=D-(z[C]-D)}z[h][E]=l}}else{if(!z[h][y]&&!z[h][E]){z[h][E]=B}A=z[h][y]}return A+i},getBorderWidth:function(x,z){var y=null;if(!x[t][w]){x[h].zoom=1}switch(z){case g:y=x[a];break;case v:y=x.offsetHeight-x.clientHeight-x[a];break;case d:y=x[f];break;case p:y=x.offsetWidth-x.clientWidth-x[f];break}return y+i},getPixel:function(y,x){var A=null,B=y[t][k],z=y[t][x];y[h][k]=z;A=y[h].pixelRight;y[h][k]=B;return A+i},getMargin:function(y,x){var z;if(y[t][x]==l){z=0+i}else{z=b.Dom.IE.ComputedStyle.getPixel(y,x)}return z},getVisibility:function(y,x){var z;while((z=y[t])&&z[x]=="inherit"){y=y[j]}return(z)?z[x]:s},getColor:function(y,x){return b.Dom.Color.toRGB(y[t][x])||q},getBorderColor:function(y,x){var z=y[t],A=z[x]||z.color;return b.Dom.Color.toRGB(b.Dom.Color.toHex(A))}},c={};c.top=c.right=c.bottom=c.left=c[e]=c[n]=m.getOffset;c.color=m.getColor;c[g]=c[p]=c[v]=c[d]=m.getBorderWidth;c.marginTop=c.marginRight=c.marginBottom=c.marginLeft=m.getMargin;c.visibility=m.getVisibility;c.borderColor=c.borderTopColor=c.borderRightColor=c.borderBottomColor=c.borderLeftColor=m.getBorderColor;b.Dom.IE_COMPUTED=c;b.Dom.IE_ComputedStyle=m})();(function(){var c="toString",a=parseInt,b=RegExp,d=YAHOO.util;d.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(e){if(!d.Dom.Color.re_RGB.test(e)){e=d.Dom.Color.toHex(e)}if(d.Dom.Color.re_hex.exec(e)){e="rgb("+[a(b.$1,16),a(b.$2,16),a(b.$3,16)].join(", ")+")"}return e},toHex:function(f){f=d.Dom.Color.KEYWORDS[f]||f;if(d.Dom.Color.re_RGB.exec(f)){f=[Number(b.$1).toString(16),Number(b.$2).toString(16),Number(b.$3).toString(16)];for(var e=0;e<f.length;e++){if(f[e].length<2){f[e]="0"+f[e]}}f=f.join("")}if(f.length<6){f=f.replace(d.Dom.Color.re_hex3,"$1$1")}if(f!=="transparent"&&f.indexOf("#")<0){f="#"+f}return f.toUpperCase()}}}());YAHOO.register("dom",YAHOO.util.Dom,{version:"2.9.0",build:"2800"});YAHOO.util.Connect={_msxml_progid:["Microsoft.XMLHTTP","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP"],_http_headers:{},_has_http_headers:false,_use_default_post_header:true,_default_post_header:"application/x-www-form-urlencoded; charset=UTF-8",_default_form_header:"application/x-www-form-urlencoded",_use_default_xhr_header:true,_default_xhr_header:"XMLHttpRequest",_has_default_headers:true,_isFormSubmit:false,_default_headers:{},_poll:{},_timeOut:{},_polling_interval:50,_transaction_id:0,startEvent:new YAHOO.util.CustomEvent("start"),completeEvent:new YAHOO.util.CustomEvent("complete"),successEvent:new YAHOO.util.CustomEvent("success"),failureEvent:new YAHOO.util.CustomEvent("failure"),abortEvent:new YAHOO.util.CustomEvent("abort"),_customEvents:{onStart:["startEvent","start"],onComplete:["completeEvent","complete"],onSuccess:["successEvent","success"],onFailure:["failureEvent","failure"],onUpload:["uploadEvent","upload"],onAbort:["abortEvent","abort"]},setProgId:function(a){this._msxml_progid.unshift(a)},setDefaultPostHeader:function(a){if(typeof a=="string"){this._default_post_header=a;this._use_default_post_header=true}else{if(typeof a=="boolean"){this._use_default_post_header=a}}},setDefaultXhrHeader:function(a){if(typeof a=="string"){this._default_xhr_header=a}else{this._use_default_xhr_header=a}},setPollingInterval:function(a){if(typeof a=="number"&&isFinite(a)){this._polling_interval=a}},createXhrObject:function(g){var d,a,b;try{a=new XMLHttpRequest();d={conn:a,tId:g,xhr:true}}catch(c){for(b=0;b<this._msxml_progid.length;++b){try{a=new ActiveXObject(this._msxml_progid[b]);d={conn:a,tId:g,xhr:true};break}catch(f){}}}finally{return d}},getConnectionObject:function(a){var c,d=this._transaction_id;try{if(!a){c=this.createXhrObject(d)}else{c={tId:d};if(a==="xdr"){c.conn=this._transport;c.xdr=true}else{if(a==="upload"){c.upload=true}}}if(c){this._transaction_id++}}catch(b){}return c},asyncRequest:function(h,d,g,a){var b=g&&g.argument?g.argument:null,e=this,f,c;if(this._isFileUpload){c="upload"}else{if(g&&g.xdr){c="xdr"}}f=this.getConnectionObject(c);if(!f){return null}else{if(g&&g.customevents){this.initCustomEvents(f,g)}if(this._isFormSubmit){if(this._isFileUpload){window.setTimeout(function(){e.uploadFile(f,g,d,a)},10);return f}if(h.toUpperCase()=="GET"){if(this._sFormData.length!==0){d+=((d.indexOf("?")==-1)?"?":"&")+this._sFormData}}else{if(h.toUpperCase()=="POST"){a=a?this._sFormData+"&"+a:this._sFormData}}}if(h.toUpperCase()=="GET"&&(g&&g.cache===false)){d+=((d.indexOf("?")==-1)?"?":"&")+"rnd="+new Date().valueOf().toString()}if(this._use_default_xhr_header){if(!this._default_headers["X-Requested-With"]){this.initHeader("X-Requested-With",this._default_xhr_header,true)}}if((h.toUpperCase()==="POST"&&this._use_default_post_header)&&this._isFormSubmit===false){this.initHeader("Content-Type",this._default_post_header)}if(f.xdr){this.xdr(f,h,d,g,a);return f}f.conn.open(h,d,true);if(this._has_default_headers||this._has_http_headers){this.setHeader(f)}this.handleReadyState(f,g);f.conn.send(a||"");if(this._isFormSubmit===true){this.resetFormState()}this.startEvent.fire(f,b);if(f.startEvent){f.startEvent.fire(f,b)}return f}},initCustomEvents:function(a,c){var b;for(b in c.customevents){if(this._customEvents[b][0]){a[this._customEvents[b][0]]=new YAHOO.util.CustomEvent(this._customEvents[b][1],(c.scope)?c.scope:null);a[this._customEvents[b][0]].subscribe(c.customevents[b])}}},handleReadyState:function(c,d){var b=this,a=(d&&d.argument)?d.argument:null;if(d&&d.timeout){this._timeOut[c.tId]=window.setTimeout(function(){b.abort(c,d,true)},d.timeout)}this._poll[c.tId]=window.setInterval(function(){if(c.conn&&c.conn.readyState===4){window.clearInterval(b._poll[c.tId]);delete b._poll[c.tId];if(d&&d.timeout){window.clearTimeout(b._timeOut[c.tId]);delete b._timeOut[c.tId]}b.completeEvent.fire(c,a);if(c.completeEvent){c.completeEvent.fire(c,a)}b.handleTransactionResponse(c,d)}},this._polling_interval)},handleTransactionResponse:function(b,j,d){var f,a,h=(j&&j.argument)?j.argument:null,c=(b.r&&b.r.statusText==="xdr:success")?true:false,i=(b.r&&b.r.statusText==="xdr:failure")?true:false,k=d;try{if((b.conn.status!==undefined&&b.conn.status!==0)||c){f=b.conn.status}else{if(i&&!k){f=0}else{f=13030}}}catch(g){f=13030}if((f>=200&&f<300)||f===1223||c){a=b.xdr?b.r:this.createResponseObject(b,h);if(j&&j.success){if(!j.scope){j.success(a)}else{j.success.apply(j.scope,[a])}}this.successEvent.fire(a);if(b.successEvent){b.successEvent.fire(a)}}else{switch(f){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:a=this.createExceptionObject(b.tId,h,(d?d:false));if(j&&j.failure){if(!j.scope){j.failure(a)}else{j.failure.apply(j.scope,[a])}}break;default:a=(b.xdr)?b.response:this.createResponseObject(b,h);if(j&&j.failure){if(!j.scope){j.failure(a)}else{j.failure.apply(j.scope,[a])}}}this.failureEvent.fire(a);if(b.failureEvent){b.failureEvent.fire(a)}}this.releaseObject(b);a=null},createResponseObject:function(a,h){var d={},k={},f,c,g,b;try{c=a.conn.getAllResponseHeaders();g=c.split("\n");for(f=0;f<g.length;f++){b=g[f].indexOf(":");if(b!=-1){k[g[f].substring(0,b)]=YAHOO.lang.trim(g[f].substring(b+2))}}}catch(j){}d.tId=a.tId;d.status=(a.conn.status==1223)?204:a.conn.status;d.statusText=(a.conn.status==1223)?"No Content":a.conn.statusText;d.getResponseHeader=k;d.getAllResponseHeaders=c;d.responseText=a.conn.responseText;d.responseXML=a.conn.responseXML;if(h){d.argument=h}return d},createExceptionObject:function(h,d,a){var f=0,g="communication failure",c=-1,b="transaction aborted",e={};e.tId=h;if(a){e.status=c;e.statusText=b}else{e.status=f;e.statusText=g}if(d){e.argument=d}return e},initHeader:function(a,d,c){var b=(c)?this._default_headers:this._http_headers;b[a]=d;if(c){this._has_default_headers=true}else{this._has_http_headers=true}},setHeader:function(a){var b;if(this._has_default_headers){for(b in this._default_headers){if(YAHOO.lang.hasOwnProperty(this._default_headers,b)){a.conn.setRequestHeader(b,this._default_headers[b])}}}if(this._has_http_headers){for(b in this._http_headers){if(YAHOO.lang.hasOwnProperty(this._http_headers,b)){a.conn.setRequestHeader(b,this._http_headers[b])}}this._http_headers={};this._has_http_headers=false}},resetDefaultHeaders:function(){this._default_headers={};this._has_default_headers=false},abort:function(e,g,a){var d,b=(g&&g.argument)?g.argument:null;e=e||{};if(e.conn){if(e.xhr){if(this.isCallInProgress(e)){e.conn.abort();window.clearInterval(this._poll[e.tId]);delete this._poll[e.tId];if(a){window.clearTimeout(this._timeOut[e.tId]);delete this._timeOut[e.tId]}d=true}}else{if(e.xdr){e.conn.abort(e.tId);d=true}}}else{if(e.upload){var c="yuiIO"+e.tId;var f=document.getElementById(c);if(f){YAHOO.util.Event.removeListener(f,"load");document.body.removeChild(f);if(a){window.clearTimeout(this._timeOut[e.tId]);delete this._timeOut[e.tId]}d=true}}else{d=false}}if(d===true){this.abortEvent.fire(e,b);if(e.abortEvent){e.abortEvent.fire(e,b)}this.handleTransactionResponse(e,g,true)}return d},isCallInProgress:function(a){a=a||{};if(a.xhr&&a.conn){return a.conn.readyState!==4&&a.conn.readyState!==0}else{if(a.xdr&&a.conn){return a.conn.isCallInProgress(a.tId)}else{if(a.upload===true){return document.getElementById("yuiIO"+a.tId)?true:false}else{return false}}}},releaseObject:function(a){if(a&&a.conn){a.conn=null;a=null}}};(function(){var g=YAHOO.util.Connect,h={};function d(i){var j='<object id="YUIConnectionSwf" type="application/x-shockwave-flash" data="'+i+'" width="0" height="0"><param name="movie" value="'+i+'"><param name="allowScriptAccess" value="always"></object>',k=document.createElement("div");document.body.appendChild(k);k.innerHTML=j}function b(l,i,j,n,k){h[parseInt(l.tId)]={o:l,c:n};if(k){n.method=i;n.data=k}l.conn.send(j,n,l.tId)}function e(i){d(i);g._transport=document.getElementById("YUIConnectionSwf")}function c(){g.xdrReadyEvent.fire()}function a(j,i){if(j){g.startEvent.fire(j,i.argument);if(j.startEvent){j.startEvent.fire(j,i.argument)}}}function f(j){var k=h[j.tId].o,i=h[j.tId].c;if(j.statusText==="xdr:start"){a(k,i);return}j.responseText=decodeURI(j.responseText);k.r=j;if(i.argument){k.r.argument=i.argument}this.handleTransactionResponse(k,i,j.statusText==="xdr:abort"?true:false);delete h[j.tId]}g.xdr=b;g.swf=d;g.transport=e;g.xdrReadyEvent=new YAHOO.util.CustomEvent("xdrReady");g.xdrReady=c;g.handleXdrResponse=f})();(function(){var e=YAHOO.util.Connect,g=YAHOO.util.Event,a=document.documentMode?document.documentMode:false;e._isFileUpload=false;e._formNode=null;e._sFormData=null;e._submitElementValue=null;e.uploadEvent=new YAHOO.util.CustomEvent("upload");e._hasSubmitListener=function(){if(g){g.addListener(document,"click",function(k){var j=g.getTarget(k),i=j.nodeName.toLowerCase();if((i==="input"||i==="button")&&(j.type&&j.type.toLowerCase()=="submit")){e._submitElementValue=encodeURIComponent(j.name)+"="+encodeURIComponent(j.value)}});return true}return false}();function h(w,r,m){var v,l,u,s,z,t=false,p=[],y=0,o,q,n,x,k;this.resetFormState();if(typeof w=="string"){v=(document.getElementById(w)||document.forms[w])}else{if(typeof w=="object"){v=w}else{return}}if(r){this.createFrame(m?m:null);this._isFormSubmit=true;this._isFileUpload=true;this._formNode=v;return}for(o=0,q=v.elements.length;o<q;++o){l=v.elements[o];z=l.disabled;u=l.name;if(!z&&u){u=encodeURIComponent(u)+"=";s=encodeURIComponent(l.value);switch(l.type){case"select-one":if(l.selectedIndex>-1){k=l.options[l.selectedIndex];p[y++]=u+encodeURIComponent((k.attributes.value&&k.attributes.value.specified)?k.value:k.text)}break;case"select-multiple":if(l.selectedIndex>-1){for(n=l.selectedIndex,x=l.options.length;n<x;++n){k=l.options[n];if(k.selected){p[y++]=u+encodeURIComponent((k.attributes.value&&k.attributes.value.specified)?k.value:k.text)}}}break;case"radio":case"checkbox":if(l.checked){p[y++]=u+s}break;case"file":case undefined:case"reset":case"button":break;case"submit":if(t===false){if(this._hasSubmitListener&&this._submitElementValue){p[y++]=this._submitElementValue}t=true}break;default:p[y++]=u+s}}}this._isFormSubmit=true;this._sFormData=p.join("&");this.initHeader("Content-Type",this._default_form_header);return this._sFormData}function d(){this._isFormSubmit=false;this._isFileUpload=false;this._formNode=null;this._sFormData=""}function c(i){var j="yuiIO"+this._transaction_id,l=(a===9)?true:false,k;if(YAHOO.env.ua.ie&&!l){k=document.createElement('<iframe id="'+j+'" name="'+j+'" />');if(typeof i=="boolean"){k.src="javascript:false"}}else{k=document.createElement("iframe");k.id=j;k.name=j}k.style.position="absolute";k.style.top="-1000px";k.style.left="-1000px";document.body.appendChild(k)}function f(j){var m=[],k=j.split("&"),l,n;for(l=0;l<k.length;l++){n=k[l].indexOf("=");if(n!=-1){m[l]=document.createElement("input");m[l].type="hidden";m[l].name=decodeURIComponent(k[l].substring(0,n));m[l].value=decodeURIComponent(k[l].substring(n+1));this._formNode.appendChild(m[l])}}return m}function b(m,y,n,l){var t="yuiIO"+m.tId,u="multipart/form-data",w=document.getElementById(t),p=(a>=8)?true:false,z=this,v=(y&&y.argument)?y.argument:null,x,s,k,r,j,q;j={action:this._formNode.getAttribute("action"),method:this._formNode.getAttribute("method"),target:this._formNode.getAttribute("target")};this._formNode.setAttribute("action",n);this._formNode.setAttribute("method","POST");this._formNode.setAttribute("target",t);if(YAHOO.env.ua.ie&&!p){this._formNode.setAttribute("encoding",u)}else{this._formNode.setAttribute("enctype",u)}if(l){x=this.appendPostData(l)}this._formNode.submit();this.startEvent.fire(m,v);if(m.startEvent){m.startEvent.fire(m,v)}if(y&&y.timeout){this._timeOut[m.tId]=window.setTimeout(function(){z.abort(m,y,true)},y.timeout)}if(x&&x.length>0){for(s=0;s<x.length;s++){this._formNode.removeChild(x[s])}}for(k in j){if(YAHOO.lang.hasOwnProperty(j,k)){if(j[k]){this._formNode.setAttribute(k,j[k])}else{this._formNode.removeAttribute(k)}}}this.resetFormState();q=function(){var i,A,B;if(y&&y.timeout){window.clearTimeout(z._timeOut[m.tId]);delete z._timeOut[m.tId]}z.completeEvent.fire(m,v);if(m.completeEvent){m.completeEvent.fire(m,v)}r={tId:m.tId,argument:v};try{i=w.contentWindow.document.getElementsByTagName("body")[0];A=w.contentWindow.document.getElementsByTagName("pre")[0];if(i){if(A){B=A.textContent?A.textContent:A.innerText}else{B=i.textContent?i.textContent:i.innerText}}r.responseText=B;r.responseXML=w.contentWindow.document.XMLDocument?w.contentWindow.document.XMLDocument:w.contentWindow.document}catch(o){}if(y&&y.upload){if(!y.scope){y.upload(r)}else{y.upload.apply(y.scope,[r])}}z.uploadEvent.fire(r);if(m.uploadEvent){m.uploadEvent.fire(r)}g.removeListener(w,"load",q);setTimeout(function(){document.body.removeChild(w);z.releaseObject(m)},100)};g.addListener(w,"load",q)}e.setForm=h;e.resetFormState=d;e.createFrame=c;e.appendPostData=f;e.uploadFile=b})();YAHOO.register("connection",YAHOO.util.Connect,{version:"2.9.0",build:"2800"});(function(){var b=YAHOO.util;var a=function(d,c,e,f){if(!d){}this.init(d,c,e,f)};a.NAME="Anim";a.prototype={toString:function(){var c=this.getEl()||{};var d=c.id||c.tagName;return(this.constructor.NAME+": "+d)},patterns:{noNegatives:/width|height|opacity|padding/i,offsetAttribute:/^((width|height)|(top|left))$/,defaultUnit:/width|height|top$|bottom$|left$|right$/i,offsetUnit:/\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i},doMethod:function(c,e,d){return this.method(this.currentFrame,e,d-e,this.totalFrames)},setAttribute:function(c,f,e){var d=this.getEl();if(this.patterns.noNegatives.test(c)){f=(f>0)?f:0}if(c in d&&!("style" in d&&c in d.style)){d[c]=f}else{b.Dom.setStyle(d,c,f+e)}},getAttribute:function(c){var e=this.getEl();var g=b.Dom.getStyle(e,c);if(g!=="auto"&&!this.patterns.offsetUnit.test(g)){return parseFloat(g)}var d=this.patterns.offsetAttribute.exec(c)||[];var h=!!(d[3]);var f=!!(d[2]);if("style" in e){if(f||(b.Dom.getStyle(e,"position")=="absolute"&&h)){g=e["offset"+d[0].charAt(0).toUpperCase()+d[0].substr(1)]}else{g=0}}else{if(c in e){g=e[c]}}return g},getDefaultUnit:function(c){if(this.patterns.defaultUnit.test(c)){return"px"}return""},setRuntimeAttribute:function(d){var j;var e;var f=this.attributes;this.runtimeAttributes[d]={};var h=function(i){return(typeof i!=="undefined")};if(!h(f[d]["to"])&&!h(f[d]["by"])){return false}j=(h(f[d]["from"]))?f[d]["from"]:this.getAttribute(d);if(h(f[d]["to"])){e=f[d]["to"]}else{if(h(f[d]["by"])){if(j.constructor==Array){e=[];for(var g=0,c=j.length;g<c;++g){e[g]=j[g]+f[d]["by"][g]*1}}else{e=j+f[d]["by"]*1}}}this.runtimeAttributes[d].start=j;this.runtimeAttributes[d].end=e;this.runtimeAttributes[d].unit=(h(f[d].unit))?f[d]["unit"]:this.getDefaultUnit(d);return true},init:function(f,c,h,i){var d=false;var e=null;var g=0;f=b.Dom.get(f);this.attributes=c||{};this.duration=!YAHOO.lang.isUndefined(h)?h:1;this.method=i||b.Easing.easeNone;this.useSeconds=true;this.currentFrame=0;this.totalFrames=b.AnimMgr.fps;this.setEl=function(j){f=b.Dom.get(j)};this.getEl=function(){return f};this.isAnimated=function(){return d};this.getStartTime=function(){return e};this.runtimeAttributes={};this.animate=function(){if(this.isAnimated()){return false}this.currentFrame=0;this.totalFrames=(this.useSeconds)?Math.ceil(b.AnimMgr.fps*this.duration):this.duration;if(this.duration===0&&this.useSeconds){this.totalFrames=1}b.AnimMgr.registerElement(this);return true};this.stop=function(j){if(!this.isAnimated()){return false}if(j){this.currentFrame=this.totalFrames;this._onTween.fire()}b.AnimMgr.stop(this)};this._handleStart=function(){this.onStart.fire();this.runtimeAttributes={};for(var j in this.attributes){if(this.attributes.hasOwnProperty(j)){this.setRuntimeAttribute(j)}}d=true;g=0;e=new Date()};this._handleTween=function(){var l={duration:new Date()-this.getStartTime(),currentFrame:this.currentFrame};l.toString=function(){return("duration: "+l.duration+", currentFrame: "+l.currentFrame)};this.onTween.fire(l);var k=this.runtimeAttributes;for(var j in k){if(k.hasOwnProperty(j)){this.setAttribute(j,this.doMethod(j,k[j].start,k[j].end),k[j].unit)}}this.afterTween.fire(l);g+=1};this._handleComplete=function(){var j=(new Date()-e)/1000;var k={duration:j,frames:g,fps:g/j};k.toString=function(){return("duration: "+k.duration+", frames: "+k.frames+", fps: "+k.fps)};d=false;g=0;this.onComplete.fire(k)};this._onStart=new b.CustomEvent("_start",this,true);this.onStart=new b.CustomEvent("start",this);this.onTween=new b.CustomEvent("tween",this);this.afterTween=new b.CustomEvent("afterTween",this);this._onTween=new b.CustomEvent("_tween",this,true);this.onComplete=new b.CustomEvent("complete",this);this._onComplete=new b.CustomEvent("_complete",this,true);this._onStart.subscribe(this._handleStart);this._onTween.subscribe(this._handleTween);this._onComplete.subscribe(this._handleComplete)}};b.Anim=a})();YAHOO.util.AnimMgr=new function(){var e=null;var c=[];var g=0;this.fps=1000;this.delay=20;this.registerElement=function(j){c[c.length]=j;g+=1;j._onStart.fire();this.start()};var f=[];var d=false;var h=function(){var j=f.shift();b.apply(YAHOO.util.AnimMgr,j);if(f.length){arguments.callee()}};var b=function(k,j){j=j||a(k);if(!k.isAnimated()||j===-1){return false}k._onComplete.fire();c.splice(j,1);g-=1;if(g<=0){this.stop()}return true};this.unRegister=function(){f.push(arguments);if(!d){d=true;h();d=false}};this.start=function(){if(e===null){e=setInterval(this.run,this.delay)}};this.stop=function(l){if(!l){clearInterval(e);for(var k=0,j=c.length;k<j;++k){this.unRegister(c[0],0)}c=[];e=null;g=0}else{this.unRegister(l)}};this.run=function(){for(var l=0,j=c.length;l<j;++l){var k=c[l];if(!k||!k.isAnimated()){continue}if(k.currentFrame<k.totalFrames||k.totalFrames===null){k.currentFrame+=1;if(k.useSeconds){i(k)}k._onTween.fire()}else{YAHOO.util.AnimMgr.stop(k,l)}}};var a=function(l){for(var k=0,j=c.length;k<j;++k){if(c[k]===l){return k}}return -1};var i=function(k){var n=k.totalFrames;var m=k.currentFrame;var l=(k.currentFrame*k.duration*1000/k.totalFrames);var j=(new Date()-k.getStartTime());var o=0;if(j<k.duration*1000){o=Math.round((j/l-1)*k.currentFrame)}else{o=n-(m+1)}if(o>0&&isFinite(o)){if(k.currentFrame+o>=n){o=n-(m+1)}k.currentFrame+=o}};this._queue=c;this._getIndex=a};YAHOO.util.Bezier=new function(){this.getPosition=function(e,d){var f=e.length;var c=[];for(var b=0;b<f;++b){c[b]=[e[b][0],e[b][1]]}for(var a=1;a<f;++a){for(b=0;b<f-a;++b){c[b][0]=(1-d)*c[b][0]+d*c[parseInt(b+1,10)][0];c[b][1]=(1-d)*c[b][1]+d*c[parseInt(b+1,10)][1]}}return[c[0][0],c[0][1]]}};(function(){var a=function(f,e,g,h){a.superclass.constructor.call(this,f,e,g,h)};a.NAME="ColorAnim";a.DEFAULT_BGCOLOR="#fff";var c=YAHOO.util;YAHOO.extend(a,c.Anim);var d=a.superclass;var b=a.prototype;b.patterns.color=/color$/i;b.patterns.rgb=/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i;b.patterns.hex=/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i;b.patterns.hex3=/^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i;b.patterns.transparent=/^transparent|rgba\(0, 0, 0, 0\)$/;b.parseColor=function(e){if(e.length==3){return e}var f=this.patterns.hex.exec(e);if(f&&f.length==4){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}f=this.patterns.rgb.exec(e);if(f&&f.length==4){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}f=this.patterns.hex3.exec(e);if(f&&f.length==4){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}return null};b.getAttribute=function(e){var g=this.getEl();if(this.patterns.color.test(e)){var i=YAHOO.util.Dom.getStyle(g,e);var h=this;if(this.patterns.transparent.test(i)){var f=YAHOO.util.Dom.getAncestorBy(g,function(j){return !h.patterns.transparent.test(i)});if(f){i=c.Dom.getStyle(f,e)}else{i=a.DEFAULT_BGCOLOR}}}else{i=d.getAttribute.call(this,e)}return i};b.doMethod=function(f,k,g){var j;if(this.patterns.color.test(f)){j=[];for(var h=0,e=k.length;h<e;++h){j[h]=d.doMethod.call(this,f,k[h],g[h])}j="rgb("+Math.floor(j[0])+","+Math.floor(j[1])+","+Math.floor(j[2])+")"}else{j=d.doMethod.call(this,f,k,g)}return j};b.setRuntimeAttribute=function(f){d.setRuntimeAttribute.call(this,f);if(this.patterns.color.test(f)){var h=this.attributes;var k=this.parseColor(this.runtimeAttributes[f].start);var g=this.parseColor(this.runtimeAttributes[f].end);if(typeof h[f]["to"]==="undefined"&&typeof h[f]["by"]!=="undefined"){g=this.parseColor(h[f].by);for(var j=0,e=k.length;j<e;++j){g[j]=k[j]+g[j]}}this.runtimeAttributes[f].start=k;this.runtimeAttributes[f].end=g}};c.ColorAnim=a})();
-/*!
-TERMS OF USE - EASING EQUATIONS
-Open source under the BSD License.
-Copyright 2001 Robert Penner All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- * Neither the name of the author nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-YAHOO.util.Easing={easeNone:function(e,a,g,f){return g*e/f+a},easeIn:function(e,a,g,f){return g*(e/=f)*e+a},easeOut:function(e,a,g,f){return -g*(e/=f)*(e-2)+a},easeBoth:function(e,a,g,f){if((e/=f/2)<1){return g/2*e*e+a}return -g/2*((--e)*(e-2)-1)+a},easeInStrong:function(e,a,g,f){return g*(e/=f)*e*e*e+a},easeOutStrong:function(e,a,g,f){return -g*((e=e/f-1)*e*e*e-1)+a},easeBothStrong:function(e,a,g,f){if((e/=f/2)<1){return g/2*e*e*e*e+a}return -g/2*((e-=2)*e*e*e-2)+a},elasticIn:function(g,e,k,j,f,i){if(g==0){return e}if((g/=j)==1){return e+k}if(!i){i=j*0.3}if(!f||f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}return -(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e},elasticOut:function(g,e,k,j,f,i){if(g==0){return e}if((g/=j)==1){return e+k}if(!i){i=j*0.3}if(!f||f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}return f*Math.pow(2,-10*g)*Math.sin((g*j-h)*(2*Math.PI)/i)+k+e},elasticBoth:function(g,e,k,j,f,i){if(g==0){return e}if((g/=j/2)==2){return e+k}if(!i){i=j*(0.3*1.5)}if(!f||f<Math.abs(k)){f=k;var h=i/4}else{var h=i/(2*Math.PI)*Math.asin(k/f)}if(g<1){return -0.5*(f*Math.pow(2,10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i))+e}return f*Math.pow(2,-10*(g-=1))*Math.sin((g*j-h)*(2*Math.PI)/i)*0.5+k+e},backIn:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158}return h*(e/=g)*e*((f+1)*e-f)+a},backOut:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158}return h*((e=e/g-1)*e*((f+1)*e+f)+1)+a},backBoth:function(e,a,h,g,f){if(typeof f=="undefined"){f=1.70158}if((e/=g/2)<1){return h/2*(e*e*(((f*=(1.525))+1)*e-f))+a}return h/2*((e-=2)*e*(((f*=(1.525))+1)*e+f)+2)+a},bounceIn:function(e,a,g,f){return g-YAHOO.util.Easing.bounceOut(f-e,0,g,f)+a},bounceOut:function(e,a,g,f){if((e/=f)<(1/2.75)){return g*(7.5625*e*e)+a}else{if(e<(2/2.75)){return g*(7.5625*(e-=(1.5/2.75))*e+0.75)+a}else{if(e<(2.5/2.75)){return g*(7.5625*(e-=(2.25/2.75))*e+0.9375)+a}}}return g*(7.5625*(e-=(2.625/2.75))*e+0.984375)+a},bounceBoth:function(e,a,g,f){if(e<f/2){return YAHOO.util.Easing.bounceIn(e*2,0,g,f)*0.5+a}return YAHOO.util.Easing.bounceOut(e*2-f,0,g,f)*0.5+g*0.5+a}};(function(){var a=function(h,g,i,j){if(h){a.superclass.constructor.call(this,h,g,i,j)}};a.NAME="Motion";var e=YAHOO.util;YAHOO.extend(a,e.ColorAnim);var f=a.superclass;var c=a.prototype;c.patterns.points=/^points$/i;c.setAttribute=function(g,i,h){if(this.patterns.points.test(g)){h=h||"px";f.setAttribute.call(this,"left",i[0],h);f.setAttribute.call(this,"top",i[1],h)}else{f.setAttribute.call(this,g,i,h)}};c.getAttribute=function(g){if(this.patterns.points.test(g)){var h=[f.getAttribute.call(this,"left"),f.getAttribute.call(this,"top")]}else{h=f.getAttribute.call(this,g)}return h};c.doMethod=function(g,k,h){var j=null;if(this.patterns.points.test(g)){var i=this.method(this.currentFrame,0,100,this.totalFrames)/100;j=e.Bezier.getPosition(this.runtimeAttributes[g],i)}else{j=f.doMethod.call(this,g,k,h)}return j};c.setRuntimeAttribute=function(q){if(this.patterns.points.test(q)){var h=this.getEl();var k=this.attributes;var g;var m=k.points["control"]||[];var j;var n,p;if(m.length>0&&!(m[0] instanceof Array)){m=[m]}else{var l=[];for(n=0,p=m.length;n<p;++n){l[n]=m[n]}m=l}if(e.Dom.getStyle(h,"position")=="static"){e.Dom.setStyle(h,"position","relative")}if(d(k.points["from"])){e.Dom.setXY(h,k.points["from"])}else{e.Dom.setXY(h,e.Dom.getXY(h))}g=this.getAttribute("points");if(d(k.points["to"])){j=b.call(this,k.points["to"],g);var o=e.Dom.getXY(this.getEl());for(n=0,p=m.length;n<p;++n){m[n]=b.call(this,m[n],g)}}else{if(d(k.points["by"])){j=[g[0]+k.points["by"][0],g[1]+k.points["by"][1]];for(n=0,p=m.length;n<p;++n){m[n]=[g[0]+m[n][0],g[1]+m[n][1]]}}}this.runtimeAttributes[q]=[g];if(m.length>0){this.runtimeAttributes[q]=this.runtimeAttributes[q].concat(m)}this.runtimeAttributes[q][this.runtimeAttributes[q].length]=j}else{f.setRuntimeAttribute.call(this,q)}};var b=function(g,i){var h=e.Dom.getXY(this.getEl());g=[g[0]-h[0]+i[0],g[1]-h[1]+i[1]];return g};var d=function(g){return(typeof g!=="undefined")};e.Motion=a})();(function(){var d=function(f,e,g,h){if(f){d.superclass.constructor.call(this,f,e,g,h)}};d.NAME="Scroll";var b=YAHOO.util;YAHOO.extend(d,b.ColorAnim);var c=d.superclass;var a=d.prototype;a.doMethod=function(e,h,f){var g=null;if(e=="scroll"){g=[this.method(this.currentFrame,h[0],f[0]-h[0],this.totalFrames),this.method(this.currentFrame,h[1],f[1]-h[1],this.totalFrames)]}else{g=c.doMethod.call(this,e,h,f)}return g};a.getAttribute=function(e){var g=null;var f=this.getEl();if(e=="scroll"){g=[f.scrollLeft,f.scrollTop]}else{g=c.getAttribute.call(this,e)}return g};a.setAttribute=function(e,h,g){var f=this.getEl();if(e=="scroll"){f.scrollLeft=h[0];f.scrollTop=h[1]}else{c.setAttribute.call(this,e,h,g)}};b.Scroll=d})();YAHOO.register("animation",YAHOO.util.Anim,{version:"2.9.0",build:"2800"});if(!YAHOO.util.DragDropMgr){YAHOO.util.DragDropMgr=function(){var a=YAHOO.util.Event,b=YAHOO.util.Dom;return{useShim:false,_shimActive:false,_shimState:false,_debugShim:false,_createShim:function(){var c=document.createElement("div");c.id="yui-ddm-shim";if(document.body.firstChild){document.body.insertBefore(c,document.body.firstChild)}else{document.body.appendChild(c)}c.style.display="none";c.style.backgroundColor="red";c.style.position="absolute";c.style.zIndex="99999";b.setStyle(c,"opacity","0");this._shim=c;a.on(c,"mouseup",this.handleMouseUp,this,true);a.on(c,"mousemove",this.handleMouseMove,this,true);a.on(window,"scroll",this._sizeShim,this,true)},_sizeShim:function(){if(this._shimActive){var c=this._shim;c.style.height=b.getDocumentHeight()+"px";c.style.width=b.getDocumentWidth()+"px";c.style.top="0";c.style.left="0"}},_activateShim:function(){if(this.useShim){if(!this._shim){this._createShim()}this._shimActive=true;var c=this._shim,d="0";if(this._debugShim){d=".5"}b.setStyle(c,"opacity",d);this._sizeShim();c.style.display="block"}},_deactivateShim:function(){this._shim.style.display="none";this._shimActive=false},_shim:null,ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,interactionInfo:null,init:function(){this.initialized=true},POINT:0,INTERSECT:1,STRICT_INTERSECT:2,mode:0,_execOnAll:function(e,d){for(var f in this.ids){for(var c in this.ids[f]){var g=this.ids[f][c];if(!this.isTypeOfDD(g)){continue}g[e].apply(g,d)}}},_onLoad:function(){this.init();a.on(document,"mouseup",this.handleMouseUp,this,true);a.on(document,"mousemove",this.handleMouseMove,this,true);a.on(window,"unload",this._onUnload,this,true);a.on(window,"resize",this._onResize,this,true)},_onResize:function(c){this._execOnAll("resetConstraints",[])},lock:function(){this.locked=true},unlock:function(){this.locked=false},isLocked:function(){return this.locked},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:1000,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,fromTimeout:false,regDragDrop:function(d,c){if(!this.initialized){this.init()}if(!this.ids[c]){this.ids[c]={}}this.ids[c][d.id]=d},removeDDFromGroup:function(e,c){if(!this.ids[c]){this.ids[c]={}}var d=this.ids[c];if(d&&d[e.id]){delete d[e.id]}},_remove:function(e){for(var d in e.groups){if(d){var c=this.ids[d];if(c&&c[e.id]){delete c[e.id]}}}delete this.handleIds[e.id]},regHandle:function(d,c){if(!this.handleIds[d]){this.handleIds[d]={}}this.handleIds[d][c]=c},isDragDrop:function(c){return(this.getDDById(c))?true:false},getRelated:function(h,d){var g=[];for(var f in h.groups){for(var e in this.ids[f]){var c=this.ids[f][e];if(!this.isTypeOfDD(c)){continue}if(!d||c.isTarget){g[g.length]=c}}}return g},isLegalTarget:function(g,f){var d=this.getRelated(g,true);for(var e=0,c=d.length;e<c;++e){if(d[e].id==f.id){return true}}return false},isTypeOfDD:function(c){return(c&&c.__ygDragDrop)},isHandle:function(d,c){return(this.handleIds[d]&&this.handleIds[d][c])},getDDById:function(d){for(var c in this.ids){if(this.ids[c][d]){return this.ids[c][d]}}return null},handleMouseDown:function(f,d){this.currentTarget=YAHOO.util.Event.getTarget(f);this.dragCurrent=d;var c=d.getEl();this.startX=YAHOO.util.Event.getPageX(f);this.startY=YAHOO.util.Event.getPageY(f);this.deltaX=this.startX-c.offsetLeft;this.deltaY=this.startY-c.offsetTop;this.dragThreshMet=false;this.clickTimeout=setTimeout(function(){var e=YAHOO.util.DDM;e.startDrag(e.startX,e.startY);e.fromTimeout=true},this.clickTimeThresh)},startDrag:function(c,e){if(this.dragCurrent&&this.dragCurrent.useShim){this._shimState=this.useShim;this.useShim=true}this._activateShim();clearTimeout(this.clickTimeout);var d=this.dragCurrent;if(d&&d.events.b4StartDrag){d.b4StartDrag(c,e);d.fireEvent("b4StartDragEvent",{x:c,y:e})}if(d&&d.events.startDrag){d.startDrag(c,e);d.fireEvent("startDragEvent",{x:c,y:e})}this.dragThreshMet=true},handleMouseUp:function(c){if(this.dragCurrent){clearTimeout(this.clickTimeout);if(this.dragThreshMet){if(this.fromTimeout){this.fromTimeout=false;this.handleMouseMove(c)}this.fromTimeout=false;this.fireEvents(c,true)}else{}this.stopDrag(c);this.stopEvent(c)}},stopEvent:function(c){if(this.stopPropagation){YAHOO.util.Event.stopPropagation(c)}if(this.preventDefault){YAHOO.util.Event.preventDefault(c)}},stopDrag:function(f,d){var c=this.dragCurrent;if(c&&!d){if(this.dragThreshMet){if(c.events.b4EndDrag){c.b4EndDrag(f);c.fireEvent("b4EndDragEvent",{e:f})}if(c.events.endDrag){c.endDrag(f);c.fireEvent("endDragEvent",{e:f})}}if(c.events.mouseUp){c.onMouseUp(f);c.fireEvent("mouseUpEvent",{e:f})}}if(this._shimActive){this._deactivateShim();if(this.dragCurrent&&this.dragCurrent.useShim){this.useShim=this._shimState;this._shimState=false}}this.dragCurrent=null;this.dragOvers={}},handleMouseMove:function(g){var c=this.dragCurrent;if(c){if(YAHOO.env.ua.ie&&(YAHOO.env.ua.ie<9)&&!g.button){this.stopEvent(g);return this.handleMouseUp(g)}else{if(g.clientX<0||g.clientY<0){}}if(!this.dragThreshMet){var f=Math.abs(this.startX-YAHOO.util.Event.getPageX(g));var d=Math.abs(this.startY-YAHOO.util.Event.getPageY(g));if(f>this.clickPixelThresh||d>this.clickPixelThresh){this.startDrag(this.startX,this.startY)}}if(this.dragThreshMet){if(c&&c.events.b4Drag){c.b4Drag(g);c.fireEvent("b4DragEvent",{e:g})}if(c&&c.events.drag){c.onDrag(g);c.fireEvent("dragEvent",{e:g})}if(c){this.fireEvents(g,false)}}this.stopEvent(g)}},fireEvents:function(A,o){var F=this.dragCurrent;if(!F||F.isLocked()||F.dragOnly){return}var q=YAHOO.util.Event.getPageX(A),p=YAHOO.util.Event.getPageY(A),s=new YAHOO.util.Point(q,p),m=F.getTargetCoord(s.x,s.y),g=F.getDragEl(),f=["out","over","drop","enter"],z=new YAHOO.util.Region(m.y,m.x+g.offsetWidth,m.y+g.offsetHeight,m.x),k=[],d={},n={},t=[],G={outEvts:[],overEvts:[],dropEvts:[],enterEvts:[]};for(var v in this.dragOvers){var H=this.dragOvers[v];if(!this.isTypeOfDD(H)){continue}if(!this.isOverTarget(s,H,this.mode,z)){G.outEvts.push(H)}k[v]=true;delete this.dragOvers[v]}for(var u in F.groups){if("string"!=typeof u){continue}for(v in this.ids[u]){var h=this.ids[u][v];if(!this.isTypeOfDD(h)){continue}if(h.isTarget&&!h.isLocked()&&h!=F){if(this.isOverTarget(s,h,this.mode,z)){d[u]=true;if(o){G.dropEvts.push(h)}else{if(!k[h.id]){G.enterEvts.push(h)}else{G.overEvts.push(h)}this.dragOvers[h.id]=h}}}}}this.interactionInfo={out:G.outEvts,enter:G.enterEvts,over:G.overEvts,drop:G.dropEvts,point:s,draggedRegion:z,sourceRegion:this.locationCache[F.id],validDrop:o};for(var c in d){t.push(c)}if(o&&!G.dropEvts.length){this.interactionInfo.validDrop=false;if(F.events.invalidDrop){F.onInvalidDrop(A);F.fireEvent("invalidDropEvent",{e:A})}}for(v=0;v<f.length;v++){var D=null;if(G[f[v]+"Evts"]){D=G[f[v]+"Evts"]}if(D&&D.length){var j=f[v].charAt(0).toUpperCase()+f[v].substr(1),C="onDrag"+j,l="b4Drag"+j,r="drag"+j+"Event",B="drag"+j;if(this.mode){if(F.events[l]){F[l](A,D,t);n[C]=F.fireEvent(l+"Event",{event:A,info:D,group:t})}if(F.events[B]&&(n[C]!==false)){F[C](A,D,t);F.fireEvent(r,{event:A,info:D,group:t})}}else{for(var E=0,w=D.length;E<w;++E){if(F.events[l]){F[l](A,D[E].id,t[0]);n[C]=F.fireEvent(l+"Event",{event:A,info:D[E].id,group:t[0]})}if(F.events[B]&&(n[C]!==false)){F[C](A,D[E].id,t[0]);F.fireEvent(r,{event:A,info:D[E].id,group:t[0]})}}}}}},getBestMatch:function(e){var g=null;var d=e.length;if(d==1){g=e[0]}else{for(var f=0;f<d;++f){var c=e[f];if(this.mode==this.INTERSECT&&c.cursorIsOver){g=c;break}else{if(!g||!g.overlap||(c.overlap&&g.overlap.getArea()<c.overlap.getArea())){g=c}}}}return g},refreshCache:function(d){var f=d||this.ids;for(var c in f){if("string"!=typeof c){continue}for(var e in this.ids[c]){var h=this.ids[c][e];if(this.isTypeOfDD(h)){var j=this.getLocation(h);if(j){this.locationCache[h.id]=j}else{delete this.locationCache[h.id]}}}}},verifyEl:function(d){try{if(d){var c=d.offsetParent;if(c){return true}}}catch(f){}return false},getLocation:function(i){if(!this.isTypeOfDD(i)){return null}var g=i.getEl(),m,f,d,o,n,p,c,k,h;try{m=YAHOO.util.Dom.getXY(g)}catch(j){}if(!m){return null}f=m[0];d=f+g.offsetWidth;o=m[1];n=o+g.offsetHeight;p=o-i.padding[0];c=d+i.padding[1];k=n+i.padding[2];h=f-i.padding[3];return new YAHOO.util.Region(p,c,k,h)},isOverTarget:function(k,c,e,f){var g=this.locationCache[c.id];if(!g||!this.useCache){g=this.getLocation(c);this.locationCache[c.id]=g}if(!g){return false}c.cursorIsOver=g.contains(k);var j=this.dragCurrent;if(!j||(!e&&!j.constrainX&&!j.constrainY)){return c.cursorIsOver}c.overlap=null;if(!f){var h=j.getTargetCoord(k.x,k.y);var d=j.getDragEl();f=new YAHOO.util.Region(h.y,h.x+d.offsetWidth,h.y+d.offsetHeight,h.x)}var i=f.intersect(g);if(i){c.overlap=i;return(e)?true:c.cursorIsOver}else{return false}},_onUnload:function(d,c){this.unregAll()},unregAll:function(){if(this.dragCurrent){this.stopDrag();this.dragCurrent=null}this._execOnAll("unreg",[]);this.ids={}},elementCache:{},getElWrapper:function(d){var c=this.elementCache[d];if(!c||!c.el){c=this.elementCache[d]=new this.ElementWrapper(YAHOO.util.Dom.get(d))}return c},getElement:function(c){return YAHOO.util.Dom.get(c)},getCss:function(d){var c=YAHOO.util.Dom.get(d);return(c)?c.style:null},ElementWrapper:function(c){this.el=c||null;this.id=this.el&&c.id;this.css=this.el&&c.style},getPosX:function(c){return YAHOO.util.Dom.getX(c)},getPosY:function(c){return YAHOO.util.Dom.getY(c)},swapNode:function(e,c){if(e.swapNode){e.swapNode(c)}else{var f=c.parentNode;var d=c.nextSibling;if(d==e){f.insertBefore(e,c)}else{if(c==e.nextSibling){f.insertBefore(c,e)}else{e.parentNode.replaceChild(c,e);f.insertBefore(e,d)}}}},getScroll:function(){var e,c,f=document.documentElement,d=document.body;if(f&&(f.scrollTop||f.scrollLeft)){e=f.scrollTop;c=f.scrollLeft}else{if(d){e=d.scrollTop;c=d.scrollLeft}else{}}return{top:e,left:c}},getStyle:function(d,c){return YAHOO.util.Dom.getStyle(d,c)},getScrollTop:function(){return this.getScroll().top},getScrollLeft:function(){return this.getScroll().left},moveToEl:function(c,e){var d=YAHOO.util.Dom.getXY(e);YAHOO.util.Dom.setXY(c,d)},getClientHeight:function(){return YAHOO.util.Dom.getViewportHeight()},getClientWidth:function(){return YAHOO.util.Dom.getViewportWidth()},numericSort:function(d,c){return(d-c)},_timeoutCount:0,_addListeners:function(){var c=YAHOO.util.DDM;if(YAHOO.util.Event&&document){c._onLoad()}else{if(c._timeoutCount>2000){}else{setTimeout(c._addListeners,10);if(document&&document.body){c._timeoutCount+=1}}}},handleWasClicked:function(c,e){if(this.isHandle(e,c.id)){return true}else{var d=c.parentNode;while(d){if(this.isHandle(e,d.id)){return true}else{d=d.parentNode}}}return false}}}();YAHOO.util.DDM=YAHOO.util.DragDropMgr;YAHOO.util.DDM._addListeners()}(function(){var a=YAHOO.util.Event;var b=YAHOO.util.Dom;YAHOO.util.DragDrop=function(e,c,d){if(e){this.init(e,c,d)}};YAHOO.util.DragDrop.prototype={events:null,on:function(){this.subscribe.apply(this,arguments)},id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true},unlock:function(){this.locked=false},isTarget:true,padding:null,dragOnly:false,useShim:false,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,deltaX:0,deltaY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,cursorIsOver:false,overlap:null,b4StartDrag:function(c,d){},startDrag:function(c,d){},b4Drag:function(c){},onDrag:function(c){},onDragEnter:function(c,d){},b4DragOver:function(c){},onDragOver:function(c,d){},b4DragOut:function(c){},onDragOut:function(c,d){},b4DragDrop:function(c){},onDragDrop:function(c,d){},onInvalidDrop:function(c){},b4EndDrag:function(c){},endDrag:function(c){},b4MouseDown:function(c){},onMouseDown:function(c){},onMouseUp:function(c){},onAvailable:function(){},getEl:function(){if(!this._domRef){this._domRef=b.get(this.id)}return this._domRef},getDragEl:function(){return b.get(this.dragElId)},init:function(f,c,d){this.initTarget(f,c,d);a.on(this._domRef||this.id,"mousedown",this.handleMouseDown,this,true);for(var e in this.events){this.createEvent(e+"Event")}},initTarget:function(e,c,d){this.config=d||{};this.events={};this.DDM=YAHOO.util.DDM;this.groups={};if(typeof e!=="string"){this._domRef=e;e=b.generateId(e)}this.id=e;this.addToGroup((c)?c:"default");this.handleElId=e;a.onAvailable(e,this.handleOnAvailable,this,true);this.setDragElId(e);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig()},applyConfig:function(){this.events={mouseDown:true,b4MouseDown:true,mouseUp:true,b4StartDrag:true,startDrag:true,b4EndDrag:true,endDrag:true,drag:true,b4Drag:true,invalidDrop:true,b4DragOut:true,dragOut:true,dragEnter:true,b4DragOver:true,dragOver:true,b4DragDrop:true,dragDrop:true};if(this.config.events){for(var c in this.config.events){if(this.config.events[c]===false){this.events[c]=false}}}this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false);this.dragOnly=((this.config.dragOnly===true)?true:false);this.useShim=((this.config.useShim===true)?true:false)},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable()},setPadding:function(e,c,f,d){if(!c&&0!==c){this.padding=[e,e,e,e]}else{if(!f&&0!==f){this.padding=[e,c,e,c]}else{this.padding=[e,c,f,d]}}},setInitPosition:function(f,e){var g=this.getEl();if(!this.DDM.verifyEl(g)){if(g&&g.style&&(g.style.display=="none")){}else{}return}var d=f||0;var c=e||0;var h=b.getXY(g);this.initPageX=h[0]-d;this.initPageY=h[1]-c;this.lastPageX=h[0];this.lastPageY=h[1];this.setStartPosition(h)},setStartPosition:function(d){var c=d||b.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=c[0];this.startPageY=c[1]},addToGroup:function(c){this.groups[c]=true;this.DDM.regDragDrop(this,c)},removeFromGroup:function(c){if(this.groups[c]){delete this.groups[c]}this.DDM.removeDDFromGroup(this,c)},setDragElId:function(c){this.dragElId=c},setHandleElId:function(c){if(typeof c!=="string"){c=b.generateId(c)}this.handleElId=c;this.DDM.regHandle(this.id,c)},setOuterHandleElId:function(c){if(typeof c!=="string"){c=b.generateId(c)}a.on(c,"mousedown",this.handleMouseDown,this,true);this.setHandleElId(c);this.hasOuterHandles=true},unreg:function(){a.removeListener(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this)},isLocked:function(){return(this.DDM.isLocked()||this.locked)},handleMouseDown:function(k,j){var d=k.which||k.button;if(this.primaryButtonOnly&&d>1){return}if(this.isLocked()){return}var c=this.b4MouseDown(k),g=true;if(this.events.b4MouseDown){g=this.fireEvent("b4MouseDownEvent",k)}var f=this.onMouseDown(k),i=true;if(this.events.mouseDown){if(f===false){i=false}else{i=this.fireEvent("mouseDownEvent",k)}}if((c===false)||(f===false)||(g===false)||(i===false)){return}this.DDM.refreshCache(this.groups);var h=new YAHOO.util.Point(a.getPageX(k),a.getPageY(k));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(h,this)){}else{if(this.clickValidator(k)){this.setStartPosition();this.DDM.handleMouseDown(k,this);this.DDM.stopEvent(k)}else{}}},clickValidator:function(d){var c=YAHOO.util.Event.getTarget(d);return(this.isValidHandleChild(c)&&(this.id==this.handleElId||this.DDM.handleWasClicked(c,this.id)))},getTargetCoord:function(e,d){var c=e-this.deltaX;var f=d-this.deltaY;if(this.constrainX){if(c<this.minX){c=this.minX}if(c>this.maxX){c=this.maxX}}if(this.constrainY){if(f<this.minY){f=this.minY}if(f>this.maxY){f=this.maxY}}c=this.getTick(c,this.xTicks);f=this.getTick(f,this.yTicks);return{x:c,y:f}},addInvalidHandleType:function(c){var d=c.toUpperCase();this.invalidHandleTypes[d]=d},addInvalidHandleId:function(c){if(typeof c!=="string"){c=b.generateId(c)}this.invalidHandleIds[c]=c},addInvalidHandleClass:function(c){this.invalidHandleClasses.push(c)},removeInvalidHandleType:function(c){var d=c.toUpperCase();delete this.invalidHandleTypes[d]},removeInvalidHandleId:function(c){if(typeof c!=="string"){c=b.generateId(c)}delete this.invalidHandleIds[c]},removeInvalidHandleClass:function(d){for(var e=0,c=this.invalidHandleClasses.length;e<c;++e){if(this.invalidHandleClasses[e]==d){delete this.invalidHandleClasses[e]}}},isValidHandleChild:function(g){var f=true;var j;try{j=g.nodeName.toUpperCase()}catch(h){j=g.nodeName}f=f&&!this.invalidHandleTypes[j];f=f&&!this.invalidHandleIds[g.id];for(var d=0,c=this.invalidHandleClasses.length;f&&d<c;++d){f=!b.hasClass(g,this.invalidHandleClasses[d])}return f},setXTicks:function(f,c){this.xTicks=[];this.xTickSize=c;var e={};for(var d=this.initPageX;d>=this.minX;d=d-c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}for(d=this.initPageX;d<=this.maxX;d=d+c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}this.xTicks.sort(this.DDM.numericSort)},setYTicks:function(f,c){this.yTicks=[];this.yTickSize=c;var e={};for(var d=this.initPageY;d>=this.minY;d=d-c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}for(d=this.initPageY;d<=this.maxY;d=d+c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}this.yTicks.sort(this.DDM.numericSort)},setXConstraint:function(e,d,c){this.leftConstraint=parseInt(e,10);this.rightConstraint=parseInt(d,10);this.minX=this.initPageX-this.leftConstraint;this.maxX=this.initPageX+this.rightConstraint;if(c){this.setXTicks(this.initPageX,c)}this.constrainX=true},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks()},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0},setYConstraint:function(c,e,d){this.topConstraint=parseInt(c,10);this.bottomConstraint=parseInt(e,10);this.minY=this.initPageY-this.topConstraint;this.maxY=this.initPageY+this.bottomConstraint;if(d){this.setYTicks(this.initPageY,d)}this.constrainY=true},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var d=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var c=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(d,c)}else{this.setInitPosition()}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize)}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize)}},getTick:function(j,f){if(!f){return j}else{if(f[0]>=j){return f[0]}else{for(var d=0,c=f.length;d<c;++d){var e=d+1;if(f[e]&&f[e]>=j){var h=j-f[d];var g=f[e]-j;return(g>h)?f[d]:f[e]}}return f[f.length-1]}}},toString:function(){return("DragDrop "+this.id)}};YAHOO.augment(YAHOO.util.DragDrop,YAHOO.util.EventProvider)})();YAHOO.util.DD=function(c,a,b){if(c){this.init(c,a,b)}};YAHOO.extend(YAHOO.util.DD,YAHOO.util.DragDrop,{scroll:true,autoOffset:function(c,b){var a=c-this.startPageX;var d=b-this.startPageY;this.setDelta(a,d)},setDelta:function(b,a){this.deltaX=b;this.deltaY=a},setDragElPos:function(c,b){var a=this.getDragEl();this.alignElWithMouse(a,c,b)},alignElWithMouse:function(c,g,f){var e=this.getTargetCoord(g,f);if(!this.deltaSetXY){var h=[e.x,e.y];YAHOO.util.Dom.setXY(c,h);var d=parseInt(YAHOO.util.Dom.getStyle(c,"left"),10);var b=parseInt(YAHOO.util.Dom.getStyle(c,"top"),10);this.deltaSetXY=[d-e.x,b-e.y]}else{YAHOO.util.Dom.setStyle(c,"left",(e.x+this.deltaSetXY[0])+"px");YAHOO.util.Dom.setStyle(c,"top",(e.y+this.deltaSetXY[1])+"px")}this.cachePosition(e.x,e.y);var a=this;setTimeout(function(){a.autoScroll.call(a,e.x,e.y,c.offsetHeight,c.offsetWidth)},0)},cachePosition:function(b,a){if(b){this.lastPageX=b;this.lastPageY=a}else{var c=YAHOO.util.Dom.getXY(this.getEl());this.lastPageX=c[0];this.lastPageY=c[1]}},autoScroll:function(k,j,e,l){if(this.scroll){var m=this.DDM.getClientHeight();var b=this.DDM.getClientWidth();var o=this.DDM.getScrollTop();var d=this.DDM.getScrollLeft();var i=e+j;var n=l+k;var g=(m+o-j-this.deltaY);var f=(b+d-k-this.deltaX);var c=40;var a=(document.all)?80:30;if(i>m&&g<c){window.scrollTo(d,o+a)}if(j<o&&o>0&&j-o<c){window.scrollTo(d,o-a)}if(n>b&&f<c){window.scrollTo(d+a,o)}if(k<d&&d>0&&k-d<c){window.scrollTo(d-a,o)}}},applyConfig:function(){YAHOO.util.DD.superclass.applyConfig.call(this);this.scroll=(this.config.scroll!==false)},b4MouseDown:function(a){this.setStartPosition();this.autoOffset(YAHOO.util.Event.getPageX(a),YAHOO.util.Event.getPageY(a))},b4Drag:function(a){this.setDragElPos(YAHOO.util.Event.getPageX(a),YAHOO.util.Event.getPageY(a))},toString:function(){return("DD "+this.id)}});YAHOO.util.DDProxy=function(c,a,b){if(c){this.init(c,a,b);this.initFrame()}};YAHOO.util.DDProxy.dragElId="ygddfdiv";YAHOO.extend(YAHOO.util.DDProxy,YAHOO.util.DD,{resizeFrame:true,centerFrame:false,createFrame:function(){var b=this,a=document.body;if(!a||!a.firstChild){setTimeout(function(){b.createFrame()},50);return}var f=this.getDragEl(),e=YAHOO.util.Dom;if(!f){f=document.createElement("div");f.id=this.dragElId;var d=f.style;d.position="absolute";d.visibility="hidden";d.cursor="move";d.border="2px solid #aaa";d.zIndex=999;d.height="25px";d.width="25px";var c=document.createElement("div");e.setStyle(c,"height","100%");e.setStyle(c,"width","100%");e.setStyle(c,"background-color","#ccc");e.setStyle(c,"opacity","0");f.appendChild(c);a.insertBefore(f,a.firstChild)}},initFrame:function(){this.createFrame()},applyConfig:function(){YAHOO.util.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=(this.config.resizeFrame!==false);this.centerFrame=(this.config.centerFrame);this.setDragElId(this.config.dragElId||YAHOO.util.DDProxy.dragElId)},showFrame:function(e,d){var c=this.getEl();var a=this.getDragEl();var b=a.style;this._resizeProxy();if(this.centerFrame){this.setDelta(Math.round(parseInt(b.width,10)/2),Math.round(parseInt(b.height,10)/2))}this.setDragElPos(e,d);YAHOO.util.Dom.setStyle(a,"visibility","visible")},_resizeProxy:function(){if(this.resizeFrame){var h=YAHOO.util.Dom;var b=this.getEl();var c=this.getDragEl();var g=parseInt(h.getStyle(c,"borderTopWidth"),10);var i=parseInt(h.getStyle(c,"borderRightWidth"),10);var f=parseInt(h.getStyle(c,"borderBottomWidth"),10);var d=parseInt(h.getStyle(c,"borderLeftWidth"),10);if(isNaN(g)){g=0}if(isNaN(i)){i=0}if(isNaN(f)){f=0}if(isNaN(d)){d=0}var e=Math.max(0,b.offsetWidth-i-d);var a=Math.max(0,b.offsetHeight-g-f);h.setStyle(c,"width",e+"px");h.setStyle(c,"height",a+"px")}},b4MouseDown:function(b){this.setStartPosition();var a=YAHOO.util.Event.getPageX(b);var c=YAHOO.util.Event.getPageY(b);this.autoOffset(a,c)},b4StartDrag:function(a,b){this.showFrame(a,b)},b4EndDrag:function(a){YAHOO.util.Dom.setStyle(this.getDragEl(),"visibility","hidden")},endDrag:function(d){var c=YAHOO.util.Dom;var b=this.getEl();var a=this.getDragEl();c.setStyle(a,"visibility","");c.setStyle(b,"visibility","hidden");YAHOO.util.DDM.moveToEl(b,a);c.setStyle(a,"visibility","hidden");c.setStyle(b,"visibility","")},toString:function(){return("DDProxy "+this.id)}});YAHOO.util.DDTarget=function(c,a,b){if(c){this.initTarget(c,a,b)}};YAHOO.extend(YAHOO.util.DDTarget,YAHOO.util.DragDrop,{toString:function(){return("DDTarget "+this.id)}});YAHOO.register("dragdrop",YAHOO.util.DragDropMgr,{version:"2.9.0",build:"2800"});YAHOO.util.Attribute=function(b,a){if(a){this.owner=a;this.configure(b,true)}};YAHOO.util.Attribute.INVALID_VALUE={};YAHOO.util.Attribute.prototype={name:undefined,value:null,owner:null,readOnly:false,writeOnce:false,_initialConfig:null,_written:false,method:null,setter:null,getter:null,validator:null,getValue:function(){var a=this.value;if(this.getter){a=this.getter.call(this.owner,this.name,a)}return a},setValue:function(f,b){var e,a=this.owner,c=this.name,g=YAHOO.util.Attribute.INVALID_VALUE,d={type:c,prevValue:this.getValue(),newValue:f};if(this.readOnly||(this.writeOnce&&this._written)){return false}if(this.validator&&!this.validator.call(a,f)){return false}if(!b){e=a.fireBeforeChangeEvent(d);if(e===false){return false}}if(this.setter){f=this.setter.call(a,f,this.name);if(f===undefined){}if(f===g){return false}}if(this.method){if(this.method.call(a,f,this.name)===g){return false}}this.value=f;this._written=true;d.type=c;if(!b){this.owner.fireChangeEvent(d)}return true},configure:function(b,c){b=b||{};if(c){this._written=false}this._initialConfig=this._initialConfig||{};for(var a in b){if(b.hasOwnProperty(a)){this[a]=b[a];if(c){this._initialConfig[a]=b[a]}}}},resetValue:function(){return this.setValue(this._initialConfig.value)},resetConfig:function(){this.configure(this._initialConfig,true)},refresh:function(a){this.setValue(this.value,a)}};(function(){var a=YAHOO.util.Lang;YAHOO.util.AttributeProvider=function(){};YAHOO.util.AttributeProvider.prototype={_configs:null,get:function(c){this._configs=this._configs||{};var b=this._configs[c];if(!b||!this._configs.hasOwnProperty(c)){return null}return b.getValue()},set:function(d,e,b){this._configs=this._configs||{};var c=this._configs[d];if(!c){return false}return c.setValue(e,b)},getAttributeKeys:function(){this._configs=this._configs;var c=[],b;for(b in this._configs){if(a.hasOwnProperty(this._configs,b)&&!a.isUndefined(this._configs[b])){c[c.length]=b}}return c},setAttributes:function(d,b){for(var c in d){if(a.hasOwnProperty(d,c)){this.set(c,d[c],b)}}},resetValue:function(c,b){this._configs=this._configs||{};if(this._configs[c]){this.set(c,this._configs[c]._initialConfig.value,b);return true}return false},refresh:function(e,c){this._configs=this._configs||{};var f=this._configs;e=((a.isString(e))?[e]:e)||this.getAttributeKeys();for(var d=0,b=e.length;d<b;++d){if(f.hasOwnProperty(e[d])){this._configs[e[d]].refresh(c)}}},register:function(b,c){this.setAttributeConfig(b,c)},getAttributeConfig:function(c){this._configs=this._configs||{};var b=this._configs[c]||{};var d={};for(c in b){if(a.hasOwnProperty(b,c)){d[c]=b[c]}}return d},setAttributeConfig:function(b,c,d){this._configs=this._configs||{};c=c||{};if(!this._configs[b]){c.name=b;this._configs[b]=this.createAttribute(c)}else{this._configs[b].configure(c,d)}},configureAttribute:function(b,c,d){this.setAttributeConfig(b,c,d)},resetAttributeConfig:function(b){this._configs=this._configs||{};this._configs[b].resetConfig()},subscribe:function(b,c){this._events=this._events||{};if(!(b in this._events)){this._events[b]=this.createEvent(b)}YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments)},on:function(){this.subscribe.apply(this,arguments)},addListener:function(){this.subscribe.apply(this,arguments)},fireBeforeChangeEvent:function(c){var b="before";b+=c.type.charAt(0).toUpperCase()+c.type.substr(1)+"Change";c.type=b;return this.fireEvent(c.type,c)},fireChangeEvent:function(b){b.type+="Change";return this.fireEvent(b.type,b)},createAttribute:function(b){return new YAHOO.util.Attribute(b,this)}};YAHOO.augment(YAHOO.util.AttributeProvider,YAHOO.util.EventProvider)})();(function(){var b=YAHOO.util.Dom,d=YAHOO.util.AttributeProvider,c={mouseenter:true,mouseleave:true};var a=function(e,f){this.init.apply(this,arguments)};a.DOM_EVENTS={click:true,dblclick:true,keydown:true,keypress:true,keyup:true,mousedown:true,mousemove:true,mouseout:true,mouseover:true,mouseup:true,mouseenter:true,mouseleave:true,focus:true,blur:true,submit:true,change:true};a.prototype={DOM_EVENTS:null,DEFAULT_HTML_SETTER:function(g,e){var f=this.get("element");if(f){f[e]=g}return g},DEFAULT_HTML_GETTER:function(e){var f=this.get("element"),g;if(f){g=f[e]}return g},appendChild:function(e){e=e.get?e.get("element"):e;return this.get("element").appendChild(e)},getElementsByTagName:function(e){return this.get("element").getElementsByTagName(e)},hasChildNodes:function(){return this.get("element").hasChildNodes()},insertBefore:function(e,f){e=e.get?e.get("element"):e;f=(f&&f.get)?f.get("element"):f;return this.get("element").insertBefore(e,f)},removeChild:function(e){e=e.get?e.get("element"):e;return this.get("element").removeChild(e)},replaceChild:function(e,f){e=e.get?e.get("element"):e;f=f.get?f.get("element"):f;return this.get("element").replaceChild(e,f)},initAttributes:function(e){},addListener:function(j,i,k,h){h=h||this;var e=YAHOO.util.Event,g=this.get("element")||this.get("id"),f=this;if(c[j]&&!e._createMouseDelegate){return false}if(!this._events[j]){if(g&&this.DOM_EVENTS[j]){e.on(g,j,function(m,l){if(m.srcElement&&!m.target){m.target=m.srcElement}if((m.toElement&&!m.relatedTarget)||(m.fromElement&&!m.relatedTarget)){m.relatedTarget=e.getRelatedTarget(m)}if(!m.currentTarget){m.currentTarget=g}f.fireEvent(j,m,l)},k,h)}this.createEvent(j,{scope:this})}return YAHOO.util.EventProvider.prototype.subscribe.apply(this,arguments)},on:function(){return this.addListener.apply(this,arguments)},subscribe:function(){return this.addListener.apply(this,arguments)},removeListener:function(f,e){return this.unsubscribe.apply(this,arguments)},addClass:function(e){b.addClass(this.get("element"),e)},getElementsByClassName:function(f,e){return b.getElementsByClassName(f,e,this.get("element"))},hasClass:function(e){return b.hasClass(this.get("element"),e)},removeClass:function(e){return b.removeClass(this.get("element"),e)},replaceClass:function(f,e){return b.replaceClass(this.get("element"),f,e)},setStyle:function(f,e){return b.setStyle(this.get("element"),f,e)},getStyle:function(e){return b.getStyle(this.get("element"),e)},fireQueue:function(){var f=this._queue;for(var g=0,e=f.length;g<e;++g){this[f[g][0]].apply(this,f[g][1])}},appendTo:function(f,g){f=(f.get)?f.get("element"):b.get(f);this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:f});g=(g&&g.get)?g.get("element"):b.get(g);var e=this.get("element");if(!e){return false}if(!f){return false}if(e.parent!=f){if(g){f.insertBefore(e,g)}else{f.appendChild(e)}}this.fireEvent("appendTo",{type:"appendTo",target:f});return e},get:function(e){var g=this._configs||{},f=g.element;if(f&&!g[e]&&!YAHOO.lang.isUndefined(f.value[e])){this._setHTMLAttrConfig(e)}return d.prototype.get.call(this,e)},setAttributes:function(l,h){var f={},j=this._configOrder;for(var k=0,e=j.length;k<e;++k){if(l[j[k]]!==undefined){f[j[k]]=true;this.set(j[k],l[j[k]],h)}}for(var g in l){if(l.hasOwnProperty(g)&&!f[g]){this.set(g,l[g],h)}}},set:function(f,h,e){var g=this.get("element");if(!g){this._queue[this._queue.length]=["set",arguments];if(this._configs[f]){this._configs[f].value=h}return}if(!this._configs[f]&&!YAHOO.lang.isUndefined(g[f])){this._setHTMLAttrConfig(f)}return d.prototype.set.apply(this,arguments)},setAttributeConfig:function(e,f,g){this._configOrder.push(e);d.prototype.setAttributeConfig.apply(this,arguments)},createEvent:function(f,e){this._events[f]=true;return d.prototype.createEvent.apply(this,arguments)},init:function(f,e){this._initElement(f,e)},destroy:function(){var e=this.get("element");YAHOO.util.Event.purgeElement(e,true);this.unsubscribeAll();if(e&&e.parentNode){e.parentNode.removeChild(e)}this._queue=[];this._events={};this._configs={};this._configOrder=[]},_initElement:function(g,f){this._queue=this._queue||[];this._events=this._events||{};this._configs=this._configs||{};this._configOrder=[];f=f||{};f.element=f.element||g||null;var i=false;var e=a.DOM_EVENTS;this.DOM_EVENTS=this.DOM_EVENTS||{};for(var h in e){if(e.hasOwnProperty(h)){this.DOM_EVENTS[h]=e[h]}}if(typeof f.element==="string"){this._setHTMLAttrConfig("id",{value:f.element})}if(b.get(f.element)){i=true;this._initHTMLElement(f);this._initContent(f)}YAHOO.util.Event.onAvailable(f.element,function(){if(!i){this._initHTMLElement(f)}this.fireEvent("available",{type:"available",target:b.get(f.element)})},this,true);YAHOO.util.Event.onContentReady(f.element,function(){if(!i){this._initContent(f)}this.fireEvent("contentReady",{type:"contentReady",target:b.get(f.element)})},this,true)},_initHTMLElement:function(e){this.setAttributeConfig("element",{value:b.get(e.element),readOnly:true})},_initContent:function(e){this.initAttributes(e);this.setAttributes(e,true);this.fireQueue()},_setHTMLAttrConfig:function(e,g){var f=this.get("element");g=g||{};g.name=e;g.setter=g.setter||this.DEFAULT_HTML_SETTER;g.getter=g.getter||this.DEFAULT_HTML_GETTER;g.value=g.value||f[e];this._configs[e]=new YAHOO.util.Attribute(g,this)}};YAHOO.augment(a,d);YAHOO.util.Element=a})();YAHOO.register("element",YAHOO.util.Element,{version:"2.9.0",build:"2800"});(function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig]}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_cloneObject:function(o){if(!lang.isValue(o)){return o}var copy={};if(Object.prototype.toString.apply(o)==="[object RegExp]"){copy=o}else{if(lang.isFunction(o)){copy=o}else{if(lang.isArray(o)){var array=[];for(var i=0,len=o.length;i<len;i++){array[i]=DS._cloneObject(o[i])}copy=array}else{if(lang.isObject(o)){for(var x in o){if(lang.hasOwnProperty(o,x)){if(lang.isValue(o[x])&&lang.isObject(o[x])||lang.isArray(o[x])){copy[x]=DS._cloneObject(o[x])}else{copy[x]=o[x]}}}}else{copy=o}}}}return copy},_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null}return value}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params)}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]))}}}},parseString:function(oData){if(!lang.isValue(oData)){return null}var string=oData+"";if(lang.isString(string)){return string}else{return null}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null}var number=oData*1;if(lang.isNumber(number)){return number}else{return null}},convertNumber:function(oData){return DS.parseNumber(oData)},parseDate:function(oData){var date=null;if(lang.isValue(oData)&&!(oData instanceof Date)){date=new Date(oData)}else{return oData}if(date instanceof Date){return date}else{return null}},convertDate:function(oData){return DS.parseDate(oData)}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,cloneBeforeCaching:false,toString:function(){return this._sName},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[]}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse)}oResponse.cached=true;break}}return oResponse}}}else{if(aCache){this._aCache=null}}return null},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest)},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return}while(aCache.length>=this.maxCacheEntries){aCache.shift()}oResponse=(this.cloneBeforeCaching)?DS._cloneObject(oResponse):oResponse;var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse})},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent")}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller)},nMsec);this._aIntervals.push(nId);return nId}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId)}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i])}tracker=[]},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null}return this.makeConnection(oRequest,oCallback,oCaller)},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs)}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs)}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1))}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length)}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")")}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs)}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs)}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1))}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length)}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")")}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0]}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[]}if(!oParsedResponse.meta){oParsedResponse.meta={}}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse)}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL})}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller)},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse}return null},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]}}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data)}if(data===undefined){data=null}oResult[field.key]=data}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data)}if(data===undefined){data=null}oResult[field.key]=data}}}results[i]=oResult}}else{results=oFullResponse}var oParsedResponse={results:results};return oParsedResponse}return null},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength)}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1)}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1)}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data)}if(data===undefined){data=null}oResult[key]=data}else{bError=true}}catch(e){bError=true}}}else{oResult=fielddataarray}if(!bError){oParsedResponse.results[recIdx++]=oResult}}}}}return oParsedResponse}}return null},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result)}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue}}if(datapieces.length>0){data=datapieces.join("")}}}}}if(data===null){data=""}if(!field.parser&&field.converter){field.parser=field.converter}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data)}if(data===undefined){data=null}oResult[key]=data}}catch(e){}return oResult},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse)}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value}}if(lang.isValue(v)){oParsedResponse.meta[k]=v}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult}}if(bError){oParsedResponse.error=true}else{}return oParsedResponse},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++)}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++)}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)]}}}else{}}return path};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]]}return v};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true}}else{bError=true}if(!resultsList){resultsList=[]}if(!lang.isArray(resultsList)){resultsList=[resultsList]}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser}}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path}}else{simpleFields[simpleFields.length]={key:key,path:path[0]}}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j]}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r)}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser.call(this,rec[p]);if(rec[p]===undefined){rec[p]=null}}}results[i]=rec}}else{results=resultsList}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v}}}}else{oParsedResponse.error=true}oParsedResponse.results=results}else{oParsedResponse.error=true}return oParsedResponse},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data)}if(data===undefined){data=null}oResult[key]=data}oParsedResponse.results[j]=oResult}}}else{bError=true}if(bError){oParsedResponse.error=true}else{}return oParsedResponse}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true)}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs)};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs)};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this,oCallback):this.liveData(oRequest,oCallback);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs)};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]"},doBeforeGetScriptNode:function(sUri){return sUri},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId)}else{}delete util.ScriptNodeDataSource.callbacks[id]};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs)};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId)}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest)}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift()}else{clearInterval(oQueue.interval);oQueue.interval=null}}},50)}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest)}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller)}return tId}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){return new util.LocalDataSource(oLiveData,oConfigs)}else{if(dataType==DS.TYPE_XHR){return new util.XHRDataSource(oLiveData,oConfigs)}else{if(dataType==DS.TYPE_SCRIPTNODE){return new util.ScriptNodeDataSource(oLiveData,oConfigs)}else{if(dataType==DS.TYPE_JSFUNCTION){return new util.FunctionDataSource(oLiveData,oConfigs)}}}}}if(YAHOO.lang.isString(oLiveData)){return new util.XHRDataSource(oLiveData,oConfigs)}else{if(YAHOO.lang.isFunction(oLiveData)){return new util.FunctionDataSource(oLiveData,oConfigs)}else{return new util.LocalDataSource(oLiveData,oConfigs)}}};lang.augmentObject(util.DataSource,DS)})();YAHOO.util.Number={format:function(e,k){if(e===""||e===null||!isFinite(e)){return""}e=+e;k=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(k||{}));var j=e+"",l=Math.abs(e),b=k.decimalPlaces||0,r=k.thousandsSeparator,f=k.negativeFormat||("-"+k.format),q,p,g,h;if(f.indexOf("#")>-1){f=f.replace(/#/,k.format)}if(b<0){q=l-(l%1)+"";g=q.length+b;if(g>0){q=Number("."+q).toFixed(g).slice(2)+new Array(q.length-g+1).join("0")}else{q="0"}}else{var a=l+"";if(b>0||a.indexOf(".")>0){var d=Math.pow(10,b);q=Math.round(l*d)/d+"";var c=q.indexOf("."),m,o;if(c<0){m=b;o=(Math.pow(10,m)+"").substring(1);if(b>0){q=q+"."+o}}else{m=b-(q.length-c-1);o=(Math.pow(10,m)+"").substring(1);q=q+o}}else{q=l.toFixed(b)+""}}p=q.split(/\D/);if(l>=1000){g=p[0].length%3||3;p[0]=p[0].slice(0,g)+p[0].slice(g).replace(/(\d{3})/g,r+"$1")}return YAHOO.util.Number.format._applyFormat((e<0?f:k.format),p.join(k.decimalSeparator),k)}};YAHOO.util.Number.format.defaults={format:"{prefix}{number}{suffix}",negativeFormat:null,decimalSeparator:".",decimalPlaces:null,thousandsSeparator:""};YAHOO.util.Number.format._applyFormat=function(a,b,c){return a.replace(/\{(\w+)\}/g,function(d,e){return e==="number"?b:e in c?c[e]:""})};(function(){var a=function(c,e,d){if(typeof d==="undefined"){d=10}for(;parseInt(c,10)<d&&d>1;d/=10){c=e.toString()+c}return c.toString()};var b={formats:{a:function(e,c){return c.a[e.getDay()]},A:function(e,c){return c.A[e.getDay()]},b:function(e,c){return c.b[e.getMonth()]},B:function(e,c){return c.B[e.getMonth()]},C:function(c){return a(parseInt(c.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(c){return a(parseInt(b.formats.G(c)%100,10),0)},G:function(f){var g=f.getFullYear();var e=parseInt(b.formats.V(f),10);var c=parseInt(b.formats.W(f),10);if(c>e){g++}else{if(c===0&&e>=52){g--}}return g},H:["getHours","0"],I:function(e){var c=e.getHours()%12;return a(c===0?12:c,0)},j:function(h){var g=new Date(""+h.getFullYear()+"/1/1 GMT");var e=new Date(""+h.getFullYear()+"/"+(h.getMonth()+1)+"/"+h.getDate()+" GMT");var c=e-g;var f=parseInt(c/60000/60/24,10)+1;return a(f,0,100)},k:["getHours"," "],l:function(e){var c=e.getHours()%12;return a(c===0?12:c," ")},m:function(c){return a(c.getMonth()+1,0)},M:["getMinutes","0"],p:function(e,c){return c.p[e.getHours()>=12?1:0]},P:function(e,c){return c.P[e.getHours()>=12?1:0]},s:function(e,c){return parseInt(e.getTime()/1000,10)},S:["getSeconds","0"],u:function(c){var e=c.getDay();return e===0?7:e},U:function(g){var c=parseInt(b.formats.j(g),10);var f=6-g.getDay();var e=parseInt((c+f)/7,10);return a(e,0)},V:function(g){var f=parseInt(b.formats.W(g),10);var c=(new Date(""+g.getFullYear()+"/1/1")).getDay();var e=f+(c>4||c<=1?0:1);if(e===53&&(new Date(""+g.getFullYear()+"/12/31")).getDay()<4){e=1}else{if(e===0){e=b.formats.V(new Date(""+(g.getFullYear()-1)+"/12/31"))}}return a(e,0)},w:"getDay",W:function(g){var c=parseInt(b.formats.j(g),10);var f=7-b.formats.u(g);var e=parseInt((c+f)/7,10);return a(e,0,10)},y:function(c){return a(c.getFullYear()%100,0)},Y:"getFullYear",z:function(f){var e=f.getTimezoneOffset();var c=a(parseInt(Math.abs(e/60),10),0);var g=a(Math.abs(e%60),0);return(e>0?"-":"+")+c+g},Z:function(c){var e=c.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(e.length>4){e=b.formats.z(c)}return e},"%":function(c){return"%"}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(g,f,d){f=f||{};if(!(g instanceof Date)){return YAHOO.lang.isValue(g)?g:""}var h=f.format||"%m/%d/%Y";if(h==="YYYY/MM/DD"){h="%Y/%m/%d"}else{if(h==="DD/MM/YYYY"){h="%d/%m/%Y"}else{if(h==="MM/DD/YYYY"){h="%m/%d/%Y"}}}d=d||"en";if(!(d in YAHOO.util.DateLocale)){if(d.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){d=d.replace(/-[a-zA-Z]+$/,"")}else{d="en"}}var j=YAHOO.util.DateLocale[d];var c=function(l,k){var m=b.aggregates[k];return(m==="locale"?j[k]:m)};var e=function(l,k){var m=b.formats[k];if(typeof m==="string"){return g[m]()}else{if(typeof m==="function"){return m.call(g,g,j)}else{if(typeof m==="object"&&typeof m[0]==="string"){return a(g[m[0]](),m[1])}else{return k}}}};while(h.match(/%[cDFhnrRtTxX]/)){h=h.replace(/%([cDFhnrRtTxX])/g,c)}var i=h.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,e);c=e=undefined;return i}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=b;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale.en=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en,{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en,{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en)})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.9.0",build:"2800"});YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource;YAHOO.widget.DS_XHR=function(b,a,d){var c=new YAHOO.util.XHRDataSource(b,d);c._aDeprecatedSchema=a;return c};YAHOO.widget.DS_ScriptNode=function(b,a,d){var c=new YAHOO.util.ScriptNodeDataSource(b,d);c._aDeprecatedSchema=a;return c};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT;YAHOO.widget.AutoComplete=function(g,b,j,c){if(g&&b&&j){if(j&&YAHOO.lang.isFunction(j.sendRequest)){this.dataSource=j}else{return}this.key=0;var d=j.responseSchema;if(j._aDeprecatedSchema){var k=j._aDeprecatedSchema;if(YAHOO.lang.isArray(k)){if((j.responseType===YAHOO.util.DataSourceBase.TYPE_JSON)||(j.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN)){d.resultsList=k[0];this.key=k[1];d.fields=(k.length<3)?null:k.slice(1)}else{if(j.responseType===YAHOO.util.DataSourceBase.TYPE_XML){d.resultNode=k[0];this.key=k[1];d.fields=k.slice(1)}else{if(j.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){d.recordDelim=k[0];d.fieldDelim=k[1]}}}j.responseSchema=d}}if(YAHOO.util.Dom.inDocument(g)){if(YAHOO.lang.isString(g)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+g;this._elTextbox=document.getElementById(g)}else{this._sName=(g.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+g.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=g}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input")}else{return}if(YAHOO.util.Dom.inDocument(b)){if(YAHOO.lang.isString(b)){this._elContainer=document.getElementById(b)}else{this._elContainer=b}if(this._elContainer.style.display=="none"){}var e=this._elContainer.parentNode;var a=e.tagName.toLowerCase();if(a=="div"){YAHOO.util.Dom.addClass(e,"yui-ac")}else{}}else{return}if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL){this.applyLocalFilter=true}if(c&&(c.constructor==Object)){for(var i in c){if(i){this[i]=c[i]}}}this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();var h=this;var f=this._elTextbox;YAHOO.util.Event.addListener(f,"keyup",h._onTextboxKeyUp,h);YAHOO.util.Event.addListener(f,"keydown",h._onTextboxKeyDown,h);YAHOO.util.Event.addListener(f,"focus",h._onTextboxFocus,h);YAHOO.util.Event.addListener(f,"blur",h._onTextboxBlur,h);YAHOO.util.Event.addListener(b,"mouseover",h._onContainerMouseover,h);YAHOO.util.Event.addListener(b,"mouseout",h._onContainerMouseout,h);YAHOO.util.Event.addListener(b,"click",h._onContainerClick,h);YAHOO.util.Event.addListener(b,"scroll",h._onContainerScroll,h);YAHOO.util.Event.addListener(b,"resize",h._onContainerResize,h);YAHOO.util.Event.addListener(f,"keypress",h._onTextboxKeyPress,h);YAHOO.util.Event.addListener(window,"unload",h._onWindowUnload,h);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataRequestCancelEvent=new YAHOO.util.CustomEvent("dataRequestCancel",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);f.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=false;YAHOO.widget.AutoComplete.prototype.queryMatchContains=false;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=false;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=false;YAHOO.widget.AutoComplete.prototype.resultTypeList=true;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=true;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=true;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer};YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(a){if(a._sResultMatch){return a._sResultMatch}else{return null}};YAHOO.widget.AutoComplete.prototype.getListItemData=function(a){if(a._oResultData){return a._oResultData}else{return null}};YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(a){if(YAHOO.lang.isNumber(a._nItemIndex)){return a._nItemIndex}else{return null}};YAHOO.widget.AutoComplete.prototype.setHeader=function(b){if(this._elHeader){var a=this._elHeader;if(b){a.innerHTML=b;a.style.display=""}else{a.innerHTML="";a.style.display="none"}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(b){if(this._elFooter){var a=this._elFooter;if(b){a.innerHTML=b;a.style.display=""}else{a.innerHTML="";a.style.display="none"}}};YAHOO.widget.AutoComplete.prototype.setBody=function(a){if(this._elBody){var b=this._elBody;YAHOO.util.Event.purgeElement(b,true);if(a){b.innerHTML=a;b.style.display=""}else{b.innerHTML="";b.style.display="none"}this._elList=null}};YAHOO.widget.AutoComplete.prototype.generateRequest=function(b){var a=this.dataSource.dataType;if(a===YAHOO.util.DataSourceBase.TYPE_XHR){if(!this.dataSource.connMethodPost){b=(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"")}else{b=(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"")}}else{if(a===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE){b="&"+(this.dataSource.scriptQueryParam||"query")+"="+b+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"")}}return b};YAHOO.widget.AutoComplete.prototype.sendQuery=function(b){this._bFocused=true;var a=(this.delimChar)?this._elTextbox.value+b:b;this._sendQuery(a)};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var a=this._elTextbox,b=YAHOO.util.Dom.getXY(a);b[1]+=YAHOO.util.Dom.get(a).offsetHeight+2;YAHOO.util.Dom.setXY(this._elContainer,b)};YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true)};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false)};YAHOO.widget.AutoComplete.prototype.clearList=function(){var b=this._elList.childNodes,a=b.length-1;for(;a>-1;a--){b[a].style.display="none"}};YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(e){var d,c,a;for(var b=e.length;b>=this.minQueryLength;b--){a=this.generateRequest(e.substr(0,b));this.dataRequestEvent.fire(this,d,a);c=this.dataSource.getCachedResponse(a);if(c){return this.filterResults.apply(this.dataSource,[e,c,c,{scope:this}])}}return null};YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(c,b,a){var d=((this.responseStripAfter!=="")&&(b.indexOf))?b.indexOf(this.responseStripAfter):-1;if(d!=-1){b=b.substring(0,d)}return b};YAHOO.widget.AutoComplete.prototype.filterResults=function(l,n,r,m){if(m&&m.argument&&YAHOO.lang.isValue(m.argument.query)){l=m.argument.query}if(l&&l!==""){r=YAHOO.widget.AutoComplete._cloneObject(r);var j=m.scope,q=this,c=r.results,o=[],b=j.maxResultsDisplayed,k=(q.queryMatchCase||j.queryMatchCase),a=(q.queryMatchContains||j.queryMatchContains);for(var d=0,h=c.length;d<h;d++){var f=c[d];var e=null;if(YAHOO.lang.isString(f)){e=f}else{if(YAHOO.lang.isArray(f)){e=f[0]}else{if(this.responseSchema.fields){var p=this.responseSchema.fields[0].key||this.responseSchema.fields[0];e=f[p]}else{if(this.key){e=f[this.key]}}}}if(YAHOO.lang.isString(e)){var g=(k)?e.indexOf(decodeURIComponent(l)):e.toLowerCase().indexOf(decodeURIComponent(l).toLowerCase());if((!a&&(g===0))||(a&&(g>-1))){o.push(f)}}if(h>b&&o.length===b){break}}r.results=o}else{}return r};YAHOO.widget.AutoComplete.prototype.handleResponse=function(c,a,b){if((this instanceof YAHOO.widget.AutoComplete)&&this._sName){this._populateList(c,a,b)}};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(c,a,b){return true};YAHOO.widget.AutoComplete.prototype.formatResult=function(b,d,a){var c=(a)?a:"";return c};YAHOO.widget.AutoComplete.prototype.formatEscapedResult=function(c,d,b){var a=(b)?b:"";return YAHOO.lang.escapeHTML(a)};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(d,a,c,b){return true};YAHOO.widget.AutoComplete.prototype.destroy=function(){var b=this.toString();var a=this._elTextbox;var d=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(a,true);YAHOO.util.Event.purgeElement(d,true);d.innerHTML="";for(var c in this){if(YAHOO.lang.hasOwnProperty(this,c)){this[c]=null}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestCancelEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=false;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var b=this.minQueryLength;if(!YAHOO.lang.isNumber(b)){this.minQueryLength=1}var e=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(e)||(e<1)){this.maxResultsDisplayed=10}var f=this.queryDelay;if(!YAHOO.lang.isNumber(f)||(f<0)){this.queryDelay=0.2}var c=this.typeAheadDelay;if(!YAHOO.lang.isNumber(c)||(c<0)){this.typeAheadDelay=0.2}var a=this.delimChar;if(YAHOO.lang.isString(a)&&(a.length>0)){this.delimChar=[a]}else{if(!YAHOO.lang.isArray(a)){this.delimChar=null}}var d=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(d)||(d<0)){this.animSpeed=0.3}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed)}else{this._oAnim.duration=this.animSpeed}}if(this.forceSelection&&a){}};YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var a=document.createElement("div");a.className="yui-ac-shadow";a.style.width=0;a.style.height=0;this._elShadow=this._elContainer.appendChild(a)}if(this.useIFrame&&!this._elIFrame){var b=document.createElement("iframe");b.src=this._iFrameSrc;b.frameBorder=0;b.scrolling="no";b.style.position="absolute";b.style.width=0;b.style.height=0;b.style.padding=0;b.tabIndex=-1;b.role="presentation";b.title="Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(b)}};YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var c=document.createElement("div");c.className="yui-ac-content";c.style.display="none";this._elContent=this._elContainer.appendChild(c);var b=document.createElement("div");b.className="yui-ac-hd";b.style.display="none";this._elHeader=this._elContent.appendChild(b);var d=document.createElement("div");d.className="yui-ac-bd";this._elBody=this._elContent.appendChild(d);var a=document.createElement("div");a.className="yui-ac-ft";a.style.display="none";this._elFooter=this._elContent.appendChild(a)}else{}};YAHOO.widget.AutoComplete.prototype._initListEl=function(){var c=this.maxResultsDisplayed,a=this._elList||document.createElement("ul"),b;while(a.childNodes.length<c){b=document.createElement("li");b.style.display="none";b._nItemIndex=a.childNodes.length;a.appendChild(b)}if(!this._elList){var d=this._elBody;YAHOO.util.Event.purgeElement(d,true);d.innerHTML="";this._elList=d.appendChild(a)}this._elBody.style.display=""};YAHOO.widget.AutoComplete.prototype._focus=function(){var a=this;setTimeout(function(){try{a._elTextbox.focus()}catch(b){}},0)};YAHOO.widget.AutoComplete.prototype._enableIntervalDetection=function(){var a=this;if(!a._queryInterval&&a.queryInterval){a._queryInterval=setInterval(function(){a._onInterval()},a.queryInterval)}};YAHOO.widget.AutoComplete.prototype.enableIntervalDetection=YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;YAHOO.widget.AutoComplete.prototype._onInterval=function(){var a=this._elTextbox.value;var b=this._sLastTextboxValue;if(a!=b){this._sLastTextboxValue=a;this._sendQuery(a)}};YAHOO.widget.AutoComplete.prototype._clearInterval=function(){if(this._queryInterval){clearInterval(this._queryInterval);this._queryInterval=null}};YAHOO.widget.AutoComplete.prototype._isIgnoreKey=function(a){if((a==9)||(a==13)||(a==16)||(a==17)||(a>=18&&a<=20)||(a==27)||(a>=33&&a<=35)||(a>=36&&a<=40)||(a>=44&&a<=45)||(a==229)){return true}return false};YAHOO.widget.AutoComplete.prototype._sendQuery=function(d){if(this.minQueryLength<0){this._toggleContainer(false);return}if(this.delimChar){var a=this._extractQuery(d);d=a.query;this._sPastSelections=a.previous}if((d&&(d.length<this.minQueryLength))||(!d&&this.minQueryLength>0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID)}this._toggleContainer(false);return}d=encodeURIComponent(d);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset){var c=this.getSubsetMatches(d);if(c){this.handleResponse(d,c,{query:d});return}}if(this.dataSource.responseStripAfter){this.dataSource.doBeforeParseData=this.preparseRawResponse}if(this.applyLocalFilter){this.dataSource.doBeforeCallback=this.filterResults}var b=this.generateRequest(d);if(b!==undefined){this.dataRequestEvent.fire(this,d,b);this.dataSource.sendRequest(b,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:d}})}else{this.dataRequestCancelEvent.fire(this,d)}};YAHOO.widget.AutoComplete.prototype._populateListItem=function(b,a,c){b.innerHTML=this.formatResult(a,c,b._sResultMatch)};YAHOO.widget.AutoComplete.prototype._populateList=function(n,f,c){if(this._nTypeAheadDelayID!=-1){clearTimeout(this._nTypeAheadDelayID)}n=(c&&c.query)?c.query:n;var h=this.doBeforeLoadData(n,f,c);if(h&&!f.error){this.dataReturnEvent.fire(this,n,f.results);if(this._bFocused){var p=decodeURIComponent(n);this._sCurQuery=p;this._bItemSelected=false;var u=f.results,a=Math.min(u.length,this.maxResultsDisplayed),m=(this.dataSource.responseSchema.fields)?(this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]):0;if(a>0){if(!this._elList||(this._elList.childNodes.length<a)){this._initListEl()}this._initContainerHelperEls();var l=this._elList.childNodes;for(var t=a-1;t>=0;t--){var s=l[t],e=u[t];if(this.resultTypeList){var b=[];b[0]=(YAHOO.lang.isString(e))?e:e[m]||e[this.key];var o=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(o)&&(o.length>1)){for(var q=1,v=o.length;q<v;q++){b[b.length]=e[o[q].key||o[q]]}}else{if(YAHOO.lang.isArray(e)){b=e}else{if(YAHOO.lang.isString(e)){b=[e]}else{b[1]=e}}}e=b}s._sResultMatch=(YAHOO.lang.isString(e))?e:(YAHOO.lang.isArray(e))?e[0]:(e[m]||"");s._oResultData=e;this._populateListItem(s,e,p);s.style.display=""}if(a<l.length){var g;for(var r=l.length-1;r>=a;r--){g=l[r];g.style.display="none"}}this._nDisplayedItems=a;this.containerPopulateEvent.fire(this,n,u);if(this.autoHighlight){var d=this._elList.firstChild;this._toggleHighlight(d,"to");this.itemArrowToEvent.fire(this,d);this._typeAhead(d,n)}else{this._toggleHighlight(this._elCurListItem,"from")}h=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,n,u);this._toggleContainer(h)}else{this._toggleContainer(false)}return}}else{this.dataErrorEvent.fire(this,n,f)}};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(d,a,c,b){if(this.autoSnapContainer){this.snapContainer()}return this.doBeforeExpandContainer(d,a,c,b)};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var a=(this.delimChar)?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=a.previous;this.selectionEnforceEvent.fire(this,a.query)};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var a=null;for(var b=0;b<this._nDisplayedItems;b++){var c=this._elList.childNodes[b];var d=(""+c._sResultMatch).toLowerCase();if(d==this._sCurQuery.toLowerCase()){a=c;break}}return(a)};YAHOO.widget.AutoComplete.prototype._typeAhead=function(b,d){if(!this.typeAhead||(this._nKeyCode==8)){return}var a=this,c=this._elTextbox;if(c.setSelectionRange||c.createTextRange){this._nTypeAheadDelayID=setTimeout(function(){var f=c.value.length;a._updateValue(b);var g=c.value.length;a._selectText(c,f,g);var e=c.value.substr(f,g);a._sCurQuery=b._sResultMatch;a.typeAheadEvent.fire(a,d,e)},(this.typeAheadDelay*1000))}};YAHOO.widget.AutoComplete.prototype._selectText=function(d,a,b){if(d.setSelectionRange){d.setSelectionRange(a,b)}else{if(d.createTextRange){var c=d.createTextRange();c.moveStart("character",a);c.moveEnd("character",b-d.value.length);c.select()}else{d.select()}}};YAHOO.widget.AutoComplete.prototype._extractQuery=function(h){var c=this.delimChar,f=-1,g,e,b=c.length-1,d;for(;b>=0;b--){g=h.lastIndexOf(c[b]);if(g>f){f=g}}if(c[b]==" "){for(var a=c.length-1;a>=0;a--){if(h[f-1]==c[a]){f--;break}}}if(f>-1){e=f+1;while(h.charAt(e)==" "){e+=1}d=h.substring(0,e);h=h.substr(e)}else{d=""}return{previous:d,query:h}};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(d){var e=this._elContent.offsetWidth+"px";var b=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var c=this._elIFrame;if(d){c.style.width=e;c.style.height=b;c.style.padding=""}else{c.style.width=0;c.style.height=0;c.style.padding=0}}if(this.useShadow&&this._elShadow){var a=this._elShadow;if(d){a.style.width=e;a.style.height=b}else{a.style.width=0;a.style.height=0}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(i){var d=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return}if(!i){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none"){return}}var a=this._oAnim;if(a&&a.getEl()&&(this.animHoriz||this.animVert)){if(a.isAnimated()){a.stop(true)}var g=this._elContent.cloneNode(true);d.appendChild(g);g.style.top="-9000px";g.style.width="";g.style.height="";g.style.display="";var f=g.offsetWidth;var c=g.offsetHeight;var b=(this.animHoriz)?0:f;var e=(this.animVert)?0:c;a.attributes=(i)?{width:{to:f},height:{to:c}}:{width:{to:b},height:{to:e}};if(i&&!this._bContainerOpen){this._elContent.style.width=b+"px";this._elContent.style.height=e+"px"}else{this._elContent.style.width=f+"px";this._elContent.style.height=c+"px"}d.removeChild(g);g=null;var h=this;var j=function(){a.onComplete.unsubscribeAll();if(i){h._toggleContainerHelpers(true);h._bContainerOpen=i;h.containerExpandEvent.fire(h)}else{h._elContent.style.display="none";h._bContainerOpen=i;h.containerCollapseEvent.fire(h)}};this._toggleContainerHelpers(false);this._elContent.style.display="";a.onComplete.subscribe(j);a.animate()}else{if(i){this._elContent.style.display="";this._toggleContainerHelpers(true);this._bContainerOpen=i;this.containerExpandEvent.fire(this)}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=i;this.containerCollapseEvent.fire(this)}}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(a,c){if(a){var b=this.highlightClassName;if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,b);this._elCurListItem=null}if((c=="to")&&b){YAHOO.util.Dom.addClass(a,b);this._elCurListItem=a}}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(b,c){var a=this.prehighlightClassName;if(this._elCurPrehighlightItem){YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,a)}if(b==this._elCurListItem){return}if((c=="mouseover")&&a){YAHOO.util.Dom.addClass(b,a);this._elCurPrehighlightItem=b}else{YAHOO.util.Dom.removeClass(b,a)}};YAHOO.widget.AutoComplete.prototype._updateValue=function(c){if(!this.suppressInputUpdate){var f=this._elTextbox;var e=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var b=c._sResultMatch;var d="";if(e){d=this._sPastSelections;d+=b+e;if(e!=" "){d+=" "}}else{d=b}f.value=d;if(f.type=="textarea"){f.scrollTop=f.scrollHeight}var a=f.value.length;this._selectText(f,a,a);this._elCurListItem=c}};YAHOO.widget.AutoComplete.prototype._selectItem=function(a){this._bItemSelected=true;this._updateValue(a);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,a,a._oResultData);this._toggleContainer(false)};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._elCurListItem){this._selectItem(this._elCurListItem)}else{this._toggleContainer(false)}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(g){if(this._bContainerOpen){var h=this._elCurListItem,d=-1;if(h){d=h._nItemIndex}var e=(g==40)?(d+1):(d-1);if(e<-2||e>=this._nDisplayedItems){return}if(h){this._toggleHighlight(h,"from");this.itemArrowFromEvent.fire(this,h)}if(e==-1){if(this.delimChar){this._elTextbox.value=this._sPastSelections+this._sCurQuery}else{this._elTextbox.value=this._sCurQuery}return}if(e==-2){this._toggleContainer(false);return}var f=this._elList.childNodes[e],b=this._elContent,c=YAHOO.util.Dom.getStyle(b,"overflow"),i=YAHOO.util.Dom.getStyle(b,"overflowY"),a=((c=="auto")||(c=="scroll")||(i=="auto")||(i=="scroll"));if(a&&(e>-1)&&(e<this._nDisplayedItems)){if(g==40){if((f.offsetTop+f.offsetHeight)>(b.scrollTop+b.offsetHeight)){b.scrollTop=(f.offsetTop+f.offsetHeight)-b.offsetHeight}else{if((f.offsetTop+f.offsetHeight)<b.scrollTop){b.scrollTop=f.offsetTop}}}else{if(f.offsetTop<b.scrollTop){this._elContent.scrollTop=f.offsetTop}else{if(f.offsetTop>(b.scrollTop+b.offsetHeight)){this._elContent.scrollTop=(f.offsetTop+f.offsetHeight)-b.offsetHeight}}}}this._toggleHighlight(f,"to");this.itemArrowToEvent.fire(this,f);if(this.typeAhead){this._updateValue(f);this._sCurQuery=f._sResultMatch}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":if(c.prehighlightClassName){c._togglePrehighlight(d,"mouseover")}else{c._toggleHighlight(d,"to")}c.itemMouseOverEvent.fire(c,d);break;case"div":if(YAHOO.util.Dom.hasClass(d,"yui-ac-container")){c._bOverContainer=true;return}break;default:break}d=d.parentNode;if(d){b=d.nodeName.toLowerCase()}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":if(c.prehighlightClassName){c._togglePrehighlight(d,"mouseout")}else{c._toggleHighlight(d,"from")}c.itemMouseOutEvent.fire(c,d);break;case"ul":c._toggleHighlight(c._elCurListItem,"to");break;case"div":if(YAHOO.util.Dom.hasClass(d,"yui-ac-container")){c._bOverContainer=false;return}break;default:break}d=d.parentNode;if(d){b=d.nodeName.toLowerCase()}}};YAHOO.widget.AutoComplete.prototype._onContainerClick=function(a,c){var d=YAHOO.util.Event.getTarget(a);var b=d.nodeName.toLowerCase();while(d&&(b!="table")){switch(b){case"body":return;case"li":c._toggleHighlight(d,"to");c._selectItem(d);return;default:break}d=d.parentNode;if(d){b=d.nodeName.toLowerCase()}}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(a,b){b._focus()};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(a,b){b._toggleContainerHelpers(b._bContainerOpen)};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(a,b){var c=a.keyCode;if(b._nTypeAheadDelayID!=-1){clearTimeout(b._nTypeAheadDelayID)}switch(c){case 9:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(b._elCurListItem){if(b.delimChar&&(b._nKeyCode!=c)){if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a)}}b._selectItem(b._elCurListItem)}else{b._toggleContainer(false)}}break;case 13:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(b._elCurListItem){if(b._nKeyCode!=c){if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a)}}b._selectItem(b._elCurListItem)}else{b._toggleContainer(false)}}break;case 27:b._toggleContainer(false);return;case 39:b._jumpSelection();break;case 38:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);b._moveSelection(c)}break;case 40:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);b._moveSelection(c)}break;default:b._bItemSelected=false;b._toggleHighlight(b._elCurListItem,"from");b.textboxKeyEvent.fire(b,c);break}if(c===18){b._enableIntervalDetection()}b._nKeyCode=c};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(a,b){var c=a.keyCode;if(YAHOO.env.ua.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&(YAHOO.env.ua.webkit<420)){switch(c){case 9:if(b._bContainerOpen){if(b.delimChar){YAHOO.util.Event.stopEvent(a)}if(b._elCurListItem){b._selectItem(b._elCurListItem)}else{b._toggleContainer(false)}}break;case 13:if(b._bContainerOpen){YAHOO.util.Event.stopEvent(a);if(b._elCurListItem){b._selectItem(b._elCurListItem)}else{b._toggleContainer(false)}}break;default:break}}else{if(c==229){b._enableIntervalDetection()}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(a,c){var b=this.value;c._initProps();var d=a.keyCode;if(c._isIgnoreKey(d)){return}if(c._nDelayID!=-1){clearTimeout(c._nDelayID)}c._nDelayID=setTimeout(function(){c._sendQuery(b)},(c.queryDelay*1000))};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(a,b){if(!b._bFocused){b._elTextbox.setAttribute("autocomplete","off");b._bFocused=true;b._sInitInputValue=b._elTextbox.value;b.textboxFocusEvent.fire(b)}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(a,c){if(!c._bOverContainer||(c._nKeyCode==9)){if(!c._bItemSelected){var b=c._textMatchesOption();if(!c._bContainerOpen||(c._bContainerOpen&&(b===null))){if(c.forceSelection){c._clearSelection()}else{c.unmatchedItemSelectEvent.fire(c,c._sCurQuery)}}else{if(c.forceSelection){c._selectItem(b)}}}c._clearInterval();c._bFocused=false;if(c._sInitInputValue!==c._elTextbox.value){c.textboxChangeEvent.fire(c)}c.textboxBlurEvent.fire(c);c._toggleContainer(false)}else{c._focus()}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(a,b){if(b&&b._elTextbox&&b.allowBrowserAutocomplete){b._elTextbox.setAttribute("autocomplete","on")}};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(a){return this.generateRequest(a)};YAHOO.widget.AutoComplete.prototype.getListItems=function(){var c=[],b=this._elList.childNodes;for(var a=b.length-1;a>=0;a--){c[a]=b[a]}return c};YAHOO.widget.AutoComplete._cloneObject=function(d){if(!YAHOO.lang.isValue(d)){return d}var f={};if(YAHOO.lang.isFunction(d)){f=d}else{if(YAHOO.lang.isArray(d)){var e=[];for(var c=0,b=d.length;c<b;c++){e[c]=YAHOO.widget.AutoComplete._cloneObject(d[c])}f=e}else{if(YAHOO.lang.isObject(d)){for(var a in d){if(YAHOO.lang.hasOwnProperty(d,a)){if(YAHOO.lang.isValue(d[a])&&YAHOO.lang.isObject(d[a])||YAHOO.lang.isArray(d[a])){f[a]=YAHOO.widget.AutoComplete._cloneObject(d[a])}else{f[a]=d[a]}}}}else{f=d}}}return f};YAHOO.register("autocomplete",YAHOO.widget.AutoComplete,{version:"2.9.0",build:"2800"});(function(){YAHOO.util.Config=function(d){if(d){this.init(d)}};var b=YAHOO.lang,c=YAHOO.util.CustomEvent,a=YAHOO.util.Config;a.CONFIG_CHANGED_EVENT="configChanged";a.BOOLEAN_TYPE="boolean";a.prototype={owner:null,queueInProgress:false,config:null,initialConfig:null,eventQueue:null,configChangedEvent:null,init:function(d){this.owner=d;this.configChangedEvent=this.createEvent(a.CONFIG_CHANGED_EVENT);this.configChangedEvent.signature=c.LIST;this.queueInProgress=false;this.config={};this.initialConfig={};this.eventQueue=[]},checkBoolean:function(d){return(typeof d==a.BOOLEAN_TYPE)},checkNumber:function(d){return(!isNaN(d))},fireEvent:function(d,f){var e=this.config[d];if(e&&e.event){e.event.fire(f)}},addProperty:function(e,d){e=e.toLowerCase();this.config[e]=d;d.event=this.createEvent(e,{scope:this.owner});d.event.signature=c.LIST;d.key=e;if(d.handler){d.event.subscribe(d.handler,this.owner)}this.setProperty(e,d.value,true);if(!d.suppressEvent){this.queueProperty(e,d.value)}},getConfig:function(){var d={},f=this.config,g,e;for(g in f){if(b.hasOwnProperty(f,g)){e=f[g];if(e&&e.event){d[g]=e.value}}}return d},getProperty:function(d){var e=this.config[d.toLowerCase()];if(e&&e.event){return e.value}else{return undefined}},resetProperty:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event){if(d in this.initialConfig){this.setProperty(d,this.initialConfig[d]);return true}}else{return false}},setProperty:function(e,g,d){var f;e=e.toLowerCase();if(this.queueInProgress&&!d){this.queueProperty(e,g);return true}else{f=this.config[e];if(f&&f.event){if(f.validator&&!f.validator(g)){return false}else{f.value=g;if(!d){this.fireEvent(e,g);this.configChangedEvent.fire([e,g])}return true}}else{return false}}},queueProperty:function(v,r){v=v.toLowerCase();var u=this.config[v],l=false,k,g,h,j,p,t,f,n,o,d,m,w,e;if(u&&u.event){if(!b.isUndefined(r)&&u.validator&&!u.validator(r)){return false}else{if(!b.isUndefined(r)){u.value=r}else{r=u.value}l=false;k=this.eventQueue.length;for(m=0;m<k;m++){g=this.eventQueue[m];if(g){h=g[0];j=g[1];if(h==v){this.eventQueue[m]=null;this.eventQueue.push([v,(!b.isUndefined(r)?r:j)]);l=true;break}}}if(!l&&!b.isUndefined(r)){this.eventQueue.push([v,r])}}if(u.supercedes){p=u.supercedes.length;for(w=0;w<p;w++){t=u.supercedes[w];f=this.eventQueue.length;for(e=0;e<f;e++){n=this.eventQueue[e];if(n){o=n[0];d=n[1];if(o==t.toLowerCase()){this.eventQueue.push([o,d]);this.eventQueue[e]=null;break}}}}}return true}else{return false}},refireEvent:function(d){d=d.toLowerCase();var e=this.config[d];if(e&&e.event&&!b.isUndefined(e.value)){if(this.queueInProgress){this.queueProperty(d)}else{this.fireEvent(d,e.value)}}},applyConfig:function(d,g){var f,e;if(g){e={};for(f in d){if(b.hasOwnProperty(d,f)){e[f.toLowerCase()]=d[f]}}this.initialConfig=e}for(f in d){if(b.hasOwnProperty(d,f)){this.queueProperty(f,d[f])}}},refresh:function(){var d;for(d in this.config){if(b.hasOwnProperty(this.config,d)){this.refireEvent(d)}}},fireQueue:function(){var e,h,d,g,f;this.queueInProgress=true;for(e=0;e<this.eventQueue.length;e++){h=this.eventQueue[e];if(h){d=h[0];g=h[1];f=this.config[d];f.value=g;this.eventQueue[e]=null;this.fireEvent(d,g)}}this.queueInProgress=false;this.eventQueue=[]},subscribeToConfigEvent:function(d,e,g,h){var f=this.config[d.toLowerCase()];if(f&&f.event){if(!a.alreadySubscribed(f.event,e,g)){f.event.subscribe(e,g,h)}return true}else{return false}},unsubscribeFromConfigEvent:function(d,e,g){var f=this.config[d.toLowerCase()];if(f&&f.event){return f.event.unsubscribe(e,g)}else{return false}},toString:function(){var d="Config";if(this.owner){d+=" ["+this.owner.toString()+"]"}return d},outputEventQueue:function(){var d="",g,e,f=this.eventQueue.length;for(e=0;e<f;e++){g=this.eventQueue[e];if(g){d+=g[0]+"="+g[1]+", "}}return d},destroy:function(){var e=this.config,d,f;for(d in e){if(b.hasOwnProperty(e,d)){f=e[d];f.event.unsubscribeAll();f.event=null}}this.configChangedEvent.unsubscribeAll();this.configChangedEvent=null;this.owner=null;this.config=null;this.initialConfig=null;this.eventQueue=null}};a.alreadySubscribed=function(e,h,j){var f=e.subscribers.length,d,g;if(f>0){g=f-1;do{d=e.subscribers[g];if(d&&d.obj==j&&d.fn==h){return true}}while(g--)}return false};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider)}());(function(){YAHOO.widget.Module=function(r,q){if(r){this.init(r,q)}else{}};var f=YAHOO.util.Dom,d=YAHOO.util.Config,n=YAHOO.util.Event,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,i=YAHOO.env.ua,h,p,o,e,a={BEFORE_INIT:"beforeInit",INIT:"init",APPEND:"append",BEFORE_RENDER:"beforeRender",RENDER:"render",CHANGE_HEADER:"changeHeader",CHANGE_BODY:"changeBody",CHANGE_FOOTER:"changeFooter",CHANGE_CONTENT:"changeContent",DESTROY:"destroy",BEFORE_SHOW:"beforeShow",SHOW:"show",BEFORE_HIDE:"beforeHide",HIDE:"hide"},j={VISIBLE:{key:"visible",value:true,validator:YAHOO.lang.isBoolean},EFFECT:{key:"effect",suppressEvent:true,supercedes:["visible"]},MONITOR_RESIZE:{key:"monitorresize",value:true},APPEND_TO_DOCUMENT_BODY:{key:"appendtodocumentbody",value:false}};g.IMG_ROOT=null;g.IMG_ROOT_SSL=null;g.CSS_MODULE="yui-module";g.CSS_HEADER="hd";g.CSS_BODY="bd";g.CSS_FOOTER="ft";g.RESIZE_MONITOR_SECURE_URL="javascript:false;";g.RESIZE_MONITOR_BUFFER=1;g.textResizeEvent=new m("textResize");g.forceDocumentRedraw=function(){var q=document.documentElement;if(q){q.className+=" ";q.className=YAHOO.lang.trim(q.className)}};function l(){if(!h){h=document.createElement("div");h.innerHTML=('<div class="'+g.CSS_HEADER+'"></div><div class="'+g.CSS_BODY+'"></div><div class="'+g.CSS_FOOTER+'"></div>');p=h.firstChild;o=p.nextSibling;e=o.nextSibling}return h}function k(){if(!p){l()}return(p.cloneNode(false))}function b(){if(!o){l()}return(o.cloneNode(false))}function c(){if(!e){l()}return(e.cloneNode(false))}g.prototype={constructor:g,element:null,header:null,body:null,footer:null,id:null,imageRoot:g.IMG_ROOT,initEvents:function(){var q=m.LIST;this.beforeInitEvent=this.createEvent(a.BEFORE_INIT);this.beforeInitEvent.signature=q;this.initEvent=this.createEvent(a.INIT);this.initEvent.signature=q;this.appendEvent=this.createEvent(a.APPEND);this.appendEvent.signature=q;this.beforeRenderEvent=this.createEvent(a.BEFORE_RENDER);this.beforeRenderEvent.signature=q;this.renderEvent=this.createEvent(a.RENDER);this.renderEvent.signature=q;this.changeHeaderEvent=this.createEvent(a.CHANGE_HEADER);this.changeHeaderEvent.signature=q;this.changeBodyEvent=this.createEvent(a.CHANGE_BODY);this.changeBodyEvent.signature=q;this.changeFooterEvent=this.createEvent(a.CHANGE_FOOTER);this.changeFooterEvent.signature=q;this.changeContentEvent=this.createEvent(a.CHANGE_CONTENT);this.changeContentEvent.signature=q;this.destroyEvent=this.createEvent(a.DESTROY);this.destroyEvent.signature=q;this.beforeShowEvent=this.createEvent(a.BEFORE_SHOW);this.beforeShowEvent.signature=q;this.showEvent=this.createEvent(a.SHOW);this.showEvent.signature=q;this.beforeHideEvent=this.createEvent(a.BEFORE_HIDE);this.beforeHideEvent.signature=q;this.hideEvent=this.createEvent(a.HIDE);this.hideEvent.signature=q},platform:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("windows")!=-1||q.indexOf("win32")!=-1){return"windows"}else{if(q.indexOf("macintosh")!=-1){return"mac"}else{return false}}}(),browser:function(){var q=navigator.userAgent.toLowerCase();if(q.indexOf("opera")!=-1){return"opera"}else{if(q.indexOf("msie 7")!=-1){return"ie7"}else{if(q.indexOf("msie")!=-1){return"ie"}else{if(q.indexOf("safari")!=-1){return"safari"}else{if(q.indexOf("gecko")!=-1){return"gecko"}else{return false}}}}}}(),isSecure:function(){if(window.location.href.toLowerCase().indexOf("https")===0){return true}else{return false}}(),initDefaultConfig:function(){this.cfg.addProperty(j.VISIBLE.key,{handler:this.configVisible,value:j.VISIBLE.value,validator:j.VISIBLE.validator});this.cfg.addProperty(j.EFFECT.key,{handler:this.configEffect,suppressEvent:j.EFFECT.suppressEvent,supercedes:j.EFFECT.supercedes});this.cfg.addProperty(j.MONITOR_RESIZE.key,{handler:this.configMonitorResize,value:j.MONITOR_RESIZE.value});this.cfg.addProperty(j.APPEND_TO_DOCUMENT_BODY.key,{value:j.APPEND_TO_DOCUMENT_BODY.value})},init:function(v,u){var s,w;this.initEvents();this.beforeInitEvent.fire(g);this.cfg=new d(this);if(this.isSecure){this.imageRoot=g.IMG_ROOT_SSL}if(typeof v=="string"){s=v;v=document.getElementById(v);if(!v){v=(l()).cloneNode(false);v.id=s}}this.id=f.generateId(v);this.element=v;w=this.element.firstChild;if(w){var r=false,q=false,t=false;do{if(1==w.nodeType){if(!r&&f.hasClass(w,g.CSS_HEADER)){this.header=w;r=true}else{if(!q&&f.hasClass(w,g.CSS_BODY)){this.body=w;q=true}else{if(!t&&f.hasClass(w,g.CSS_FOOTER)){this.footer=w;t=true}}}}}while((w=w.nextSibling))}this.initDefaultConfig();f.addClass(this.element,g.CSS_MODULE);if(u){this.cfg.applyConfig(u,true)}if(!d.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)){this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true)}this.initEvent.fire(g)},initResizeMonitor:function(){var r=(i.gecko&&this.platform=="windows");if(r){var q=this;setTimeout(function(){q._initResizeMonitor()},0)}else{this._initResizeMonitor()}},_initResizeMonitor:function(){var q,s,u;function w(){g.textResizeEvent.fire()}if(!i.opera){s=f.get("_yuiResizeMonitor");var v=this._supportsCWResize();if(!s){s=document.createElement("iframe");if(this.isSecure&&g.RESIZE_MONITOR_SECURE_URL&&i.ie){s.src=g.RESIZE_MONITOR_SECURE_URL}if(!v){u=["<html><head><script ",'type="text/javascript">',"window.onresize=function(){window.parent.","YAHOO.widget.Module.textResizeEvent.","fire();};<","/script></head>","<body></body></html>"].join("");s.src="data:text/html;charset=utf-8,"+encodeURIComponent(u)}s.id="_yuiResizeMonitor";s.title="Text Resize Monitor";s.tabIndex=-1;s.setAttribute("role","presentation");s.style.position="absolute";s.style.visibility="hidden";var r=document.body,t=r.firstChild;if(t){r.insertBefore(s,t)}else{r.appendChild(s)}s.style.backgroundColor="transparent";s.style.borderWidth="0";s.style.width="2em";s.style.height="2em";s.style.left="0";s.style.top=(-1*(s.offsetHeight+g.RESIZE_MONITOR_BUFFER))+"px";s.style.visibility="visible";if(i.webkit){q=s.contentWindow.document;q.open();q.close()}}if(s&&s.contentWindow){g.textResizeEvent.subscribe(this.onDomResize,this,true);if(!g.textResizeInitialized){if(v){if(!n.on(s.contentWindow,"resize",w)){n.on(s,"resize",w)}}g.textResizeInitialized=true}this.resizeMonitor=s}}},_supportsCWResize:function(){var q=true;if(i.gecko&&i.gecko<=1.8){q=false}return q},onDomResize:function(s,r){var q=-1*(this.resizeMonitor.offsetHeight+g.RESIZE_MONITOR_BUFFER);this.resizeMonitor.style.top=q+"px";this.resizeMonitor.style.left="0"},setHeader:function(r){var q=this.header||(this.header=k());if(r.nodeName){q.innerHTML="";q.appendChild(r)}else{q.innerHTML=r}if(this._rendered){this._renderHeader()}this.changeHeaderEvent.fire(r);this.changeContentEvent.fire()},appendToHeader:function(r){var q=this.header||(this.header=k());q.appendChild(r);this.changeHeaderEvent.fire(r);this.changeContentEvent.fire()},setBody:function(r){var q=this.body||(this.body=b());if(r.nodeName){q.innerHTML="";q.appendChild(r)}else{q.innerHTML=r}if(this._rendered){this._renderBody()}this.changeBodyEvent.fire(r);this.changeContentEvent.fire()},appendToBody:function(r){var q=this.body||(this.body=b());q.appendChild(r);this.changeBodyEvent.fire(r);this.changeContentEvent.fire()},setFooter:function(r){var q=this.footer||(this.footer=c());if(r.nodeName){q.innerHTML="";q.appendChild(r)}else{q.innerHTML=r}if(this._rendered){this._renderFooter()}this.changeFooterEvent.fire(r);this.changeContentEvent.fire()},appendToFooter:function(r){var q=this.footer||(this.footer=c());q.appendChild(r);this.changeFooterEvent.fire(r);this.changeContentEvent.fire()},render:function(s,q){var t=this;function r(u){if(typeof u=="string"){u=document.getElementById(u)}if(u){t._addToParent(u,t.element);t.appendEvent.fire()}}this.beforeRenderEvent.fire();if(!q){q=this.element}if(s){r(s)}else{if(!f.inDocument(this.element)){return false}}this._renderHeader(q);this._renderBody(q);this._renderFooter(q);this._rendered=true;this.renderEvent.fire();return true},_renderHeader:function(q){q=q||this.element;if(this.header&&!f.inDocument(this.header)){var r=q.firstChild;if(r){q.insertBefore(this.header,r)}else{q.appendChild(this.header)}}},_renderBody:function(q){q=q||this.element;if(this.body&&!f.inDocument(this.body)){if(this.footer&&f.isAncestor(q,this.footer)){q.insertBefore(this.body,this.footer)}else{q.appendChild(this.body)}}},_renderFooter:function(q){q=q||this.element;if(this.footer&&!f.inDocument(this.footer)){q.appendChild(this.footer)}},destroy:function(q){var r,s=!(q);if(this.element){n.purgeElement(this.element,s);r=this.element.parentNode}if(r){r.removeChild(this.element)}this.element=null;this.header=null;this.body=null;this.footer=null;g.textResizeEvent.unsubscribe(this.onDomResize,this);this.cfg.destroy();this.cfg=null;this.destroyEvent.fire()},show:function(){this.cfg.setProperty("visible",true)},hide:function(){this.cfg.setProperty("visible",false)},configVisible:function(r,q,s){var t=q[0];if(t){if(this.beforeShowEvent.fire()){f.setStyle(this.element,"display","block");this.showEvent.fire()}}else{if(this.beforeHideEvent.fire()){f.setStyle(this.element,"display","none");this.hideEvent.fire()}}},configEffect:function(r,q,s){this._cachedEffects=(this.cacheEffects)?this._createEffects(q[0]):null},cacheEffects:true,_createEffects:function(t){var q=null,u,r,s;if(t){if(t instanceof Array){q=[];u=t.length;for(r=0;r<u;r++){s=t[r];if(s.effect){q[q.length]=s.effect(this,s.duration)}}}else{if(t.effect){q=[t.effect(this,t.duration)]}}}return q},configMonitorResize:function(s,r,t){var q=r[0];if(q){this.initResizeMonitor()}else{g.textResizeEvent.unsubscribe(this.onDomResize,this,true);this.resizeMonitor=null}},_addToParent:function(q,r){if(!this.cfg.getProperty("appendtodocumentbody")&&q===document.body&&q.firstChild){q.insertBefore(r,q.firstChild)}else{q.appendChild(r)}},toString:function(){return"Module "+this.id}};YAHOO.lang.augmentProto(g,YAHOO.util.EventProvider)}());(function(){YAHOO.widget.Overlay=function(p,o){YAHOO.widget.Overlay.superclass.constructor.call(this,p,o)};var i=YAHOO.lang,m=YAHOO.util.CustomEvent,g=YAHOO.widget.Module,n=YAHOO.util.Event,f=YAHOO.util.Dom,d=YAHOO.util.Config,k=YAHOO.env.ua,b=YAHOO.widget.Overlay,h="subscribe",e="unsubscribe",c="contained",j,a={BEFORE_MOVE:"beforeMove",MOVE:"move"},l={X:{key:"x",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},Y:{key:"y",validator:i.isNumber,suppressEvent:true,supercedes:["iframe"]},XY:{key:"xy",suppressEvent:true,supercedes:["iframe"]},CONTEXT:{key:"context",suppressEvent:true,supercedes:["iframe"]},FIXED_CENTER:{key:"fixedcenter",value:false,supercedes:["iframe","visible"]},WIDTH:{key:"width",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},HEIGHT:{key:"height",suppressEvent:true,supercedes:["context","fixedcenter","iframe"]},AUTO_FILL_HEIGHT:{key:"autofillheight",supercedes:["height"],value:"body"},ZINDEX:{key:"zindex",value:null},CONSTRAIN_TO_VIEWPORT:{key:"constraintoviewport",value:false,validator:i.isBoolean,supercedes:["iframe","x","y","xy"]},IFRAME:{key:"iframe",value:(k.ie==6?true:false),validator:i.isBoolean,supercedes:["zindex"]},PREVENT_CONTEXT_OVERLAP:{key:"preventcontextoverlap",value:false,validator:i.isBoolean,supercedes:["constraintoviewport"]}};b.IFRAME_SRC="javascript:false;";b.IFRAME_OFFSET=3;b.VIEWPORT_OFFSET=10;b.TOP_LEFT="tl";b.TOP_RIGHT="tr";b.BOTTOM_LEFT="bl";b.BOTTOM_RIGHT="br";b.PREVENT_OVERLAP_X={tltr:true,blbr:true,brbl:true,trtl:true};b.PREVENT_OVERLAP_Y={trbr:true,tlbl:true,bltl:true,brtr:true};b.CSS_OVERLAY="yui-overlay";b.CSS_HIDDEN="yui-overlay-hidden";b.CSS_IFRAME="yui-overlay-iframe";b.STD_MOD_RE=/^\s*?(body|footer|header)\s*?$/i;b.windowScrollEvent=new m("windowScroll");b.windowResizeEvent=new m("windowResize");b.windowScrollHandler=function(p){var o=n.getTarget(p);if(!o||o===window||o===window.document){if(k.ie){if(!window.scrollEnd){window.scrollEnd=-1}clearTimeout(window.scrollEnd);window.scrollEnd=setTimeout(function(){b.windowScrollEvent.fire()},1)}else{b.windowScrollEvent.fire()}}};b.windowResizeHandler=function(o){if(k.ie){if(!window.resizeEnd){window.resizeEnd=-1}clearTimeout(window.resizeEnd);window.resizeEnd=setTimeout(function(){b.windowResizeEvent.fire()},100)}else{b.windowResizeEvent.fire()}};b._initialized=null;if(b._initialized===null){n.on(window,"scroll",b.windowScrollHandler);n.on(window,"resize",b.windowResizeHandler);b._initialized=true}b._TRIGGER_MAP={windowScroll:b.windowScrollEvent,windowResize:b.windowResizeEvent,textResize:g.textResizeEvent};YAHOO.extend(b,g,{CONTEXT_TRIGGERS:[],init:function(p,o){b.superclass.init.call(this,p);this.beforeInitEvent.fire(b);f.addClass(this.element,b.CSS_OVERLAY);if(o){this.cfg.applyConfig(o,true)}if(this.platform=="mac"&&k.gecko){if(!d.alreadySubscribed(this.showEvent,this.showMacGeckoScrollbars,this)){this.showEvent.subscribe(this.showMacGeckoScrollbars,this,true)}if(!d.alreadySubscribed(this.hideEvent,this.hideMacGeckoScrollbars,this)){this.hideEvent.subscribe(this.hideMacGeckoScrollbars,this,true)}}this.initEvent.fire(b)},initEvents:function(){b.superclass.initEvents.call(this);var o=m.LIST;this.beforeMoveEvent=this.createEvent(a.BEFORE_MOVE);this.beforeMoveEvent.signature=o;this.moveEvent=this.createEvent(a.MOVE);this.moveEvent.signature=o},initDefaultConfig:function(){b.superclass.initDefaultConfig.call(this);var o=this.cfg;o.addProperty(l.X.key,{handler:this.configX,validator:l.X.validator,suppressEvent:l.X.suppressEvent,supercedes:l.X.supercedes});o.addProperty(l.Y.key,{handler:this.configY,validator:l.Y.validator,suppressEvent:l.Y.suppressEvent,supercedes:l.Y.supercedes});o.addProperty(l.XY.key,{handler:this.configXY,suppressEvent:l.XY.suppressEvent,supercedes:l.XY.supercedes});o.addProperty(l.CONTEXT.key,{handler:this.configContext,suppressEvent:l.CONTEXT.suppressEvent,supercedes:l.CONTEXT.supercedes});o.addProperty(l.FIXED_CENTER.key,{handler:this.configFixedCenter,value:l.FIXED_CENTER.value,validator:l.FIXED_CENTER.validator,supercedes:l.FIXED_CENTER.supercedes});o.addProperty(l.WIDTH.key,{handler:this.configWidth,suppressEvent:l.WIDTH.suppressEvent,supercedes:l.WIDTH.supercedes});o.addProperty(l.HEIGHT.key,{handler:this.configHeight,suppressEvent:l.HEIGHT.suppressEvent,supercedes:l.HEIGHT.supercedes});o.addProperty(l.AUTO_FILL_HEIGHT.key,{handler:this.configAutoFillHeight,value:l.AUTO_FILL_HEIGHT.value,validator:this._validateAutoFill,supercedes:l.AUTO_FILL_HEIGHT.supercedes});o.addProperty(l.ZINDEX.key,{handler:this.configzIndex,value:l.ZINDEX.value});o.addProperty(l.CONSTRAIN_TO_VIEWPORT.key,{handler:this.configConstrainToViewport,value:l.CONSTRAIN_TO_VIEWPORT.value,validator:l.CONSTRAIN_TO_VIEWPORT.validator,supercedes:l.CONSTRAIN_TO_VIEWPORT.supercedes});o.addProperty(l.IFRAME.key,{handler:this.configIframe,value:l.IFRAME.value,validator:l.IFRAME.validator,supercedes:l.IFRAME.supercedes});o.addProperty(l.PREVENT_CONTEXT_OVERLAP.key,{value:l.PREVENT_CONTEXT_OVERLAP.value,validator:l.PREVENT_CONTEXT_OVERLAP.validator,supercedes:l.PREVENT_CONTEXT_OVERLAP.supercedes})},moveTo:function(o,p){this.cfg.setProperty("xy",[o,p])},hideMacGeckoScrollbars:function(){f.replaceClass(this.element,"show-scrollbars","hide-scrollbars")},showMacGeckoScrollbars:function(){f.replaceClass(this.element,"hide-scrollbars","show-scrollbars")},_setDomVisibility:function(o){f.setStyle(this.element,"visibility",(o)?"visible":"hidden");var p=b.CSS_HIDDEN;if(o){f.removeClass(this.element,p)}else{f.addClass(this.element,p)}},configVisible:function(x,w,t){var p=w[0],B=f.getStyle(this.element,"visibility"),o=this._cachedEffects||this._createEffects(this.cfg.getProperty("effect")),A=(this.platform=="mac"&&k.gecko),y=d.alreadySubscribed,q,v,s,r,u,z;if(B=="inherit"){v=this.element.parentNode;while(v.nodeType!=9&&v.nodeType!=11){B=f.getStyle(v,"visibility");if(B!="inherit"){break}v=v.parentNode}if(B=="inherit"){B="visible"}}if(p){if(A){this.showMacGeckoScrollbars()}if(o){if(p){if(B!="visible"||B===""||this._fadingOut){if(this.beforeShowEvent.fire()){z=o.length;for(s=0;s<z;s++){q=o[s];if(s===0&&!y(q.animateInCompleteEvent,this.showEvent.fire,this.showEvent)){q.animateInCompleteEvent.subscribe(this.showEvent.fire,this.showEvent,true)}q.animateIn()}}}}}else{if(B!="visible"||B===""){if(this.beforeShowEvent.fire()){this._setDomVisibility(true);this.cfg.refireEvent("iframe");this.showEvent.fire()}}else{this._setDomVisibility(true)}}}else{if(A){this.hideMacGeckoScrollbars()}if(o){if(B=="visible"||this._fadingIn){if(this.beforeHideEvent.fire()){z=o.length;for(r=0;r<z;r++){u=o[r];if(r===0&&!y(u.animateOutCompleteEvent,this.hideEvent.fire,this.hideEvent)){u.animateOutCompleteEvent.subscribe(this.hideEvent.fire,this.hideEvent,true)}u.animateOut()}}}else{if(B===""){this._setDomVisibility(false)}}}else{if(B=="visible"||B===""){if(this.beforeHideEvent.fire()){this._setDomVisibility(false);this.hideEvent.fire()}}else{this._setDomVisibility(false)}}}},doCenterOnDOMEvent:function(){var o=this.cfg,p=o.getProperty("fixedcenter");if(o.getProperty("visible")){if(p&&(p!==c||this.fitsInViewport())){this.center()}}},fitsInViewport:function(){var s=b.VIEWPORT_OFFSET,q=this.element,t=q.offsetWidth,r=q.offsetHeight,o=f.getViewportWidth(),p=f.getViewportHeight();return((t+s<o)&&(r+s<p))},configFixedCenter:function(s,q,t){var u=q[0],p=d.alreadySubscribed,r=b.windowResizeEvent,o=b.windowScrollEvent;if(u){this.center();if(!p(this.beforeShowEvent,this.center)){this.beforeShowEvent.subscribe(this.center)}if(!p(r,this.doCenterOnDOMEvent,this)){r.subscribe(this.doCenterOnDOMEvent,this,true)}if(!p(o,this.doCenterOnDOMEvent,this)){o.subscribe(this.doCenterOnDOMEvent,this,true)}}else{this.beforeShowEvent.unsubscribe(this.center);r.unsubscribe(this.doCenterOnDOMEvent,this);o.unsubscribe(this.doCenterOnDOMEvent,this)}},configHeight:function(r,p,s){var o=p[0],q=this.element;f.setStyle(q,"height",o);this.cfg.refireEvent("iframe")},configAutoFillHeight:function(t,s,p){var v=s[0],q=this.cfg,u="autofillheight",w="height",r=q.getProperty(u),o=this._autoFillOnHeightChange;q.unsubscribeFromConfigEvent(w,o);g.textResizeEvent.unsubscribe(o);this.changeContentEvent.unsubscribe(o);if(r&&v!==r&&this[r]){f.setStyle(this[r],w,"")}if(v){v=i.trim(v.toLowerCase());q.subscribeToConfigEvent(w,o,this[v],this);g.textResizeEvent.subscribe(o,this[v],this);this.changeContentEvent.subscribe(o,this[v],this);q.setProperty(u,v,true)}},configWidth:function(r,o,s){var q=o[0],p=this.element;f.setStyle(p,"width",q);this.cfg.refireEvent("iframe")},configzIndex:function(q,o,r){var s=o[0],p=this.element;if(!s){s=f.getStyle(p,"zIndex");if(!s||isNaN(s)){s=0}}if(this.iframe||this.cfg.getProperty("iframe")===true){if(s<=0){s=1}}f.setStyle(p,"zIndex",s);this.cfg.setProperty("zIndex",s,true);if(this.iframe){this.stackIframe()}},configXY:function(q,p,r){var t=p[0],o=t[0],s=t[1];this.cfg.setProperty("x",o);this.cfg.setProperty("y",s);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s])},configX:function(q,p,r){var o=p[0],s=this.cfg.getProperty("y");this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setX(this.element,o,true);this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s])},configY:function(q,p,r){var o=this.cfg.getProperty("x"),s=p[0];this.cfg.setProperty("x",o,true);this.cfg.setProperty("y",s,true);this.beforeMoveEvent.fire([o,s]);o=this.cfg.getProperty("x");s=this.cfg.getProperty("y");f.setY(this.element,s,true);this.cfg.setProperty("xy",[o,s],true);this.cfg.refireEvent("iframe");this.moveEvent.fire([o,s])},showIframe:function(){var p=this.iframe,o;if(p){o=this.element.parentNode;if(o!=p.parentNode){this._addToParent(o,p)}p.style.display="block"}},hideIframe:function(){if(this.iframe){this.iframe.style.display="none"}},syncIframe:function(){var o=this.iframe,q=this.element,s=b.IFRAME_OFFSET,p=(s*2),r;if(o){o.style.width=(q.offsetWidth+p+"px");o.style.height=(q.offsetHeight+p+"px");r=this.cfg.getProperty("xy");if(!i.isArray(r)||(isNaN(r[0])||isNaN(r[1]))){this.syncPosition();r=this.cfg.getProperty("xy")}f.setXY(o,[(r[0]-s),(r[1]-s)])}},stackIframe:function(){if(this.iframe){var o=f.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(o)&&!isNaN(o)){f.setStyle(this.iframe,"zIndex",(o-1))}}},configIframe:function(r,q,s){var o=q[0];function t(){var v=this.iframe,w=this.element,x;if(!v){if(!j){j=document.createElement("iframe");if(this.isSecure){j.src=b.IFRAME_SRC}if(k.ie){j.style.filter="alpha(opacity=0)";j.frameBorder=0}else{j.style.opacity="0"}j.style.position="absolute";j.style.border="none";j.style.margin="0";j.style.padding="0";j.style.display="none";j.tabIndex=-1;j.className=b.CSS_IFRAME}v=j.cloneNode(false);v.id=this.id+"_f";x=w.parentNode;var u=x||document.body;this._addToParent(u,v);this.iframe=v}this.showIframe();this.syncIframe();this.stackIframe();if(!this._hasIframeEventListeners){this.showEvent.subscribe(this.showIframe);this.hideEvent.subscribe(this.hideIframe);this.changeContentEvent.subscribe(this.syncIframe);this._hasIframeEventListeners=true}}function p(){t.call(this);this.beforeShowEvent.unsubscribe(p);this._iframeDeferred=false}if(o){if(this.cfg.getProperty("visible")){t.call(this)}else{if(!this._iframeDeferred){this.beforeShowEvent.subscribe(p);this._iframeDeferred=true}}}else{this.hideIframe();if(this._hasIframeEventListeners){this.showEvent.unsubscribe(this.showIframe);this.hideEvent.unsubscribe(this.hideIframe);this.changeContentEvent.unsubscribe(this.syncIframe);this._hasIframeEventListeners=false}}},_primeXYFromDOM:function(){if(YAHOO.lang.isUndefined(this.cfg.getProperty("xy"))){this.syncPosition();this.cfg.refireEvent("xy");this.beforeShowEvent.unsubscribe(this._primeXYFromDOM)}},configConstrainToViewport:function(p,o,q){var r=o[0];if(r){if(!d.alreadySubscribed(this.beforeMoveEvent,this.enforceConstraints,this)){this.beforeMoveEvent.subscribe(this.enforceConstraints,this,true)}if(!d.alreadySubscribed(this.beforeShowEvent,this._primeXYFromDOM)){this.beforeShowEvent.subscribe(this._primeXYFromDOM)}}else{this.beforeShowEvent.unsubscribe(this._primeXYFromDOM);this.beforeMoveEvent.unsubscribe(this.enforceConstraints,this)}},configContext:function(u,t,q){var x=t[0],r,o,v,s,p,w=this.CONTEXT_TRIGGERS;if(x){r=x[0];o=x[1];v=x[2];s=x[3];p=x[4];if(w&&w.length>0){s=(s||[]).concat(w)}if(r){if(typeof r=="string"){this.cfg.setProperty("context",[document.getElementById(r),o,v,s,p],true)}if(o&&v){this.align(o,v,p)}if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger)}if(s){this._processTriggers(s,h,this._alignOnTrigger);this._contextTriggers=s}}}},_alignOnTrigger:function(p,o){this.align()},_findTriggerCE:function(o){var p=null;if(o instanceof m){p=o}else{if(b._TRIGGER_MAP[o]){p=b._TRIGGER_MAP[o]}}return p},_processTriggers:function(s,v,r){var q,u;for(var p=0,o=s.length;p<o;++p){q=s[p];u=this._findTriggerCE(q);if(u){u[v](r,this,true)}else{this[v](q,r)}}},align:function(p,w,s){var v=this.cfg.getProperty("context"),t=this,o,q,u;function r(z,A){var y=null,x=null;switch(p){case b.TOP_LEFT:y=A;x=z;break;case b.TOP_RIGHT:y=A-q.offsetWidth;x=z;break;case b.BOTTOM_LEFT:y=A;x=z-q.offsetHeight;break;case b.BOTTOM_RIGHT:y=A-q.offsetWidth;x=z-q.offsetHeight;break}if(y!==null&&x!==null){if(s){y+=s[0];x+=s[1]}t.moveTo(y,x)}}if(v){o=v[0];q=this.element;t=this;if(!p){p=v[1]}if(!w){w=v[2]}if(!s&&v[4]){s=v[4]}if(q&&o){u=f.getRegion(o);switch(w){case b.TOP_LEFT:r(u.top,u.left);break;case b.TOP_RIGHT:r(u.top,u.right);break;case b.BOTTOM_LEFT:r(u.bottom,u.left);break;case b.BOTTOM_RIGHT:r(u.bottom,u.right);break}}}},enforceConstraints:function(p,o,q){var s=o[0];var r=this.getConstrainedXY(s[0],s[1]);this.cfg.setProperty("x",r[0],true);this.cfg.setProperty("y",r[1],true);this.cfg.setProperty("xy",r,true)},_getConstrainedPos:function(y,p){var t=this.element,r=b.VIEWPORT_OFFSET,A=(y=="x"),z=(A)?t.offsetWidth:t.offsetHeight,s=(A)?f.getViewportWidth():f.getViewportHeight(),D=(A)?f.getDocumentScrollLeft():f.getDocumentScrollTop(),C=(A)?b.PREVENT_OVERLAP_X:b.PREVENT_OVERLAP_Y,o=this.cfg.getProperty("context"),u=(z+r<s),w=this.cfg.getProperty("preventcontextoverlap")&&o&&C[(o[1]+o[2])],v=D+r,B=D+s-z-r,q=p;if(p<v||p>B){if(w){q=this._preventOverlap(y,o[0],z,s,D)}else{if(u){if(p<v){q=v}else{if(p>B){q=B}}}else{q=v}}}return q},_preventOverlap:function(y,w,z,u,C){var A=(y=="x"),t=b.VIEWPORT_OFFSET,s=this,q=((A)?f.getX(w):f.getY(w))-C,o=(A)?w.offsetWidth:w.offsetHeight,p=q-t,r=(u-(q+o))-t,D=false,v=function(){var x;if((s.cfg.getProperty(y)-C)>q){x=(q-z)}else{x=(q+o)}s.cfg.setProperty(y,(x+C),true);return x},B=function(){var E=((s.cfg.getProperty(y)-C)>q)?r:p,x;if(z>E){if(D){v()}else{v();D=true;x=B()}}return x};B();return this.cfg.getProperty(y)},getConstrainedX:function(o){return this._getConstrainedPos("x",o)},getConstrainedY:function(o){return this._getConstrainedPos("y",o)},getConstrainedXY:function(o,p){return[this.getConstrainedX(o),this.getConstrainedY(p)]},center:function(){var r=b.VIEWPORT_OFFSET,s=this.element.offsetWidth,q=this.element.offsetHeight,p=f.getViewportWidth(),t=f.getViewportHeight(),o,u;if(s<p){o=(p/2)-(s/2)+f.getDocumentScrollLeft()}else{o=r+f.getDocumentScrollLeft()}if(q<t){u=(t/2)-(q/2)+f.getDocumentScrollTop()}else{u=r+f.getDocumentScrollTop()}this.cfg.setProperty("xy",[parseInt(o,10),parseInt(u,10)]);this.cfg.refireEvent("iframe");if(k.webkit){this.forceContainerRedraw()}},syncPosition:function(){var o=f.getXY(this.element);this.cfg.setProperty("x",o[0],true);this.cfg.setProperty("y",o[1],true);this.cfg.setProperty("xy",o,true)},onDomResize:function(q,p){var o=this;b.superclass.onDomResize.call(this,q,p);setTimeout(function(){o.syncPosition();o.cfg.refireEvent("iframe");o.cfg.refireEvent("context")},0)},_getComputedHeight:(function(){if(document.defaultView&&document.defaultView.getComputedStyle){return function(p){var o=null;if(p.ownerDocument&&p.ownerDocument.defaultView){var q=p.ownerDocument.defaultView.getComputedStyle(p,"");if(q){o=parseInt(q.height,10)}}return(i.isNumber(o))?o:null}}else{return function(p){var o=null;if(p.style.pixelHeight){o=p.style.pixelHeight}return(i.isNumber(o))?o:null}}})(),_validateAutoFillHeight:function(o){return(!o)||(i.isString(o)&&b.STD_MOD_RE.test(o))},_autoFillOnHeightChange:function(r,p,q){var o=this.cfg.getProperty("height");if((o&&o!=="auto")||(o===0)){this.fillHeight(q)}},_getPreciseHeight:function(p){var o=p.offsetHeight;if(p.getBoundingClientRect){var q=p.getBoundingClientRect();o=q.bottom-q.top}return o},fillHeight:function(r){if(r){var p=this.innerElement||this.element,o=[this.header,this.body,this.footer],v,w=0,x=0,t=0,q=false;for(var u=0,s=o.length;u<s;u++){v=o[u];if(v){if(r!==v){x+=this._getPreciseHeight(v)}else{q=true}}}if(q){if(k.ie||k.opera){f.setStyle(r,"height",0+"px")}w=this._getComputedHeight(p);if(w===null){f.addClass(p,"yui-override-padding");w=p.clientHeight;f.removeClass(p,"yui-override-padding")}t=Math.max(w-x,0);f.setStyle(r,"height",t+"px");if(r.offsetHeight!=t){t=Math.max(t-(r.offsetHeight-t),0)}f.setStyle(r,"height",t+"px")}}},bringToTop:function(){var s=[],r=this.element;function v(z,y){var B=f.getStyle(z,"zIndex"),A=f.getStyle(y,"zIndex"),x=(!B||isNaN(B))?0:parseInt(B,10),w=(!A||isNaN(A))?0:parseInt(A,10);if(x>w){return -1}else{if(x<w){return 1}else{return 0}}}function q(y){var x=f.hasClass(y,b.CSS_OVERLAY),w=YAHOO.widget.Panel;if(x&&!f.isAncestor(r,y)){if(w&&f.hasClass(y,w.CSS_PANEL)){s[s.length]=y.parentNode}else{s[s.length]=y}}}f.getElementsBy(q,"div",document.body);s.sort(v);var o=s[0],u;if(o){u=f.getStyle(o,"zIndex");if(!isNaN(u)){var t=false;if(o!=r){t=true}else{if(s.length>1){var p=f.getStyle(s[1],"zIndex");if(!isNaN(p)&&(u==p)){t=true}}}if(t){this.cfg.setProperty("zindex",(parseInt(u,10)+2))}}}},destroy:function(o){if(this.iframe){this.iframe.parentNode.removeChild(this.iframe)}this.iframe=null;b.windowResizeEvent.unsubscribe(this.doCenterOnDOMEvent,this);b.windowScrollEvent.unsubscribe(this.doCenterOnDOMEvent,this);g.textResizeEvent.unsubscribe(this._autoFillOnHeightChange);if(this._contextTriggers){this._processTriggers(this._contextTriggers,e,this._alignOnTrigger)}b.superclass.destroy.call(this,o)},forceContainerRedraw:function(){var o=this;f.addClass(o.element,"yui-force-redraw");setTimeout(function(){f.removeClass(o.element,"yui-force-redraw")},0)},toString:function(){return"Overlay "+this.id}})}());(function(){YAHOO.widget.OverlayManager=function(g){this.init(g)};var d=YAHOO.widget.Overlay,c=YAHOO.util.Event,e=YAHOO.util.Dom,b=YAHOO.util.Config,f=YAHOO.util.CustomEvent,a=YAHOO.widget.OverlayManager;a.CSS_FOCUSED="focused";a.prototype={constructor:a,overlays:null,initDefaultConfig:function(){this.cfg.addProperty("overlays",{suppressEvent:true});this.cfg.addProperty("focusevent",{value:"mousedown"})},init:function(i){this.cfg=new b(this);this.initDefaultConfig();if(i){this.cfg.applyConfig(i,true)}this.cfg.fireQueue();var h=null;this.getActive=function(){return h};this.focus=function(j){var k=this.find(j);if(k){k.focus()}};this.remove=function(k){var m=this.find(k),j;if(m){if(h==m){h=null}var l=(m.element===null&&m.cfg===null)?true:false;if(!l){j=e.getStyle(m.element,"zIndex");m.cfg.setProperty("zIndex",-1000,true)}this.overlays.sort(this.compareZIndexDesc);this.overlays=this.overlays.slice(0,(this.overlays.length-1));m.hideEvent.unsubscribe(m.blur);m.destroyEvent.unsubscribe(this._onOverlayDestroy,m);m.focusEvent.unsubscribe(this._onOverlayFocusHandler,m);m.blurEvent.unsubscribe(this._onOverlayBlurHandler,m);if(!l){c.removeListener(m.element,this.cfg.getProperty("focusevent"),this._onOverlayElementFocus);m.cfg.setProperty("zIndex",j,true);m.cfg.setProperty("manager",null)}if(m.focusEvent._managed){m.focusEvent=null}if(m.blurEvent._managed){m.blurEvent=null}if(m.focus._managed){m.focus=null}if(m.blur._managed){m.blur=null}}};this.blurAll=function(){var k=this.overlays.length,j;if(k>0){j=k-1;do{this.overlays[j].blur()}while(j--)}};this._manageBlur=function(j){var k=false;if(h==j){e.removeClass(h.element,a.CSS_FOCUSED);h=null;k=true}return k};this._manageFocus=function(j){var k=false;if(h!=j){if(h){h.blur()}h=j;this.bringToTop(h);e.addClass(h.element,a.CSS_FOCUSED);k=true}return k};var g=this.cfg.getProperty("overlays");if(!this.overlays){this.overlays=[]}if(g){this.register(g);this.overlays.sort(this.compareZIndexDesc)}},_onOverlayElementFocus:function(i){var g=c.getTarget(i),h=this.close;if(h&&(g==h||e.isAncestor(h,g))){this.blur()}else{this.focus()}},_onOverlayDestroy:function(h,g,i){this.remove(i)},_onOverlayFocusHandler:function(h,g,i){this._manageFocus(i)},_onOverlayBlurHandler:function(h,g,i){this._manageBlur(i)},_bindFocus:function(g){var h=this;if(!g.focusEvent){g.focusEvent=g.createEvent("focus");g.focusEvent.signature=f.LIST;g.focusEvent._managed=true}else{g.focusEvent.subscribe(h._onOverlayFocusHandler,g,h)}if(!g.focus){c.on(g.element,h.cfg.getProperty("focusevent"),h._onOverlayElementFocus,null,g);g.focus=function(){if(h._manageFocus(this)){if(this.cfg.getProperty("visible")&&this.focusFirst){this.focusFirst()}this.focusEvent.fire()}};g.focus._managed=true}},_bindBlur:function(g){var h=this;if(!g.blurEvent){g.blurEvent=g.createEvent("blur");g.blurEvent.signature=f.LIST;g.focusEvent._managed=true}else{g.blurEvent.subscribe(h._onOverlayBlurHandler,g,h)}if(!g.blur){g.blur=function(){if(h._manageBlur(this)){this.blurEvent.fire()}};g.blur._managed=true}g.hideEvent.subscribe(g.blur)},_bindDestroy:function(g){var h=this;g.destroyEvent.subscribe(h._onOverlayDestroy,g,h)},_syncZIndex:function(g){var h=e.getStyle(g.element,"zIndex");if(!isNaN(h)){g.cfg.setProperty("zIndex",parseInt(h,10))}else{g.cfg.setProperty("zIndex",0)}},register:function(g){var k=false,h,j;if(g instanceof d){g.cfg.addProperty("manager",{value:this});this._bindFocus(g);this._bindBlur(g);this._bindDestroy(g);this._syncZIndex(g);this.overlays.push(g);this.bringToTop(g);k=true}else{if(g instanceof Array){for(h=0,j=g.length;h<j;h++){k=this.register(g[h])||k}}}return k},bringToTop:function(m){var i=this.find(m),l,g,j;if(i){j=this.overlays;j.sort(this.compareZIndexDesc);g=j[0];if(g){l=e.getStyle(g.element,"zIndex");if(!isNaN(l)){var k=false;if(g!==i){k=true}else{if(j.length>1){var h=e.getStyle(j[1].element,"zIndex");if(!isNaN(h)&&(l==h)){k=true}}}if(k){i.cfg.setProperty("zindex",(parseInt(l,10)+2))}}j.sort(this.compareZIndexDesc)}}},find:function(g){var l=g instanceof d,j=this.overlays,p=j.length,k=null,m,h;if(l||typeof g=="string"){for(h=p-1;h>=0;h--){m=j[h];if((l&&(m===g))||(m.id==g)){k=m;break}}}return k},compareZIndexDesc:function(j,i){var h=(j.cfg)?j.cfg.getProperty("zIndex"):null,g=(i.cfg)?i.cfg.getProperty("zIndex"):null;if(h===null&&g===null){return 0}else{if(h===null){return 1}else{if(g===null){return -1}else{if(h>g){return -1}else{if(h<g){return 1}else{return 0}}}}}},showAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].show()}},hideAll:function(){var h=this.overlays,j=h.length,g;for(g=j-1;g>=0;g--){h[g].hide()}},toString:function(){return"OverlayManager"}}}());(function(){YAHOO.widget.Tooltip=function(p,o){YAHOO.widget.Tooltip.superclass.constructor.call(this,p,o)};var e=YAHOO.lang,n=YAHOO.util.Event,m=YAHOO.util.CustomEvent,c=YAHOO.util.Dom,j=YAHOO.widget.Tooltip,h=YAHOO.env.ua,g=(h.ie&&(h.ie<=6||document.compatMode=="BackCompat")),f,i={PREVENT_OVERLAP:{key:"preventoverlap",value:true,validator:e.isBoolean,supercedes:["x","y","xy"]},SHOW_DELAY:{key:"showdelay",value:200,validator:e.isNumber},AUTO_DISMISS_DELAY:{key:"autodismissdelay",value:5000,validator:e.isNumber},HIDE_DELAY:{key:"hidedelay",value:250,validator:e.isNumber},TEXT:{key:"text",suppressEvent:true},CONTAINER:{key:"container"},DISABLED:{key:"disabled",value:false,suppressEvent:true},XY_OFFSET:{key:"xyoffset",value:[0,25],suppressEvent:true}},a={CONTEXT_MOUSE_OVER:"contextMouseOver",CONTEXT_MOUSE_OUT:"contextMouseOut",CONTEXT_TRIGGER:"contextTrigger"};j.CSS_TOOLTIP="yui-tt";function k(q,o){var p=this.cfg,r=p.getProperty("width");if(r==o){p.setProperty("width",q)}}function d(p,o){if("_originalWidth" in this){k.call(this,this._originalWidth,this._forcedWidth)}var q=document.body,u=this.cfg,t=u.getProperty("width"),r,s;if((!t||t=="auto")&&(u.getProperty("container")!=q||u.getProperty("x")>=c.getViewportWidth()||u.getProperty("y")>=c.getViewportHeight())){s=this.element.cloneNode(true);s.style.visibility="hidden";s.style.top="0px";s.style.left="0px";q.appendChild(s);r=(s.offsetWidth+"px");q.removeChild(s);s=null;u.setProperty("width",r);u.refireEvent("xy");this._originalWidth=t||"";this._forcedWidth=r}}function b(p,o,q){this.render(q)}function l(){n.onDOMReady(b,this.cfg.getProperty("container"),this)}YAHOO.extend(j,YAHOO.widget.Overlay,{init:function(p,o){j.superclass.init.call(this,p);this.beforeInitEvent.fire(j);c.addClass(this.element,j.CSS_TOOLTIP);if(o){this.cfg.applyConfig(o,true)}this.cfg.queueProperty("visible",false);this.cfg.queueProperty("constraintoviewport",true);this.setBody("");this.subscribe("changeContent",d);this.subscribe("init",l);this.subscribe("render",this.onRender);this.initEvent.fire(j)},initEvents:function(){j.superclass.initEvents.call(this);var o=m.LIST;this.contextMouseOverEvent=this.createEvent(a.CONTEXT_MOUSE_OVER);this.contextMouseOverEvent.signature=o;this.contextMouseOutEvent=this.createEvent(a.CONTEXT_MOUSE_OUT);this.contextMouseOutEvent.signature=o;this.contextTriggerEvent=this.createEvent(a.CONTEXT_TRIGGER);this.contextTriggerEvent.signature=o},initDefaultConfig:function(){j.superclass.initDefaultConfig.call(this);this.cfg.addProperty(i.PREVENT_OVERLAP.key,{value:i.PREVENT_OVERLAP.value,validator:i.PREVENT_OVERLAP.validator,supercedes:i.PREVENT_OVERLAP.supercedes});this.cfg.addProperty(i.SHOW_DELAY.key,{handler:this.configShowDelay,value:200,validator:i.SHOW_DELAY.validator});this.cfg.addProperty(i.AUTO_DISMISS_DELAY.key,{handler:this.configAutoDismissDelay,value:i.AUTO_DISMISS_DELAY.value,validator:i.AUTO_DISMISS_DELAY.validator});this.cfg.addProperty(i.HIDE_DELAY.key,{handler:this.configHideDelay,value:i.HIDE_DELAY.value,validator:i.HIDE_DELAY.validator});this.cfg.addProperty(i.TEXT.key,{handler:this.configText,suppressEvent:i.TEXT.suppressEvent});this.cfg.addProperty(i.CONTAINER.key,{handler:this.configContainer,value:document.body});this.cfg.addProperty(i.DISABLED.key,{handler:this.configContainer,value:i.DISABLED.value,supressEvent:i.DISABLED.suppressEvent});this.cfg.addProperty(i.XY_OFFSET.key,{value:i.XY_OFFSET.value.concat(),supressEvent:i.XY_OFFSET.suppressEvent})},configText:function(p,o,q){var r=o[0];if(r){this.setBody(r)}},configContainer:function(q,p,r){var o=p[0];if(typeof o=="string"){this.cfg.setProperty("container",document.getElementById(o),true)}},_removeEventListeners:function(){var r=this._context,o,q,p;if(r){o=r.length;if(o>0){p=o-1;do{q=r[p];n.removeListener(q,"mouseover",this.onContextMouseOver);n.removeListener(q,"mousemove",this.onContextMouseMove);n.removeListener(q,"mouseout",this.onContextMouseOut)}while(p--)}}},configContext:function(t,p,u){var s=p[0],v,o,r,q;if(s){if(!(s instanceof Array)){if(typeof s=="string"){this.cfg.setProperty("context",[document.getElementById(s)],true)}else{this.cfg.setProperty("context",[s],true)}s=this.cfg.getProperty("context")}this._removeEventListeners();this._context=s;v=this._context;if(v){o=v.length;if(o>0){q=o-1;do{r=v[q];n.on(r,"mouseover",this.onContextMouseOver,this);n.on(r,"mousemove",this.onContextMouseMove,this);n.on(r,"mouseout",this.onContextMouseOut,this)}while(q--)}}}},onContextMouseMove:function(p,o){o.pageX=n.getPageX(p);o.pageY=n.getPageY(p)},onContextMouseOver:function(q,p){var o=this;if(o.title){p._tempTitle=o.title;o.title=""}if(p.fireEvent("contextMouseOver",o,q)!==false&&!p.cfg.getProperty("disabled")){if(p.hideProcId){clearTimeout(p.hideProcId);p.hideProcId=null}n.on(o,"mousemove",p.onContextMouseMove,p);p.showProcId=p.doShow(q,o)}},onContextMouseOut:function(q,p){var o=this;if(p._tempTitle){o.title=p._tempTitle;p._tempTitle=null}if(p.showProcId){clearTimeout(p.showProcId);p.showProcId=null}if(p.hideProcId){clearTimeout(p.hideProcId);p.hideProcId=null}p.fireEvent("contextMouseOut",o,q);p.hideProcId=setTimeout(function(){p.hide()},p.cfg.getProperty("hidedelay"))},doShow:function(r,o){var t=this.cfg.getProperty("xyoffset"),p=t[0],s=t[1],q=this;if(h.opera&&o.tagName&&o.tagName.toUpperCase()=="A"){s+=12}return setTimeout(function(){var u=q.cfg.getProperty("text");if(q._tempTitle&&(u===""||YAHOO.lang.isUndefined(u)||YAHOO.lang.isNull(u))){q.setBody(q._tempTitle)}else{q.cfg.refireEvent("text")}q.moveTo(q.pageX+p,q.pageY+s);if(q.cfg.getProperty("preventoverlap")){q.preventOverlap(q.pageX,q.pageY)}n.removeListener(o,"mousemove",q.onContextMouseMove);q.contextTriggerEvent.fire(o);q.show();q.hideProcId=q.doHide()},this.cfg.getProperty("showdelay"))},doHide:function(){var o=this;return setTimeout(function(){o.hide()},this.cfg.getProperty("autodismissdelay"))},preventOverlap:function(s,r){var o=this.element.offsetHeight,q=new YAHOO.util.Point(s,r),p=c.getRegion(this.element);p.top-=5;p.left-=5;p.right+=5;p.bottom+=5;if(p.contains(q)){this.cfg.setProperty("y",(r-o-5))}},onRender:function(s,r){function t(){var w=this.element,v=this.underlay;if(v){v.style.width=(w.offsetWidth+6)+"px";v.style.height=(w.offsetHeight+1)+"px"}}function p(){c.addClass(this.underlay,"yui-tt-shadow-visible");if(h.ie){this.forceUnderlayRedraw()}}function o(){c.removeClass(this.underlay,"yui-tt-shadow-visible")}function u(){var x=this.underlay,w,v,z,y;if(!x){w=this.element;v=YAHOO.widget.Module;z=h.ie;y=this;if(!f){f=document.createElement("div");f.className="yui-tt-shadow"}x=f.cloneNode(false);w.appendChild(x);this.underlay=x;this._shadow=this.underlay;p.call(this);this.subscribe("beforeShow",p);this.subscribe("hide",o);if(g){window.setTimeout(function(){t.call(y)},0);this.cfg.subscribeToConfigEvent("width",t);this.cfg.subscribeToConfigEvent("height",t);this.subscribe("changeContent",t);v.textResizeEvent.subscribe(t,this,true);this.subscribe("destroy",function(){v.textResizeEvent.unsubscribe(t,this)})}}}function q(){u.call(this);this.unsubscribe("beforeShow",q)}if(this.cfg.getProperty("visible")){u.call(this)}else{this.subscribe("beforeShow",q)}},forceUnderlayRedraw:function(){var o=this;c.addClass(o.underlay,"yui-force-redraw");setTimeout(function(){c.removeClass(o.underlay,"yui-force-redraw")},0)},destroy:function(){this._removeEventListeners();j.superclass.destroy.call(this)},toString:function(){return"Tooltip "+this.id}})}());(function(){YAHOO.widget.Panel=function(v,u){YAHOO.widget.Panel.superclass.constructor.call(this,v,u)};var s=null;var e=YAHOO.lang,f=YAHOO.util,a=f.Dom,t=f.Event,m=f.CustomEvent,k=YAHOO.util.KeyListener,i=f.Config,h=YAHOO.widget.Overlay,o=YAHOO.widget.Panel,l=YAHOO.env.ua,p=(l.ie&&(l.ie<=6||document.compatMode=="BackCompat")),g,q,c,d={BEFORE_SHOW_MASK:"beforeShowMask",BEFORE_HIDE_MASK:"beforeHideMask",SHOW_MASK:"showMask",HIDE_MASK:"hideMask",DRAG:"drag"},n={CLOSE:{key:"close",value:true,validator:e.isBoolean,supercedes:["visible"]},DRAGGABLE:{key:"draggable",value:(f.DD?true:false),validator:e.isBoolean,supercedes:["visible"]},DRAG_ONLY:{key:"dragonly",value:false,validator:e.isBoolean,supercedes:["draggable"]},UNDERLAY:{key:"underlay",value:"shadow",supercedes:["visible"]},MODAL:{key:"modal",value:false,validator:e.isBoolean,supercedes:["visible","zindex"]},KEY_LISTENERS:{key:"keylisteners",suppressEvent:true,supercedes:["visible"]},STRINGS:{key:"strings",supercedes:["close"],validator:e.isObject,value:{close:"Close"}}};o.CSS_PANEL="yui-panel";o.CSS_PANEL_CONTAINER="yui-panel-container";o.FOCUSABLE=["a","button","select","textarea","input","iframe"];function j(v,u){if(!this.header&&this.cfg.getProperty("draggable")){this.setHeader("&#160;")}}function r(v,u,w){var z=w[0],x=w[1],y=this.cfg,A=y.getProperty("width");if(A==x){y.setProperty("width",z)}this.unsubscribe("hide",r,w)}function b(v,u){var y,x,w;if(p){y=this.cfg;x=y.getProperty("width");if(!x||x=="auto"){w=(this.element.offsetWidth+"px");y.setProperty("width",w);this.subscribe("hide",r,[(x||""),w])}}}YAHOO.extend(o,h,{init:function(v,u){o.superclass.init.call(this,v);this.beforeInitEvent.fire(o);a.addClass(this.element,o.CSS_PANEL);this.buildWrapper();if(u){this.cfg.applyConfig(u,true)}this.subscribe("showMask",this._addFocusHandlers);this.subscribe("hideMask",this._removeFocusHandlers);this.subscribe("beforeRender",j);this.subscribe("render",function(){this.setFirstLastFocusable();this.subscribe("changeContent",this.setFirstLastFocusable)});this.subscribe("show",this._focusOnShow);this.initEvent.fire(o)},_onElementFocus:function(z){if(s===this){var y=t.getTarget(z),x=document.documentElement,v=(y!==x&&y!==window);if(v&&y!==this.element&&y!==this.mask&&!a.isAncestor(this.element,y)){try{this._focusFirstModal()}catch(w){try{if(v&&y!==document.body){y.blur()}}catch(u){}}}}},_focusFirstModal:function(){var u=this.firstElement;if(u){u.focus()}else{if(this._modalFocus){this._modalFocus.focus()}else{this.innerElement.focus()}}},_addFocusHandlers:function(v,u){if(!this.firstElement){if(l.webkit||l.opera){if(!this._modalFocus){this._createHiddenFocusElement()}}else{this.innerElement.tabIndex=0}}this._setTabLoop(this.firstElement,this.lastElement);t.onFocus(document.documentElement,this._onElementFocus,this,true);s=this},_createHiddenFocusElement:function(){var u=document.createElement("button");u.style.height="1px";u.style.width="1px";u.style.position="absolute";u.style.left="-10000em";u.style.opacity=0;u.tabIndex=-1;this.innerElement.appendChild(u);this._modalFocus=u},_removeFocusHandlers:function(v,u){t.removeFocusListener(document.documentElement,this._onElementFocus,this);if(s==this){s=null}},_focusOnShow:function(v,u,w){if(u&&u[1]){t.stopEvent(u[1])}if(!this.focusFirst(v,u,w)){if(this.cfg.getProperty("modal")){this._focusFirstModal()}}},focusFirst:function(w,u,z){var v=this.firstElement,y=false;if(u&&u[1]){t.stopEvent(u[1])}if(v){try{v.focus();y=true}catch(x){}}return y},focusLast:function(w,u,z){var v=this.lastElement,y=false;if(u&&u[1]){t.stopEvent(u[1])}if(v){try{v.focus();y=true}catch(x){}}return y},_setTabLoop:function(u,v){this.setTabLoop(u,v)},setTabLoop:function(x,z){var v=this.preventBackTab,w=this.preventTabOut,u=this.showEvent,y=this.hideEvent;if(v){v.disable();u.unsubscribe(v.enable,v);y.unsubscribe(v.disable,v);v=this.preventBackTab=null}if(w){w.disable();u.unsubscribe(w.enable,w);y.unsubscribe(w.disable,w);w=this.preventTabOut=null}if(x){this.preventBackTab=new k(x,{shift:true,keys:9},{fn:this.focusLast,scope:this,correctScope:true});v=this.preventBackTab;u.subscribe(v.enable,v,true);y.subscribe(v.disable,v,true)}if(z){this.preventTabOut=new k(z,{shift:false,keys:9},{fn:this.focusFirst,scope:this,correctScope:true});w=this.preventTabOut;u.subscribe(w.enable,w,true);y.subscribe(w.disable,w,true)}},getFocusableElements:function(v){v=v||this.innerElement;var x={},u=this;for(var w=0;w<o.FOCUSABLE.length;w++){x[o.FOCUSABLE[w]]=true}return a.getElementsBy(function(y){return u._testIfFocusable(y,x)},null,v)},_testIfFocusable:function(u,v){if(u.focus&&u.type!=="hidden"&&!u.disabled&&v[u.tagName.toLowerCase()]){return true}return false},setFirstLastFocusable:function(){this.firstElement=null;this.lastElement=null;var u=this.getFocusableElements();this.focusableElements=u;if(u.length>0){this.firstElement=u[0];this.lastElement=u[u.length-1]}if(this.cfg.getProperty("modal")){this._setTabLoop(this.firstElement,this.lastElement)}},initEvents:function(){o.superclass.initEvents.call(this);var u=m.LIST;this.showMaskEvent=this.createEvent(d.SHOW_MASK);this.showMaskEvent.signature=u;this.beforeShowMaskEvent=this.createEvent(d.BEFORE_SHOW_MASK);this.beforeShowMaskEvent.signature=u;this.hideMaskEvent=this.createEvent(d.HIDE_MASK);this.hideMaskEvent.signature=u;this.beforeHideMaskEvent=this.createEvent(d.BEFORE_HIDE_MASK);this.beforeHideMaskEvent.signature=u;this.dragEvent=this.createEvent(d.DRAG);this.dragEvent.signature=u},initDefaultConfig:function(){o.superclass.initDefaultConfig.call(this);this.cfg.addProperty(n.CLOSE.key,{handler:this.configClose,value:n.CLOSE.value,validator:n.CLOSE.validator,supercedes:n.CLOSE.supercedes});this.cfg.addProperty(n.DRAGGABLE.key,{handler:this.configDraggable,value:(f.DD)?true:false,validator:n.DRAGGABLE.validator,supercedes:n.DRAGGABLE.supercedes});this.cfg.addProperty(n.DRAG_ONLY.key,{value:n.DRAG_ONLY.value,validator:n.DRAG_ONLY.validator,supercedes:n.DRAG_ONLY.supercedes});this.cfg.addProperty(n.UNDERLAY.key,{handler:this.configUnderlay,value:n.UNDERLAY.value,supercedes:n.UNDERLAY.supercedes});this.cfg.addProperty(n.MODAL.key,{handler:this.configModal,value:n.MODAL.value,validator:n.MODAL.validator,supercedes:n.MODAL.supercedes});this.cfg.addProperty(n.KEY_LISTENERS.key,{handler:this.configKeyListeners,suppressEvent:n.KEY_LISTENERS.suppressEvent,supercedes:n.KEY_LISTENERS.supercedes});this.cfg.addProperty(n.STRINGS.key,{value:n.STRINGS.value,handler:this.configStrings,validator:n.STRINGS.validator,supercedes:n.STRINGS.supercedes})},configClose:function(y,v,z){var A=v[0],x=this.close,u=this.cfg.getProperty("strings"),w;if(A){if(!x){if(!c){c=document.createElement("a");c.className="container-close";c.href="#"}x=c.cloneNode(true);w=this.innerElement.firstChild;if(w){this.innerElement.insertBefore(x,w)}else{this.innerElement.appendChild(x)}x.innerHTML=(u&&u.close)?u.close:"&#160;";t.on(x,"click",this._doClose,this,true);this.close=x}else{x.style.display="block"}}else{if(x){x.style.display="none"}}},_doClose:function(u){t.preventDefault(u);this.hide()},configDraggable:function(v,u,w){var x=u[0];if(x){if(!f.DD){this.cfg.setProperty("draggable",false);return}if(this.header){a.setStyle(this.header,"cursor","move");this.registerDragDrop()}this.subscribe("beforeShow",b)}else{if(this.dd){this.dd.unreg()}if(this.header){a.setStyle(this.header,"cursor","auto")}this.unsubscribe("beforeShow",b)}},configUnderlay:function(D,C,z){var B=(this.platform=="mac"&&l.gecko),E=C[0].toLowerCase(),v=this.underlay,w=this.element;function x(){var F=false;if(!v){if(!q){q=document.createElement("div");q.className="underlay"}v=q.cloneNode(false);this.element.appendChild(v);this.underlay=v;if(p){this.sizeUnderlay();this.cfg.subscribeToConfigEvent("width",this.sizeUnderlay);this.cfg.subscribeToConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.subscribe(this.sizeUnderlay);YAHOO.widget.Module.textResizeEvent.subscribe(this.sizeUnderlay,this,true)}if(l.webkit&&l.webkit<420){this.changeContentEvent.subscribe(this.forceUnderlayRedraw)}F=true}}function A(){var F=x.call(this);if(!F&&p){this.sizeUnderlay()}this._underlayDeferred=false;this.beforeShowEvent.unsubscribe(A)}function y(){if(this._underlayDeferred){this.beforeShowEvent.unsubscribe(A);this._underlayDeferred=false}if(v){this.cfg.unsubscribeFromConfigEvent("width",this.sizeUnderlay);this.cfg.unsubscribeFromConfigEvent("height",this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.sizeUnderlay);this.changeContentEvent.unsubscribe(this.forceUnderlayRedraw);YAHOO.widget.Module.textResizeEvent.unsubscribe(this.sizeUnderlay,this,true);this.element.removeChild(v);this.underlay=null}}switch(E){case"shadow":a.removeClass(w,"matte");a.addClass(w,"shadow");break;case"matte":if(!B){y.call(this)}a.removeClass(w,"shadow");a.addClass(w,"matte");break;default:if(!B){y.call(this)}a.removeClass(w,"shadow");a.removeClass(w,"matte");break}if((E=="shadow")||(B&&!v)){if(this.cfg.getProperty("visible")){var u=x.call(this);if(!u&&p){this.sizeUnderlay()}}else{if(!this._underlayDeferred){this.beforeShowEvent.subscribe(A);this._underlayDeferred=true}}}},configModal:function(v,u,x){var w=u[0];if(w){if(!this._hasModalityEventListeners){this.subscribe("beforeShow",this.buildMask);this.subscribe("beforeShow",this.bringToTop);this.subscribe("beforeShow",this.showMask);this.subscribe("hide",this.hideMask);h.windowResizeEvent.subscribe(this.sizeMask,this,true);this._hasModalityEventListeners=true}}else{if(this._hasModalityEventListeners){if(this.cfg.getProperty("visible")){this.hideMask();this.removeMask()}this.unsubscribe("beforeShow",this.buildMask);this.unsubscribe("beforeShow",this.bringToTop);this.unsubscribe("beforeShow",this.showMask);this.unsubscribe("hide",this.hideMask);h.windowResizeEvent.unsubscribe(this.sizeMask,this);this._hasModalityEventListeners=false}}},removeMask:function(){var v=this.mask,u;if(v){this.hideMask();u=v.parentNode;if(u){u.removeChild(v)}this.mask=null}},configKeyListeners:function(x,u,A){var w=u[0],z,y,v;if(w){if(w instanceof Array){y=w.length;for(v=0;v<y;v++){z=w[v];if(!i.alreadySubscribed(this.showEvent,z.enable,z)){this.showEvent.subscribe(z.enable,z,true)}if(!i.alreadySubscribed(this.hideEvent,z.disable,z)){this.hideEvent.subscribe(z.disable,z,true);this.destroyEvent.subscribe(z.disable,z,true)}}}else{if(!i.alreadySubscribed(this.showEvent,w.enable,w)){this.showEvent.subscribe(w.enable,w,true)}if(!i.alreadySubscribed(this.hideEvent,w.disable,w)){this.hideEvent.subscribe(w.disable,w,true);this.destroyEvent.subscribe(w.disable,w,true)}}}},configStrings:function(v,u,w){var x=e.merge(n.STRINGS.value,u[0]);this.cfg.setProperty(n.STRINGS.key,x,true)},configHeight:function(x,v,y){var u=v[0],w=this.innerElement;a.setStyle(w,"height",u);this.cfg.refireEvent("iframe")},_autoFillOnHeightChange:function(x,v,w){o.superclass._autoFillOnHeightChange.apply(this,arguments);if(p){var u=this;setTimeout(function(){u.sizeUnderlay()},0)}},configWidth:function(x,u,y){var w=u[0],v=this.innerElement;a.setStyle(v,"width",w);this.cfg.refireEvent("iframe")},configzIndex:function(v,u,x){o.superclass.configzIndex.call(this,v,u,x);if(this.mask||this.cfg.getProperty("modal")===true){var w=a.getStyle(this.element,"zIndex");if(!w||isNaN(w)){w=0}if(w===0){this.cfg.setProperty("zIndex",1)}else{this.stackMask()}}},buildWrapper:function(){var w=this.element.parentNode,u=this.element,v=document.createElement("div");v.className=o.CSS_PANEL_CONTAINER;v.id=u.id+"_c";if(w){w.insertBefore(v,u)}v.appendChild(u);this.element=v;this.innerElement=u;a.setStyle(this.innerElement,"visibility","inherit")},sizeUnderlay:function(){var v=this.underlay,u;if(v){u=this.element;v.style.width=u.offsetWidth+"px";v.style.height=u.offsetHeight+"px"}},registerDragDrop:function(){var v=this;if(this.header){if(!f.DD){return}var u=(this.cfg.getProperty("dragonly")===true);this.dd=new f.DD(this.element.id,this.id,{dragOnly:u});if(!this.header.id){this.header.id=this.id+"_h"}this.dd.startDrag=function(){var x,z,w,C,B,A;if(YAHOO.env.ua.ie==6){a.addClass(v.element,"drag")}if(v.cfg.getProperty("constraintoviewport")){var y=h.VIEWPORT_OFFSET;x=v.element.offsetHeight;z=v.element.offsetWidth;w=a.getViewportWidth();C=a.getViewportHeight();B=a.getDocumentScrollLeft();A=a.getDocumentScrollTop();if(x+y<C){this.minY=A+y;this.maxY=A+C-x-y}else{this.minY=A+y;this.maxY=A+y}if(z+y<w){this.minX=B+y;this.maxX=B+w-z-y}else{this.minX=B+y;this.maxX=B+y}this.constrainX=true;this.constrainY=true}else{this.constrainX=false;this.constrainY=false}v.dragEvent.fire("startDrag",arguments)};this.dd.onDrag=function(){v.syncPosition();v.cfg.refireEvent("iframe");if(this.platform=="mac"&&YAHOO.env.ua.gecko){this.showMacGeckoScrollbars()}v.dragEvent.fire("onDrag",arguments)};this.dd.endDrag=function(){if(YAHOO.env.ua.ie==6){a.removeClass(v.element,"drag")}v.dragEvent.fire("endDrag",arguments);v.moveEvent.fire(v.cfg.getProperty("xy"))};this.dd.setHandleElId(this.header.id);this.dd.addInvalidHandleType("INPUT");this.dd.addInvalidHandleType("SELECT");this.dd.addInvalidHandleType("TEXTAREA")}},buildMask:function(){var u=this.mask;if(!u){if(!g){g=document.createElement("div");g.className="mask";g.innerHTML="&#160;"}u=g.cloneNode(true);u.id=this.id+"_mask";document.body.insertBefore(u,document.body.firstChild);this.mask=u;if(YAHOO.env.ua.gecko&&this.platform=="mac"){a.addClass(this.mask,"block-scrollbars")}this.stackMask()}},hideMask:function(){if(this.cfg.getProperty("modal")&&this.mask&&this.beforeHideMaskEvent.fire()){this.mask.style.display="none";a.removeClass(document.body,"masked");this.hideMaskEvent.fire()}},showMask:function(){if(this.cfg.getProperty("modal")&&this.mask&&this.beforeShowMaskEvent.fire()){a.addClass(document.body,"masked");this.sizeMask();this.mask.style.display="block";this.showMaskEvent.fire()}},sizeMask:function(){if(this.mask){var v=this.mask,w=a.getViewportWidth(),u=a.getViewportHeight();if(v.offsetHeight>u){v.style.height=u+"px"}if(v.offsetWidth>w){v.style.width=w+"px"}v.style.height=a.getDocumentHeight()+"px";v.style.width=a.getDocumentWidth()+"px"}},stackMask:function(){if(this.mask){var u=a.getStyle(this.element,"zIndex");if(!YAHOO.lang.isUndefined(u)&&!isNaN(u)){a.setStyle(this.mask,"zIndex",u-1)}}},render:function(u){return o.superclass.render.call(this,u,this.innerElement)},_renderHeader:function(u){u=u||this.innerElement;o.superclass._renderHeader.call(this,u)},_renderBody:function(u){u=u||this.innerElement;o.superclass._renderBody.call(this,u)},_renderFooter:function(u){u=u||this.innerElement;o.superclass._renderFooter.call(this,u)},destroy:function(u){h.windowResizeEvent.unsubscribe(this.sizeMask,this);this.removeMask();if(this.close){t.purgeElement(this.close)}o.superclass.destroy.call(this,u)},forceUnderlayRedraw:function(){var v=this.underlay;a.addClass(v,"yui-force-redraw");setTimeout(function(){a.removeClass(v,"yui-force-redraw")},0)},toString:function(){return"Panel "+this.id}})}());(function(){YAHOO.widget.Dialog=function(j,i){YAHOO.widget.Dialog.superclass.constructor.call(this,j,i)};var b=YAHOO.util.Event,g=YAHOO.util.CustomEvent,e=YAHOO.util.Dom,a=YAHOO.widget.Dialog,f=YAHOO.lang,h={BEFORE_SUBMIT:"beforeSubmit",SUBMIT:"submit",MANUAL_SUBMIT:"manualSubmit",ASYNC_SUBMIT:"asyncSubmit",FORM_SUBMIT:"formSubmit",CANCEL:"cancel"},c={POST_METHOD:{key:"postmethod",value:"async"},POST_DATA:{key:"postdata",value:null},BUTTONS:{key:"buttons",value:"none",supercedes:["visible"]},HIDEAFTERSUBMIT:{key:"hideaftersubmit",value:true}};a.CSS_DIALOG="yui-dialog";function d(){var m=this._aButtons,k,l,j;if(f.isArray(m)){k=m.length;if(k>0){j=k-1;do{l=m[j];if(YAHOO.widget.Button&&l instanceof YAHOO.widget.Button){l.destroy()}else{if(l.tagName.toUpperCase()=="BUTTON"){b.purgeElement(l);b.purgeElement(l,false)}}}while(j--)}}}YAHOO.extend(a,YAHOO.widget.Panel,{form:null,initDefaultConfig:function(){a.superclass.initDefaultConfig.call(this);this.callback={success:null,failure:null,argument:null};this.cfg.addProperty(c.POST_METHOD.key,{handler:this.configPostMethod,value:c.POST_METHOD.value,validator:function(i){if(i!="form"&&i!="async"&&i!="none"&&i!="manual"){return false}else{return true}}});this.cfg.addProperty(c.POST_DATA.key,{value:c.POST_DATA.value});this.cfg.addProperty(c.HIDEAFTERSUBMIT.key,{value:c.HIDEAFTERSUBMIT.value});this.cfg.addProperty(c.BUTTONS.key,{handler:this.configButtons,value:c.BUTTONS.value,supercedes:c.BUTTONS.supercedes})},initEvents:function(){a.superclass.initEvents.call(this);var i=g.LIST;this.beforeSubmitEvent=this.createEvent(h.BEFORE_SUBMIT);this.beforeSubmitEvent.signature=i;this.submitEvent=this.createEvent(h.SUBMIT);this.submitEvent.signature=i;this.manualSubmitEvent=this.createEvent(h.MANUAL_SUBMIT);this.manualSubmitEvent.signature=i;this.asyncSubmitEvent=this.createEvent(h.ASYNC_SUBMIT);this.asyncSubmitEvent.signature=i;this.formSubmitEvent=this.createEvent(h.FORM_SUBMIT);this.formSubmitEvent.signature=i;this.cancelEvent=this.createEvent(h.CANCEL);this.cancelEvent.signature=i},init:function(j,i){a.superclass.init.call(this,j);this.beforeInitEvent.fire(a);e.addClass(this.element,a.CSS_DIALOG);this.cfg.setProperty("visible",false);if(i){this.cfg.applyConfig(i,true)}this.beforeHideEvent.subscribe(this.blurButtons,this,true);this.subscribe("changeBody",this.registerForm);this.initEvent.fire(a)},doSubmit:function(){var q=YAHOO.util.Connect,r=this.form,l=false,o=false,s,n,m,j;switch(this.cfg.getProperty("postmethod")){case"async":s=r.elements;n=s.length;if(n>0){m=n-1;do{if(s[m].type=="file"){l=true;break}}while(m--)}if(l&&YAHOO.env.ua.ie&&this.isSecure){o=true}j=this._getFormAttributes(r);q.setForm(r,l,o);var k=this.cfg.getProperty("postdata");var p=q.asyncRequest(j.method,j.action,this.callback,k);this.asyncSubmitEvent.fire(p);break;case"form":r.submit();this.formSubmitEvent.fire();break;case"none":case"manual":this.manualSubmitEvent.fire();break}},_getFormAttributes:function(k){var i={method:null,action:null};if(k){if(k.getAttributeNode){var j=k.getAttributeNode("action");var l=k.getAttributeNode("method");if(j){i.action=j.value}if(l){i.method=l.value}}else{i.action=k.getAttribute("action");i.method=k.getAttribute("method")}}i.method=(f.isString(i.method)?i.method:"POST").toUpperCase();i.action=f.isString(i.action)?i.action:"";return i},registerForm:function(){var i=this.element.getElementsByTagName("form")[0];if(this.form){if(this.form==i&&e.isAncestor(this.element,this.form)){return}else{b.purgeElement(this.form);this.form=null}}if(!i){i=document.createElement("form");i.name="frm_"+this.id;this.body.appendChild(i)}if(i){this.form=i;b.on(i,"submit",this._submitHandler,this,true)}},_submitHandler:function(i){b.stopEvent(i);this.submit();this.form.blur()},setTabLoop:function(i,j){i=i||this.firstButton;j=j||this.lastButton;a.superclass.setTabLoop.call(this,i,j)},_setTabLoop:function(i,j){i=i||this.firstButton;j=this.lastButton||j;this.setTabLoop(i,j)},setFirstLastFocusable:function(){a.superclass.setFirstLastFocusable.call(this);var k,j,m,n=this.focusableElements;this.firstFormElement=null;this.lastFormElement=null;if(this.form&&n&&n.length>0){j=n.length;for(k=0;k<j;++k){m=n[k];if(this.form===m.form){this.firstFormElement=m;break}}for(k=j-1;k>=0;--k){m=n[k];if(this.form===m.form){this.lastFormElement=m;break}}}},configClose:function(j,i,k){a.superclass.configClose.apply(this,arguments)},_doClose:function(i){b.preventDefault(i);this.cancel()},configButtons:function(t,s,n){var o=YAHOO.widget.Button,v=s[0],l=this.innerElement,u,q,k,r,p,j,m;d.call(this);this._aButtons=null;if(f.isArray(v)){p=document.createElement("span");p.className="button-group";r=v.length;this._aButtons=[];this.defaultHtmlButton=null;for(m=0;m<r;m++){u=v[m];if(o){k=new o({label:u.text,type:u.type});k.appendTo(p);q=k.get("element");if(u.isDefault){k.addClass("default");this.defaultHtmlButton=q}if(f.isFunction(u.handler)){k.set("onclick",{fn:u.handler,obj:this,scope:this})}else{if(f.isObject(u.handler)&&f.isFunction(u.handler.fn)){k.set("onclick",{fn:u.handler.fn,obj:((!f.isUndefined(u.handler.obj))?u.handler.obj:this),scope:(u.handler.scope||this)})}}this._aButtons[this._aButtons.length]=k}else{q=document.createElement("button");q.setAttribute("type","button");if(u.isDefault){q.className="default";this.defaultHtmlButton=q}q.innerHTML=u.text;if(f.isFunction(u.handler)){b.on(q,"click",u.handler,this,true)}else{if(f.isObject(u.handler)&&f.isFunction(u.handler.fn)){b.on(q,"click",u.handler.fn,((!f.isUndefined(u.handler.obj))?u.handler.obj:this),(u.handler.scope||this))}}p.appendChild(q);this._aButtons[this._aButtons.length]=q}u.htmlButton=q;if(m===0){this.firstButton=q}if(m==(r-1)){this.lastButton=q}}this.setFooter(p);j=this.footer;if(e.inDocument(this.element)&&!e.isAncestor(l,j)){l.appendChild(j)}this.buttonSpan=p}else{p=this.buttonSpan;j=this.footer;if(p&&j){j.removeChild(p);this.buttonSpan=null;this.firstButton=null;this.lastButton=null;this.defaultHtmlButton=null}}this.changeContentEvent.fire()},getButtons:function(){return this._aButtons||null},focusFirst:function(k,i,n){var j=this.firstFormElement,m=false;if(i&&i[1]){b.stopEvent(i[1]);if(i[0]===9&&this.firstElement){j=this.firstElement}}if(j){try{j.focus();m=true}catch(l){}}else{if(this.defaultHtmlButton){m=this.focusDefaultButton()}else{m=this.focusFirstButton()}}return m},focusLast:function(k,i,n){var o=this.cfg.getProperty("buttons"),j=this.lastFormElement,m=false;if(i&&i[1]){b.stopEvent(i[1]);if(i[0]===9&&this.lastElement){j=this.lastElement}}if(o&&f.isArray(o)){m=this.focusLastButton()}else{if(j){try{j.focus();m=true}catch(l){}}}return m},_getButton:function(j){var i=YAHOO.widget.Button;if(i&&j&&j.nodeName&&j.id){j=i.getButton(j.id)||j}return j},focusDefaultButton:function(){var i=this._getButton(this.defaultHtmlButton),k=false;if(i){try{i.focus();k=true}catch(j){}}return k},blurButtons:function(){var o=this.cfg.getProperty("buttons"),l,n,k,j;if(o&&f.isArray(o)){l=o.length;if(l>0){j=(l-1);do{n=o[j];if(n){k=this._getButton(n.htmlButton);if(k){try{k.blur()}catch(m){}}}}while(j--)}}},focusFirstButton:function(){var m=this.cfg.getProperty("buttons"),k,i,l=false;if(m&&f.isArray(m)){k=m[0];if(k){i=this._getButton(k.htmlButton);if(i){try{i.focus();l=true}catch(j){}}}}return l},focusLastButton:function(){var n=this.cfg.getProperty("buttons"),j,l,i,m=false;if(n&&f.isArray(n)){j=n.length;if(j>0){l=n[(j-1)];if(l){i=this._getButton(l.htmlButton);if(i){try{i.focus();m=true}catch(k){}}}}}return m},configPostMethod:function(j,i,k){this.registerForm()},validate:function(){return true},submit:function(){if(this.validate()){if(this.beforeSubmitEvent.fire()){this.doSubmit();this.submitEvent.fire();if(this.cfg.getProperty("hideaftersubmit")){this.hide()}return true}else{return false}}else{return false}},cancel:function(){this.cancelEvent.fire();this.hide()},getData:function(){var A=this.form,k,t,w,m,u,r,q,j,x,l,y,B,p,C,o,z,v;function s(n){var i=n.tagName.toUpperCase();return((i=="INPUT"||i=="TEXTAREA"||i=="SELECT")&&n.name==m)}if(A){k=A.elements;t=k.length;w={};for(z=0;z<t;z++){m=k[z].name;u=e.getElementsBy(s,"*",A);r=u.length;if(r>0){if(r==1){u=u[0];q=u.type;j=u.tagName.toUpperCase();switch(j){case"INPUT":if(q=="checkbox"){w[m]=u.checked}else{if(q!="radio"){w[m]=u.value}}break;case"TEXTAREA":w[m]=u.value;break;case"SELECT":x=u.options;l=x.length;y=[];for(v=0;v<l;v++){B=x[v];if(B.selected){o=B.attributes.value;y[y.length]=(o&&o.specified)?B.value:B.text}}w[m]=y;break}}else{q=u[0].type;switch(q){case"radio":for(v=0;v<r;v++){p=u[v];if(p.checked){w[m]=p.value;break}}break;case"checkbox":y=[];for(v=0;v<r;v++){C=u[v];if(C.checked){y[y.length]=C.value}}w[m]=y;break}}}}}return w},destroy:function(i){d.call(this);this._aButtons=null;var j=this.element.getElementsByTagName("form"),k;if(j.length>0){k=j[0];if(k){b.purgeElement(k);if(k.parentNode){k.parentNode.removeChild(k)}this.form=null}}a.superclass.destroy.call(this,i)},toString:function(){return"Dialog "+this.id}})}());(function(){YAHOO.widget.SimpleDialog=function(e,d){YAHOO.widget.SimpleDialog.superclass.constructor.call(this,e,d)};var c=YAHOO.util.Dom,b=YAHOO.widget.SimpleDialog,a={ICON:{key:"icon",value:"none",suppressEvent:true},TEXT:{key:"text",value:"",suppressEvent:true,supercedes:["icon"]}};b.ICON_BLOCK="blckicon";b.ICON_ALARM="alrticon";b.ICON_HELP="hlpicon";b.ICON_INFO="infoicon";b.ICON_WARN="warnicon";b.ICON_TIP="tipicon";b.ICON_CSS_CLASSNAME="yui-icon";b.CSS_SIMPLEDIALOG="yui-simple-dialog";YAHOO.extend(b,YAHOO.widget.Dialog,{initDefaultConfig:function(){b.superclass.initDefaultConfig.call(this);this.cfg.addProperty(a.ICON.key,{handler:this.configIcon,value:a.ICON.value,suppressEvent:a.ICON.suppressEvent});this.cfg.addProperty(a.TEXT.key,{handler:this.configText,value:a.TEXT.value,suppressEvent:a.TEXT.suppressEvent,supercedes:a.TEXT.supercedes})},init:function(e,d){b.superclass.init.call(this,e);this.beforeInitEvent.fire(b);c.addClass(this.element,b.CSS_SIMPLEDIALOG);this.cfg.queueProperty("postmethod","manual");if(d){this.cfg.applyConfig(d,true)}this.beforeRenderEvent.subscribe(function(){if(!this.body){this.setBody("")}},this,true);this.initEvent.fire(b)},registerForm:function(){b.superclass.registerForm.call(this);var e=this.form.ownerDocument,d=e.createElement("input");d.type="hidden";d.name=this.id;d.value="";this.form.appendChild(d)},configIcon:function(k,j,h){var d=j[0],e=this.body,f=b.ICON_CSS_CLASSNAME,l,i,g;if(d&&d!="none"){l=c.getElementsByClassName(f,"*",e);if(l.length===1){i=l[0];g=i.parentNode;if(g){g.removeChild(i);i=null}}if(d.indexOf(".")==-1){i=document.createElement("span");i.className=(f+" "+d);i.innerHTML="&#160;"}else{i=document.createElement("img");i.src=(this.imageRoot+d);i.className=f}if(i){e.insertBefore(i,e.firstChild)}}},configText:function(e,d,f){var g=d[0];if(g){this.setBody(g);this.cfg.refireEvent("icon")}},toString:function(){return"SimpleDialog "+this.id}})}());(function(){YAHOO.widget.ContainerEffect=function(e,h,g,d,f){if(!f){f=YAHOO.util.Anim}this.overlay=e;this.attrIn=h;this.attrOut=g;this.targetElement=d||e.element;this.animClass=f};var b=YAHOO.util.Dom,c=YAHOO.util.CustomEvent,a=YAHOO.widget.ContainerEffect;a.FADE=function(d,f){var g=YAHOO.util.Easing,i={attributes:{opacity:{from:0,to:1}},duration:f,method:g.easeIn},e={attributes:{opacity:{to:0}},duration:f,method:g.easeOut},h=new a(d,i,e,d.element);h.handleUnderlayStart=function(){var k=this.overlay.underlay;if(k&&YAHOO.env.ua.ie){var j=(k.filters&&k.filters.length>0);if(j){b.addClass(d.element,"yui-effect-fade")}}};h.handleUnderlayComplete=function(){var j=this.overlay.underlay;if(j&&YAHOO.env.ua.ie){b.removeClass(d.element,"yui-effect-fade")}};h.handleStartAnimateIn=function(k,j,l){l.overlay._fadingIn=true;b.addClass(l.overlay.element,"hide-select");if(!l.overlay.underlay){l.overlay.cfg.refireEvent("underlay")}l.handleUnderlayStart();l.overlay._setDomVisibility(true);b.setStyle(l.overlay.element,"opacity",0)};h.handleCompleteAnimateIn=function(k,j,l){l.overlay._fadingIn=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null}l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateInCompleteEvent.fire()};h.handleStartAnimateOut=function(k,j,l){l.overlay._fadingOut=true;b.addClass(l.overlay.element,"hide-select");l.handleUnderlayStart()};h.handleCompleteAnimateOut=function(k,j,l){l.overlay._fadingOut=false;b.removeClass(l.overlay.element,"hide-select");if(l.overlay.element.style.filter){l.overlay.element.style.filter=null}l.overlay._setDomVisibility(false);b.setStyle(l.overlay.element,"opacity",1);l.handleUnderlayComplete();l.overlay.cfg.refireEvent("iframe");l.animateOutCompleteEvent.fire()};h.init();return h};a.SLIDE=function(f,d){var i=YAHOO.util.Easing,l=f.cfg.getProperty("x")||b.getX(f.element),k=f.cfg.getProperty("y")||b.getY(f.element),m=b.getClientWidth(),h=f.element.offsetWidth,j={attributes:{points:{to:[l,k]}},duration:d,method:i.easeIn},e={attributes:{points:{to:[(m+25),k]}},duration:d,method:i.easeOut},g=new a(f,j,e,f.element,YAHOO.util.Motion);g.handleStartAnimateIn=function(o,n,p){p.overlay.element.style.left=((-25)-h)+"px";p.overlay.element.style.top=k+"px"};g.handleTweenAnimateIn=function(q,p,r){var s=b.getXY(r.overlay.element),o=s[0],n=s[1];if(b.getStyle(r.overlay.element,"visibility")=="hidden"&&o<l){r.overlay._setDomVisibility(true)}r.overlay.cfg.setProperty("xy",[o,n],true);r.overlay.cfg.refireEvent("iframe")};g.handleCompleteAnimateIn=function(o,n,p){p.overlay.cfg.setProperty("xy",[l,k],true);p.startX=l;p.startY=k;p.overlay.cfg.refireEvent("iframe");p.animateInCompleteEvent.fire()};g.handleStartAnimateOut=function(o,n,r){var p=b.getViewportWidth(),s=b.getXY(r.overlay.element),q=s[1];r.animOut.attributes.points.to=[(p+25),q]};g.handleTweenAnimateOut=function(p,o,q){var s=b.getXY(q.overlay.element),n=s[0],r=s[1];q.overlay.cfg.setProperty("xy",[n,r],true);q.overlay.cfg.refireEvent("iframe")};g.handleCompleteAnimateOut=function(o,n,p){p.overlay._setDomVisibility(false);p.overlay.cfg.setProperty("xy",[l,k]);p.animateOutCompleteEvent.fire()};g.init();return g};a.prototype={init:function(){this.beforeAnimateInEvent=this.createEvent("beforeAnimateIn");this.beforeAnimateInEvent.signature=c.LIST;this.beforeAnimateOutEvent=this.createEvent("beforeAnimateOut");this.beforeAnimateOutEvent.signature=c.LIST;this.animateInCompleteEvent=this.createEvent("animateInComplete");this.animateInCompleteEvent.signature=c.LIST;this.animateOutCompleteEvent=this.createEvent("animateOutComplete");this.animateOutCompleteEvent.signature=c.LIST;this.animIn=new this.animClass(this.targetElement,this.attrIn.attributes,this.attrIn.duration,this.attrIn.method);this.animIn.onStart.subscribe(this.handleStartAnimateIn,this);this.animIn.onTween.subscribe(this.handleTweenAnimateIn,this);this.animIn.onComplete.subscribe(this.handleCompleteAnimateIn,this);this.animOut=new this.animClass(this.targetElement,this.attrOut.attributes,this.attrOut.duration,this.attrOut.method);this.animOut.onStart.subscribe(this.handleStartAnimateOut,this);this.animOut.onTween.subscribe(this.handleTweenAnimateOut,this);this.animOut.onComplete.subscribe(this.handleCompleteAnimateOut,this)},animateIn:function(){this._stopAnims(this.lastFrameOnStop);this.beforeAnimateInEvent.fire();this.animIn.animate()},animateOut:function(){this._stopAnims(this.lastFrameOnStop);this.beforeAnimateOutEvent.fire();this.animOut.animate()},lastFrameOnStop:true,_stopAnims:function(d){if(this.animOut&&this.animOut.isAnimated()){this.animOut.stop(d)}if(this.animIn&&this.animIn.isAnimated()){this.animIn.stop(d)}},handleStartAnimateIn:function(e,d,f){},handleTweenAnimateIn:function(e,d,f){},handleCompleteAnimateIn:function(e,d,f){},handleStartAnimateOut:function(e,d,f){},handleTweenAnimateOut:function(e,d,f){},handleCompleteAnimateOut:function(e,d,f){},toString:function(){var d="ContainerEffect";if(this.overlay){d+=" ["+this.overlay.toString()+"]"}return d}};YAHOO.lang.augmentProto(a,YAHOO.util.EventProvider)})();YAHOO.register("container",YAHOO.widget.Module,{version:"2.9.0",build:"2800"});(function(){var a=YAHOO.util.Event,c=YAHOO.lang,b=[],d=function(h,e,f){var g;if(!h||h===f){g=false}else{g=YAHOO.util.Selector.test(h,e)?h:d(h.parentNode,e,f)}return g};c.augmentObject(a,{_createDelegate:function(f,e,g,h){return function(i){var j=this,n=a.getTarget(i),l=e,p=(j.nodeType===9),q,k,o,m;if(c.isFunction(e)){q=e(n)}else{if(c.isString(e)){if(!p){o=j.id;if(!o){o=a.generateId(j)}m=("#"+o+" ");l=(m+e).replace(/,/gi,(","+m))}if(YAHOO.util.Selector.test(n,l)){q=n}else{if(YAHOO.util.Selector.test(n,((l.replace(/,/gi," *,"))+" *"))){q=d(n,l,j)}}}}if(q){k=q;if(h){if(h===true){k=g}else{k=h}}return f.call(k,i,q,j,g)}}},delegate:function(f,j,l,g,h,i){var e=j,k,m;if(c.isString(g)&&!YAHOO.util.Selector){return false}if(j=="mouseenter"||j=="mouseleave"){if(!a._createMouseDelegate){return false}e=a._getType(j);k=a._createMouseDelegate(l,h,i);m=a._createDelegate(function(p,o,n){return k.call(o,p,n)},g,h,i)}else{m=a._createDelegate(l,g,h,i)}b.push([f,e,l,m]);return a.on(f,e,m)},removeDelegate:function(f,j,i){var k=j,h=false,g,e;if(j=="mouseenter"||j=="mouseleave"){k=a._getType(j)}g=a._getCacheIndex(b,f,k,i);if(g>=0){e=b[g]}if(f&&e){h=a.removeListener(e[0],e[1],e[3]);if(h){delete b[g][2];delete b[g][3];b.splice(g,1)}}return h}})}());YAHOO.register("event-delegate",YAHOO.util.Event,{version:"2.9.0",build:"2800"});(function(){var l=YAHOO.lang,isFunction=l.isFunction,isObject=l.isObject,isArray=l.isArray,_toStr=Object.prototype.toString,Native=(YAHOO.env.ua.caja?window:this).JSON,_UNICODE_EXCEPTIONS=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_ESCAPES=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,_VALUES=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,_BRACKETS=/(?:^|:|,)(?:\s*\[)+/g,_UNSAFE=/[^\],:{}\s]/,_SPECIAL_CHARS=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,_CHARS={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},UNDEFINED="undefined",OBJECT="object",NULL="null",STRING="string",NUMBER="number",BOOLEAN="boolean",DATE="date",_allowable={"undefined":UNDEFINED,string:STRING,"[object String]":STRING,number:NUMBER,"[object Number]":NUMBER,"boolean":BOOLEAN,"[object Boolean]":BOOLEAN,"[object Date]":DATE,"[object RegExp]":OBJECT},EMPTY="",OPEN_O="{",CLOSE_O="}",OPEN_A="[",CLOSE_A="]",COMMA=",",COMMA_CR=",\n",CR="\n",COLON=":",COLON_SP=": ",QUOTE='"';Native=_toStr.call(Native)==="[object JSON]"&&Native;function _char(c){if(!_CHARS[c]){_CHARS[c]="\\u"+("0000"+(+(c.charCodeAt(0))).toString(16)).slice(-4)}return _CHARS[c]}function _revive(data,reviver){var walk=function(o,key){var k,v,value=o[key];if(value&&typeof value==="object"){for(k in value){if(l.hasOwnProperty(value,k)){v=walk(value,k);if(v===undefined){delete value[k]}else{value[k]=v}}}}return reviver.call(o,key,value)};return typeof reviver==="function"?walk({"":data},""):data}function _prepare(s){return s.replace(_UNICODE_EXCEPTIONS,_char)}function _isSafe(str){return l.isString(str)&&!_UNSAFE.test(str.replace(_ESCAPES,"@").replace(_VALUES,"]").replace(_BRACKETS,""))}function _parse(s,reviver){s=_prepare(s);if(_isSafe(s)){return _revive(eval("("+s+")"),reviver)}throw new SyntaxError("JSON.parse")}function _type(o){var t=typeof o;return _allowable[t]||_allowable[_toStr.call(o)]||(t===OBJECT?(o?OBJECT:NULL):UNDEFINED)}function _string(s){return QUOTE+s.replace(_SPECIAL_CHARS,_char)+QUOTE}function _indent(s,space){return s.replace(/^/gm,space)}function _stringify(o,w,space){if(o===undefined){return undefined}var replacer=isFunction(w)?w:null,format=_toStr.call(space).match(/String|Number/)||[],_date=YAHOO.lang.JSON.dateToString,stack=[],tmp,i,len;if(replacer||!isArray(w)){w=undefined}if(w){tmp={};for(i=0,len=w.length;i<len;++i){tmp[w[i]]=true}w=tmp}space=format[0]==="Number"?new Array(Math.min(Math.max(0,space),10)+1).join(" "):(space||EMPTY).slice(0,10);function _serialize(h,key){var value=h[key],t=_type(value),a=[],colon=space?COLON_SP:COLON,arr,i,keys,k,v;if(isObject(value)&&isFunction(value.toJSON)){value=value.toJSON(key)}else{if(t===DATE){value=_date(value)}}if(isFunction(replacer)){value=replacer.call(h,key,value)}if(value!==h[key]){t=_type(value)}switch(t){case DATE:case OBJECT:break;case STRING:return _string(value);case NUMBER:return isFinite(value)?value+EMPTY:NULL;case BOOLEAN:return value+EMPTY;case NULL:return NULL;default:return undefined}for(i=stack.length-1;i>=0;--i){if(stack[i]===value){throw new Error("JSON.stringify. Cyclical reference")}}arr=isArray(value);stack.push(value);if(arr){for(i=value.length-1;i>=0;--i){a[i]=_serialize(value,i)||NULL}}else{keys=w||value;i=0;for(k in keys){if(l.hasOwnProperty(keys,k)){v=_serialize(value,k);if(v){a[i++]=_string(k)+colon+v}}}}stack.pop();if(space&&a.length){return arr?OPEN_A+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_A:OPEN_O+CR+_indent(a.join(COMMA_CR),space)+CR+CLOSE_O}else{return arr?OPEN_A+a.join(COMMA)+CLOSE_A:OPEN_O+a.join(COMMA)+CLOSE_O}}return _serialize({"":o},"")}YAHOO.lang.JSON={useNativeParse:!!Native,useNativeStringify:!!Native,isSafe:function(s){return _isSafe(_prepare(s))},parse:function(s,reviver){if(typeof s!=="string"){s+=""}return Native&&YAHOO.lang.JSON.useNativeParse?Native.parse(s,reviver):_parse(s,reviver)},stringify:function(o,w,space){return Native&&YAHOO.lang.JSON.useNativeStringify?Native.stringify(o,w,space):_stringify(o,w,space)},dateToString:function(d){function _zeroPad(v){return v<10?"0"+v:v}return d.getUTCFullYear()+"-"+_zeroPad(d.getUTCMonth()+1)+"-"+_zeroPad(d.getUTCDate())+"T"+_zeroPad(d.getUTCHours())+COLON+_zeroPad(d.getUTCMinutes())+COLON+_zeroPad(d.getUTCSeconds())+"Z"},stringToDate:function(str){var m=str.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?Z$/);if(m){var d=new Date();d.setUTCFullYear(m[1],m[2]-1,m[3]);d.setUTCHours(m[4],m[5],m[6],(m[7]||0));return d}return str}};YAHOO.lang.JSON.isValid=YAHOO.lang.JSON.isSafe})();YAHOO.register("json",YAHOO.lang.JSON,{version:"2.9.0",build:"2800"});YAHOO.util.Chain=function(){this.q=[].slice.call(arguments);this.createEvent("end")};YAHOO.util.Chain.prototype={id:0,run:function(){var g=this.q[0],d;if(!g){this.fireEvent("end");return this}else{if(this.id){return this}}d=g.method||g;if(typeof d==="function"){var f=g.scope||{},b=g.argument||[],a=g.timeout||0,e=this;if(!(b instanceof Array)){b=[b]}if(a<0){this.id=a;if(g.until){for(;!g.until();){d.apply(f,b)}}else{if(g.iterations){for(;g.iterations-->0;){d.apply(f,b)}}else{d.apply(f,b)}}this.q.shift();this.id=0;return this.run()}else{if(g.until){if(g.until()){this.q.shift();return this.run()}}else{if(!g.iterations||!--g.iterations){this.q.shift()}}this.id=setTimeout(function(){d.apply(f,b);if(e.id){e.id=0;e.run()}},a)}}return this},add:function(a){this.q.push(a);return this},pause:function(){if(this.id>0){clearTimeout(this.id)}this.id=0;return this},stop:function(){this.pause();this.q=[];return this}};YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);(function(){var a=YAHOO.util.Event,c=YAHOO.lang,b=[],d=function(h,e,f){var g;if(!h||h===f){g=false}else{g=YAHOO.util.Selector.test(h,e)?h:d(h.parentNode,e,f)}return g};c.augmentObject(a,{_createDelegate:function(f,e,g,h){return function(i){var j=this,n=a.getTarget(i),l=e,p=(j.nodeType===9),q,k,o,m;if(c.isFunction(e)){q=e(n)}else{if(c.isString(e)){if(!p){o=j.id;if(!o){o=a.generateId(j)}m=("#"+o+" ");l=(m+e).replace(/,/gi,(","+m))}if(YAHOO.util.Selector.test(n,l)){q=n}else{if(YAHOO.util.Selector.test(n,((l.replace(/,/gi," *,"))+" *"))){q=d(n,l,j)}}}}if(q){k=q;if(h){if(h===true){k=g}else{k=h}}return f.call(k,i,q,j,g)}}},delegate:function(f,j,l,g,h,i){var e=j,k,m;if(c.isString(g)&&!YAHOO.util.Selector){return false}if(j=="mouseenter"||j=="mouseleave"){if(!a._createMouseDelegate){return false}e=a._getType(j);k=a._createMouseDelegate(l,h,i);m=a._createDelegate(function(p,o,n){return k.call(o,p,n)},g,h,i)}else{m=a._createDelegate(l,g,h,i)}b.push([f,e,l,m]);return a.on(f,e,m)},removeDelegate:function(f,j,i){var k=j,h=false,g,e;if(j=="mouseenter"||j=="mouseleave"){k=a._getType(j)}g=a._getCacheIndex(b,f,k,i);if(g>=0){e=b[g]}if(f&&e){h=a.removeListener(e[0],e[1],e[3]);if(h){delete b[g][2];delete b[g][3];b.splice(g,1)}}return h}})}());(function(){var b=YAHOO.util.Event,g=YAHOO.lang,e=b.addListener,f=b.removeListener,c=b.getListeners,d=[],h={mouseenter:"mouseover",mouseleave:"mouseout"},a=function(n,m,l){var j=b._getCacheIndex(d,n,m,l),i,k;if(j>=0){i=d[j]}if(n&&i){k=f.call(b,i[0],m,i[3]);if(k){delete d[j][2];delete d[j][3];d.splice(j,1)}}return k};g.augmentObject(b._specialTypes,h);g.augmentObject(b,{_createMouseDelegate:function(i,j,k){return function(q,m){var p=this,l=b.getRelatedTarget(q),o,n;if(p!=l&&!YAHOO.util.Dom.isAncestor(p,l)){o=p;if(k){if(k===true){o=j}else{o=k}}n=[q,j];if(m){n.splice(1,0,p,m)}return i.apply(o,n)}}},addListener:function(m,l,k,n,o){var i,j;if(h[l]){i=b._createMouseDelegate(k,n,o);i.mouseDelegate=true;d.push([m,l,k,i]);j=e.call(b,m,l,i)}else{j=e.apply(b,arguments)}return j},removeListener:function(l,k,j){var i;if(h[k]){i=a.apply(b,arguments)}else{i=f.apply(b,arguments)}return i},getListeners:function(p,o){var n=[],r,m=(o==="mouseover"||o==="mouseout"),q,k,j;if(o&&(m||h[o])){r=c.call(b,p,this._getType(o));if(r){for(k=r.length-1;k>-1;k--){j=r[k];q=j.fn.mouseDelegate;if((h[o]&&q)||(m&&!q)){n.push(j)}}}}else{n=c.apply(b,arguments)}return(n&&n.length)?n:null}},true);b.on=b.addListener}());YAHOO.register("event-mouseenter",YAHOO.util.Event,{version:"2.9.0",build:"2800"});var Y=YAHOO,Y_DOM=YAHOO.util.Dom,EMPTY_ARRAY=[],Y_UA=Y.env.ua,Y_Lang=Y.lang,Y_DOC=document,Y_DOCUMENT_ELEMENT=Y_DOC.documentElement,Y_DOM_inDoc=Y_DOM.inDocument,Y_mix=Y_Lang.augmentObject,Y_guid=Y_DOM.generateId,Y_getDoc=function(a){var b=Y_DOC;if(a){b=(a.nodeType===9)?a:a.ownerDocument||a.document||Y_DOC}return b},Y_Array=function(g,d){var c,b,h=d||0;try{return Array.prototype.slice.call(g,h)}catch(f){b=[];c=g.length;for(;h<c;h++){b.push(g[h])}return b}},Y_DOM_allById=function(f,a){a=a||Y_DOC;var b=[],c=[],d,e;if(a.querySelectorAll){c=a.querySelectorAll('[id="'+f+'"]')}else{if(a.all){b=a.all(f);if(b){if(b.nodeName){if(b.id===f){c.push(b);b=EMPTY_ARRAY}else{b=[b]}}if(b.length){for(d=0;e=b[d++];){if(e.id===f||(e.attributes&&e.attributes.id&&e.attributes.id.value===f)){c.push(e)}}}}}else{c=[Y_getDoc(a).getElementById(f)]}}return c};var COMPARE_DOCUMENT_POSITION="compareDocumentPosition",OWNER_DOCUMENT="ownerDocument",Selector={_foundCache:[],useNative:true,_compare:("sourceIndex" in Y_DOCUMENT_ELEMENT)?function(f,e){var d=f.sourceIndex,c=e.sourceIndex;if(d===c){return 0}else{if(d>c){return 1}}return -1}:(Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION]?function(b,a){if(b[COMPARE_DOCUMENT_POSITION](a)&4){return -1}else{return 1}}:function(e,d){var c,a,b;if(e&&d){c=e[OWNER_DOCUMENT].createRange();c.setStart(e,0);a=d[OWNER_DOCUMENT].createRange();a.setStart(d,0);b=c.compareBoundaryPoints(1,a)}return b}),_sort:function(a){if(a){a=Y_Array(a,0,true);if(a.sort){a.sort(Selector._compare)}}return a},_deDupe:function(a){var b=[],c,d;for(c=0;(d=a[c++]);){if(!d._found){b[b.length]=d;d._found=true}}for(c=0;(d=b[c++]);){d._found=null;d.removeAttribute("_found")}return b},query:function(b,j,k,a){if(typeof j=="string"){j=Y_DOM.get(j);if(!j){return(k)?null:[]}}else{j=j||Y_DOC}var f=[],c=(Selector.useNative&&Y_DOC.querySelector&&!a),e=[[b,j]],g,l,d,h=(c)?Selector._nativeQuery:Selector._bruteQuery;if(b&&h){if(!a&&(!c||j.tagName)){e=Selector._splitQueries(b,j)}for(d=0;(g=e[d++]);){l=h(g[0],g[1],k);if(!k){l=Y_Array(l,0,true)}if(l){f=f.concat(l)}}if(e.length>1){f=Selector._sort(Selector._deDupe(f))}}Y.log("query: "+b+" returning: "+f.length,"info","Selector");return(k)?(f[0]||null):f},_splitQueries:function(c,f){var b=c.split(","),d=[],g="",e,a;if(f){if(f.tagName){f.id=f.id||Y_guid();g='[id="'+f.id+'"] '}for(e=0,a=b.length;e<a;++e){c=g+b[e];d.push([c,f])}}return d},_nativeQuery:function(a,b,c){if(Y_UA.webkit&&a.indexOf(":checked")>-1&&(Selector.pseudos&&Selector.pseudos.checked)){return Selector.query(a,b,c,true)}try{return b["querySelector"+(c?"":"All")](a)}catch(d){return Selector.query(a,b,c,true)}},filter:function(b,a){var c=[],d,e;if(b&&a){for(d=0;(e=b[d++]);){if(Selector.test(e,a)){c[c.length]=e}}}else{Y.log("invalid filter input (nodes: "+b+", selector: "+a+")","warn","Selector")}return c},test:function(c,d,k){var g=false,b=d.split(","),a=false,l,o,h,n,f,e,m;if(c&&c.tagName){if(!k&&!Y_DOM_inDoc(c)){l=c.parentNode;if(l){k=l}else{n=c[OWNER_DOCUMENT].createDocumentFragment();n.appendChild(c);k=n;a=true}}k=k||c[OWNER_DOCUMENT];if(!c.id){c.id=Y_guid()}for(f=0;(m=b[f++]);){m+='[id="'+c.id+'"]';h=Selector.query(m,k);for(e=0;o=h[e++];){if(o===c){g=true;break}}if(g){break}}if(a){n.removeChild(c)}}return g}};YAHOO.util.Selector=Selector;var PARENT_NODE="parentNode",TAG_NAME="tagName",ATTRIBUTES="attributes",COMBINATOR="combinator",PSEUDOS="pseudos",SelectorCSS2={_reRegExpTokens:/([\^\$\?\[\]\*\+\-\.\(\)\|\\])/,SORT_RESULTS:true,_children:function(e,a){var b=e.children,d,c=[],f,g;if(e.children&&a&&e.children.tags){c=e.children.tags(a)}else{if((!b&&e[TAG_NAME])||(b&&a)){f=b||e.childNodes;b=[];for(d=0;(g=f[d++]);){if(g.tagName){if(!a||a===g.tagName){b.push(g)}}}}}return b||[]},_re:{attr:/(\[[^\]]*\])/g,esc:/\\[:\[\]\(\)#\.\'\>+~"]/gi,pseudos:/(\([^\)]*\))/g},shorthand:{"\\#(-?[_a-z]+[-\\w\\uE000]*)":"[id=$1]","\\.(-?[_a-z]+[-\\w\\uE000]*)":"[className~=$1]"},operators:{"":function(b,a){return !!b.getAttribute(a)},"~=":"(?:^|\\s+){val}(?:\\s+|$)","|=":"^{val}(?:-|$)"},pseudos:{"first-child":function(a){return Selector._children(a[PARENT_NODE])[0]===a}},_bruteQuery:function(f,j,l){var g=[],a=[],i=Selector._tokenize(f),e=i[i.length-1],k=Y_getDoc(j),c,b,h,d;if(e){b=e.id;h=e.className;d=e.tagName||"*";if(j.getElementsByTagName){if(b&&(j.all||(j.nodeType===9||Y_DOM_inDoc(j)))){a=Y_DOM_allById(b,j)}else{if(h){a=j.getElementsByClassName(h)}else{a=j.getElementsByTagName(d)}}}else{c=j.firstChild;while(c){if(c.tagName){a.push(c)}c=c.nextSilbing||c.firstChild}}if(a.length){g=Selector._filterNodes(a,i,l)}}return g},_filterNodes:function(l,f,h){var r=0,q,s=f.length,k=s-1,e=[],o=l[0],v=o,t=Selector.getters,d,p,c,g,a,m,b,u;for(r=0;(v=o=l[r++]);){k=s-1;g=null;testLoop:while(v&&v.tagName){c=f[k];b=c.tests;q=b.length;if(q&&!a){while((u=b[--q])){d=u[1];if(t[u[0]]){m=t[u[0]](v,u[0])}else{m=v[u[0]];if(m===undefined&&v.getAttribute){m=v.getAttribute(u[0])}}if((d==="="&&m!==u[2])||(typeof d!=="string"&&d.test&&!d.test(m))||(!d.test&&typeof d==="function"&&!d(v,u[0],u[2]))){if((v=v[g])){while(v&&(!v.tagName||(c.tagName&&c.tagName!==v.tagName))){v=v[g]}}continue testLoop}}}k--;if(!a&&(p=c.combinator)){g=p.axis;v=v[g];while(v&&!v.tagName){v=v[g]}if(p.direct){g=null}}else{e.push(o);if(h){return e}break}}}o=v=null;return e},combinators:{" ":{axis:"parentNode"},">":{axis:"parentNode",direct:true},"+":{axis:"previousSibling",direct:true}},_parsers:[{name:ATTRIBUTES,re:/^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,fn:function(d,e){var c=d[2]||"",a=Selector.operators,b=(d[3])?d[3].replace(/\\/g,""):"",f;if((d[1]==="id"&&c==="=")||(d[1]==="className"&&Y_DOCUMENT_ELEMENT.getElementsByClassName&&(c==="~="||c==="="))){e.prefilter=d[1];d[3]=b;e[d[1]]=(d[1]==="id")?d[3]:b}if(c in a){f=a[c];if(typeof f==="string"){d[3]=b.replace(Selector._reRegExpTokens,"\\$1");f=new RegExp(f.replace("{val}",d[3]))}d[2]=f}if(!e.last||e.prefilter!==d[1]){return d.slice(1)}}},{name:TAG_NAME,re:/^((?:-?[_a-z]+[\w-]*)|\*)/i,fn:function(b,c){var a=b[1].toUpperCase();c.tagName=a;if(a!=="*"&&(!c.last||c.prefilter)){return[TAG_NAME,"=",a]}if(!c.prefilter){c.prefilter="tagName"}}},{name:COMBINATOR,re:/^\s*([>+~]|\s)\s*/,fn:function(a,b){}},{name:PSEUDOS,re:/^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,fn:function(a,b){var c=Selector[PSEUDOS][a[1]];if(c){if(a[2]){a[2]=a[2].replace(/\\/g,"")}return[a[2],c]}else{return false}}}],_getToken:function(a){return{tagName:null,id:null,className:null,attributes:{},combinator:null,tests:[]}},_tokenize:function(c){c=c||"";c=Selector._replaceShorthand(Y_Lang.trim(c));var b=Selector._getToken(),h=c,g=[],j=false,e,f,d,a;outer:do{j=false;for(d=0;(a=Selector._parsers[d++]);){if((e=a.re.exec(c))){if(a.name!==COMBINATOR){b.selector=c}c=c.replace(e[0],"");if(!c.length){b.last=true}if(Selector._attrFilters[e[1]]){e[1]=Selector._attrFilters[e[1]]}f=a.fn(e,b);if(f===false){j=false;break outer}else{if(f){b.tests.push(f)}}if(!c.length||a.name===COMBINATOR){g.push(b);b=Selector._getToken(b);if(a.name===COMBINATOR){b.combinator=Selector.combinators[e[1]]}}j=true}}}while(j&&c.length);if(!j||c.length){Y.log("query: "+h+" contains unsupported token in: "+c,"warn","Selector");g=[]}return g},_replaceShorthand:function(b){var d=Selector.shorthand,c=b.match(Selector._re.esc),e,h,g,f,a;if(c){b=b.replace(Selector._re.esc,"\uE000")}e=b.match(Selector._re.attr);h=b.match(Selector._re.pseudos);if(e){b=b.replace(Selector._re.attr,"\uE001")}if(h){b=b.replace(Selector._re.pseudos,"\uE002")}for(g in d){if(d.hasOwnProperty(g)){b=b.replace(new RegExp(g,"gi"),d[g])}}if(e){for(f=0,a=e.length;f<a;++f){b=b.replace(/\uE001/,e[f])}}if(h){for(f=0,a=h.length;f<a;++f){b=b.replace(/\uE002/,h[f])}}b=b.replace(/\[/g,"\uE003");b=b.replace(/\]/g,"\uE004");b=b.replace(/\(/g,"\uE005");b=b.replace(/\)/g,"\uE006");if(c){for(f=0,a=c.length;f<a;++f){b=b.replace("\uE000",c[f])}}return b},_attrFilters:{"class":"className","for":"htmlFor"},getters:{href:function(b,a){return Y_DOM.getAttribute(b,a)}}};Y_mix(Selector,SelectorCSS2,true);Selector.getters.src=Selector.getters.rel=Selector.getters.href;if(Selector.useNative&&Y_DOC.querySelector){Selector.shorthand["\\.([^\\s\\\\(\\[:]*)"]="[class~=$1]"}Selector._reNth=/^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;Selector._getNth=function(d,o,q,h){Selector._reNth.test(o);var m=parseInt(RegExp.$1,10),c=RegExp.$2,j=RegExp.$3,k=parseInt(RegExp.$4,10)||0,p=[],l=Selector._children(d.parentNode,q),f;if(j){m=2;f="+";c="n";k=(j==="odd")?1:0}else{if(isNaN(m)){m=(c)?1:0}}if(m===0){if(h){k=l.length-k+1}if(l[k-1]===d){return true}else{return false}}else{if(m<0){h=!!h;m=Math.abs(m)}}if(!h){for(var e=k-1,g=l.length;e<g;e+=m){if(e>=0&&l[e]===d){return true}}}else{for(var e=l.length-k,g=l.length;e>=0;e-=m){if(e<g&&l[e]===d){return true}}}return false};Y_mix(Selector.pseudos,{root:function(a){return a===a.ownerDocument.documentElement},"nth-child":function(a,b){return Selector._getNth(a,b)},"nth-last-child":function(a,b){return Selector._getNth(a,b,null,true)},"nth-of-type":function(a,b){return Selector._getNth(a,b,a.tagName)},"nth-last-of-type":function(a,b){return Selector._getNth(a,b,a.tagName,true)},"last-child":function(b){var a=Selector._children(b.parentNode);return a[a.length-1]===b},"first-of-type":function(a){return Selector._children(a.parentNode,a.tagName)[0]===a},"last-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a[a.length-1]===b},"only-child":function(b){var a=Selector._children(b.parentNode);return a.length===1&&a[0]===b},"only-of-type":function(b){var a=Selector._children(b.parentNode,b.tagName);return a.length===1&&a[0]===b},empty:function(a){return a.childNodes.length===0},not:function(a,b){return !Selector.test(a,b)},contains:function(a,b){var c=a.innerText||a.textContent||"";return c.indexOf(b)>-1},checked:function(a){return(a.checked===true||a.selected===true)},enabled:function(a){return(a.disabled!==undefined&&!a.disabled)},disabled:function(a){return(a.disabled)}});Y_mix(Selector.operators,{"^=":"^{val}","!=":function(b,a,c){return b[a]!==c},"$=":"{val}$","*=":"{val}"});Selector.combinators["~"]={axis:"previousSibling"};YAHOO.register("selector",YAHOO.util.Selector,{version:"2.9.0",build:"2800"});var Dom=YAHOO.util.Dom;YAHOO.widget.ColumnSet=function(a){this._sId=Dom.generateId(null,"yui-cs");a=YAHOO.widget.DataTable._cloneObject(a);this._init(a);YAHOO.widget.ColumnSet._nCount++};YAHOO.widget.ColumnSet._nCount=0;YAHOO.widget.ColumnSet.prototype={_sId:null,_aDefinitions:null,tree:null,flat:null,keys:null,headers:null,_init:function(j){var k=[];var a=[];var g=[];var e=[];var c=-1;var b=function(m,s){c++;if(!k[c]){k[c]=[]}for(var o=0;o<m.length;o++){var i=m[o];var q=new YAHOO.widget.Column(i);i.yuiColumnId=q._sId;a.push(q);if(s){q._oParent=s}if(YAHOO.lang.isArray(i.children)){q.children=i.children;var r=0;var p=function(v){var w=v.children;for(var u=0;u<w.length;u++){if(YAHOO.lang.isArray(w[u].children)){p(w[u])}else{r++}}};p(i);q._nColspan=r;var t=i.children;for(var n=0;n<t.length;n++){var l=t[n];if(q.className&&(l.className===undefined)){l.className=q.className}if(q.editor&&(l.editor===undefined)){l.editor=q.editor}if(q.editorOptions&&(l.editorOptions===undefined)){l.editorOptions=q.editorOptions}if(q.formatter&&(l.formatter===undefined)){l.formatter=q.formatter}if(q.resizeable&&(l.resizeable===undefined)){l.resizeable=q.resizeable}if(q.sortable&&(l.sortable===undefined)){l.sortable=q.sortable}if(q.hidden){l.hidden=true}if(q.width&&(l.width===undefined)){l.width=q.width}if(q.minWidth&&(l.minWidth===undefined)){l.minWidth=q.minWidth}if(q.maxAutoWidth&&(l.maxAutoWidth===undefined)){l.maxAutoWidth=q.maxAutoWidth}if(q.type&&(l.type===undefined)){l.type=q.type}if(q.type&&!q.formatter){q.formatter=q.type}if(q.text&&!YAHOO.lang.isValue(q.label)){q.label=q.text}if(q.parser){}if(q.sortOptions&&((q.sortOptions.ascFunction)||(q.sortOptions.descFunction))){}}if(!k[c+1]){k[c+1]=[]}b(t,q)}else{q._nKeyIndex=g.length;q._nColspan=1;g.push(q)}k[c].push(q)}c--};if(YAHOO.lang.isArray(j)){b(j);this._aDefinitions=j}else{return null}var f;var d=function(l){var n=1;var q;var o;var r=function(t,p){p=p||1;for(var u=0;u<t.length;u++){var m=t[u];if(YAHOO.lang.isArray(m.children)){p++;r(m.children,p);p--}else{if(p>n){n=p}}}};for(var i=0;i<l.length;i++){q=l[i];r(q);for(var s=0;s<q.length;s++){o=q[s];if(!YAHOO.lang.isArray(o.children)){o._nRowspan=n}else{o._nRowspan=1}}n=1}};d(k);for(f=0;f<k[0].length;f++){k[0][f]._nTreeIndex=f}var h=function(l,m){e[l].push(m.getSanitizedKey());if(m._oParent){h(l,m._oParent)}};for(f=0;f<g.length;f++){e[f]=[];h(f,g[f]);e[f]=e[f].reverse()}this.tree=k;this.flat=a;this.keys=g;this.headers=e},getId:function(){return this._sId},toString:function(){return"ColumnSet instance "+this._sId},getDefinitions:function(){var a=this._aDefinitions;var b=function(e,g){for(var d=0;d<e.length;d++){var f=e[d];var i=g.getColumnById(f.yuiColumnId);if(i){var h=i.getDefinition();for(var c in h){if(YAHOO.lang.hasOwnProperty(h,c)){f[c]=h[c]}}}if(YAHOO.lang.isArray(f.children)){b(f.children,g)}}};b(a,this);this._aDefinitions=a;return a},getColumnById:function(c){if(YAHOO.lang.isString(c)){var a=this.flat;for(var b=a.length-1;b>-1;b--){if(a[b]._sId===c){return a[b]}}}return null},getColumn:function(c){if(YAHOO.lang.isNumber(c)&&this.keys[c]){return this.keys[c]}else{if(YAHOO.lang.isString(c)){var a=this.flat;var d=[];for(var b=0;b<a.length;b++){if(a[b].key===c){d.push(a[b])}}if(d.length===1){return d[0]}else{if(d.length>1){return d}}}}return null},getDescendants:function(d){var b=this;var c=[];var a;var e=function(f){c.push(f);if(f.children){for(a=0;a<f.children.length;a++){e(b.getColumn(f.children[a].key))}}};e(d);return c}};YAHOO.widget.Column=function(b){this._sId=Dom.generateId(null,"yui-col");if(b&&YAHOO.lang.isObject(b)){for(var a in b){if(a){this[a]=b[a]}}}if(!YAHOO.lang.isValue(this.key)){this.key=Dom.generateId(null,"yui-dt-col")}if(!YAHOO.lang.isValue(this.field)){this.field=this.key}YAHOO.widget.Column._nCount++;if(this.width&&!YAHOO.lang.isNumber(this.width)){this.width=null}if(this.editor&&YAHOO.lang.isString(this.editor)){this.editor=new YAHOO.widget.CellEditor(this.editor,this.editorOptions)}};YAHOO.lang.augmentObject(YAHOO.widget.Column,{_nCount:0,formatCheckbox:function(b,a,c,d){YAHOO.widget.DataTable.formatCheckbox(b,a,c,d)},formatCurrency:function(b,a,c,d){YAHOO.widget.DataTable.formatCurrency(b,a,c,d)},formatDate:function(b,a,c,d){YAHOO.widget.DataTable.formatDate(b,a,c,d)},formatEmail:function(b,a,c,d){YAHOO.widget.DataTable.formatEmail(b,a,c,d)},formatLink:function(b,a,c,d){YAHOO.widget.DataTable.formatLink(b,a,c,d)},formatNumber:function(b,a,c,d){YAHOO.widget.DataTable.formatNumber(b,a,c,d)},formatSelect:function(b,a,c,d){YAHOO.widget.DataTable.formatDropdown(b,a,c,d)}});YAHOO.widget.Column.prototype={_sId:null,_nKeyIndex:null,_nTreeIndex:null,_nColspan:1,_nRowspan:1,_oParent:null,_elTh:null,_elThLiner:null,_elThLabel:null,_elResizer:null,_nWidth:null,_dd:null,_ddResizer:null,key:null,field:null,label:null,abbr:null,children:null,width:null,minWidth:null,maxAutoWidth:null,hidden:false,selected:false,className:null,formatter:null,currencyOptions:null,dateOptions:null,dropdownOptions:null,editor:null,resizeable:false,sortable:false,sortOptions:null,getId:function(){return this._sId},toString:function(){return"Column instance "+this._sId},getDefinition:function(){var a={};a.abbr=this.abbr;a.className=this.className;a.editor=this.editor;a.editorOptions=this.editorOptions;a.field=this.field;a.formatter=this.formatter;a.hidden=this.hidden;a.key=this.key;a.label=this.label;a.minWidth=this.minWidth;a.maxAutoWidth=this.maxAutoWidth;a.resizeable=this.resizeable;a.selected=this.selected;a.sortable=this.sortable;a.sortOptions=this.sortOptions;a.width=this.width;a._calculatedWidth=this._calculatedWidth;return a},getKey:function(){return this.key},getField:function(){return this.field},getSanitizedKey:function(){return this.getKey().replace(/[^\w\-]/g,"")},getKeyIndex:function(){return this._nKeyIndex},getTreeIndex:function(){return this._nTreeIndex},getParent:function(){return this._oParent},getColspan:function(){return this._nColspan},getColSpan:function(){return this.getColspan()},getRowspan:function(){return this._nRowspan},getThEl:function(){return this._elTh},getThLinerEl:function(){return this._elThLiner},getResizerEl:function(){return this._elResizer},getColEl:function(){return this.getThEl()},getIndex:function(){return this.getKeyIndex()},format:function(){}};YAHOO.util.Sort={compare:function(d,c,e){if((d===null)||(typeof d=="undefined")){if((c===null)||(typeof c=="undefined")){return 0}else{return 1}}else{if((c===null)||(typeof c=="undefined")){return -1}}if(d.constructor==String){d=d.toLowerCase()}if(c.constructor==String){c=c.toLowerCase()}if(d<c){return(e)?1:-1}else{if(d>c){return(e)?-1:1}else{return 0}}}};YAHOO.widget.ColumnDD=function(d,a,c,b){if(d&&a&&c&&b){this.datatable=d;this.table=d.getTableEl();this.column=a;this.headCell=c;this.pointer=b;this.newIndex=null;this.init(c);this.initFrame();this.invalidHandleTypes={};this.setPadding(10,0,(this.datatable.getTheadEl().offsetHeight+10),0);YAHOO.util.Event.on(window,"resize",function(){this.initConstraints()},this,true)}else{}};if(YAHOO.util.DDProxy){YAHOO.extend(YAHOO.widget.ColumnDD,YAHOO.util.DDProxy,{initConstraints:function(){var g=YAHOO.util.Dom.getRegion(this.table),d=this.getEl(),f=YAHOO.util.Dom.getXY(d),c=parseInt(YAHOO.util.Dom.getStyle(d,"width"),10),a=parseInt(YAHOO.util.Dom.getStyle(d,"height"),10),e=((f[0]-g.left)+15),b=((g.right-f[0]-c)+15);this.setXConstraint(e,b);this.setYConstraint(10,10)},_resizeProxy:function(){YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this,arguments);var a=this.getDragEl(),b=this.getEl();YAHOO.util.Dom.setStyle(this.pointer,"height",(this.table.parentNode.offsetHeight+10)+"px");YAHOO.util.Dom.setStyle(this.pointer,"display","block");var c=YAHOO.util.Dom.getXY(b);YAHOO.util.Dom.setXY(this.pointer,[c[0],(c[1]-5)]);YAHOO.util.Dom.setStyle(a,"height",this.datatable.getContainerEl().offsetHeight+"px");YAHOO.util.Dom.setStyle(a,"width",(parseInt(YAHOO.util.Dom.getStyle(a,"width"),10)+4)+"px");YAHOO.util.Dom.setXY(this.dragEl,c)},onMouseDown:function(){this.initConstraints();this.resetConstraints()},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)))}},onDragOver:function(h,a){var f=this.datatable.getColumn(a);if(f){var c=f.getTreeIndex();while((c===null)&&f.getParent()){f=f.getParent();c=f.getTreeIndex()}if(c!==null){var b=f.getThEl();var k=c;var d=YAHOO.util.Event.getPageX(h),i=YAHOO.util.Dom.getX(b),j=i+((YAHOO.util.Dom.get(b).offsetWidth)/2),e=this.column.getTreeIndex();if(d<j){YAHOO.util.Dom.setX(this.pointer,i)}else{var g=parseInt(b.offsetWidth,10);YAHOO.util.Dom.setX(this.pointer,(i+g));k++}if(c>e){k--}if(k<0){k=0}else{if(k>this.datatable.getColumnSet().tree[0].length){k=this.datatable.getColumnSet().tree[0].length}}this.newIndex=k}}},onDragDrop:function(){this.datatable.reorderColumn(this.column,this.newIndex)},endDrag:function(){this.newIndex=null;YAHOO.util.Dom.setStyle(this.pointer,"display","none")}})}YAHOO.util.ColumnResizer=function(e,c,d,a,b){if(e&&c&&d&&a){this.datatable=e;this.column=c;this.headCell=d;this.headCellLiner=c.getThLinerEl();this.resizerLiner=d.firstChild;this.init(a,a,{dragOnly:true,dragElId:b.id});this.initFrame();this.resetResizerEl();this.setPadding(0,1,0,0)}else{}};if(YAHOO.util.DD){YAHOO.extend(YAHOO.util.ColumnResizer,YAHOO.util.DDProxy,{resetResizerEl:function(){var a=YAHOO.util.Dom.get(this.handleElId).style;a.left="auto";a.right=0;a.top="auto";a.bottom=0;a.height=this.headCell.offsetHeight+"px"},onMouseUp:function(h){var f=this.datatable.getColumnSet().keys,b;for(var c=0,a=f.length;c<a;c++){b=f[c];if(b._ddResizer){b._ddResizer.resetResizerEl()}}this.resetResizerEl();var d=this.headCellLiner;var g=d.offsetWidth-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingLeft"),10)|0)-(parseInt(YAHOO.util.Dom.getStyle(d,"paddingRight"),10)|0);this.datatable.fireEvent("columnResizeEvent",{column:this.column,target:this.headCell,width:g})},onMouseDown:function(a){this.startWidth=this.headCellLiner.offsetWidth;this.startX=YAHOO.util.Event.getXY(a)[0];this.nLinerPadding=(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0)+(parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0)},clickValidator:function(b){if(!this.column.hidden){var a=YAHOO.util.Event.getTarget(b);return(this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id)))}},startDrag:function(){var e=this.datatable.getColumnSet().keys,d=this.column.getKeyIndex(),b;for(var c=0,a=e.length;c<a;c++){b=e[c];if(b._ddResizer){YAHOO.util.Dom.get(b._ddResizer.handleElId).style.height="1em"}}},onDrag:function(c){var d=YAHOO.util.Event.getXY(c)[0];if(d>YAHOO.util.Dom.getX(this.headCellLiner)){var a=d-this.startX;var b=this.startWidth+a-this.nLinerPadding;if(b>0){this.datatable.setColumnWidth(this.column,b)}}}})}(function(){var g=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,c=a.Dom,f=a.Event,d=e.DataTable;YAHOO.widget.RecordSet=function(h){this._init(h)};var b=e.RecordSet;b._nCount=0;b.prototype={_sId:null,_init:function(h){this._sId=c.generateId(null,"yui-rs");e.RecordSet._nCount++;this._records=[];this._initEvents();if(h){if(g.isArray(h)){this.addRecords(h)}else{if(g.isObject(h)){this.addRecord(h)}}}},_initEvents:function(){this.createEvent("recordAddEvent");this.createEvent("recordsAddEvent");this.createEvent("recordSetEvent");this.createEvent("recordsSetEvent");this.createEvent("recordUpdateEvent");this.createEvent("recordDeleteEvent");this.createEvent("recordsDeleteEvent");this.createEvent("resetEvent");this.createEvent("recordValueUpdateEvent")},_addRecord:function(j,h){var i=new YAHOO.widget.Record(j);if(YAHOO.lang.isNumber(h)&&(h>-1)){this._records.splice(h,0,i)}else{this._records[this._records.length]=i}return i},_setRecord:function(i,h){if(!g.isNumber(h)||h<0){h=this._records.length}return(this._records[h]=new e.Record(i))},_deleteRecord:function(i,h){if(!g.isNumber(h)||(h<0)){h=1}this._records.splice(i,h)},getId:function(){return this._sId},toString:function(){return"RecordSet instance "+this._sId},getLength:function(){return this._records.length},getRecord:function(h){var j;if(h instanceof e.Record){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h._sId)){return h}}}else{if(g.isNumber(h)){if((h>-1)&&(h<this.getLength())){return this._records[h]}}else{if(g.isString(h)){for(j=0;j<this._records.length;j++){if(this._records[j]&&(this._records[j]._sId===h)){return this._records[j]}}}}}return null},getRecords:function(i,h){if(!g.isNumber(i)){return this._records}if(!g.isNumber(h)){return this._records.slice(i)}return this._records.slice(i,i+h)},hasRecords:function(j,h){var l=this.getRecords(j,h);for(var k=0;k<h;++k){if(typeof l[k]==="undefined"){return false}}return true},getRecordIndex:function(j){if(j){for(var h=this._records.length-1;h>-1;h--){if(this._records[h]&&j.getId()===this._records[h].getId()){return h}}}return null},addRecord:function(j,h){if(g.isObject(j)){var i=this._addRecord(j,h);this.fireEvent("recordAddEvent",{record:i,data:j});return i}else{return null}},addRecords:function(m,l){if(g.isArray(m)){var p=[],j,n,h;l=g.isNumber(l)?l:this._records.length;j=l;for(n=0,h=m.length;n<h;++n){if(g.isObject(m[n])){var k=this._addRecord(m[n],j++);p.push(k)}}this.fireEvent("recordsAddEvent",{records:p,data:m});return p}else{if(g.isObject(m)){var o=this._addRecord(m);this.fireEvent("recordsAddEvent",{records:[o],data:m});return o}else{return null}}},setRecord:function(j,h){if(g.isObject(j)){var i=this._setRecord(j,h);this.fireEvent("recordSetEvent",{record:i,data:j});return i}else{return null}},setRecords:function(o,n){var r=e.Record,k=g.isArray(o)?o:[o],q=[],p=0,h=k.length,m=0;n=parseInt(n,10)|0;for(;p<h;++p){if(typeof k[p]==="object"&&k[p]){q[m++]=this._records[n+p]=new r(k[p])}}this.fireEvent("recordsSetEvent",{records:q,data:o});this.fireEvent("recordsSet",{records:q,data:o});if(k.length&&!q.length){}return q},updateRecord:function(h,l){var j=this.getRecord(h);if(j&&g.isObject(l)){var k={};for(var i in j._oData){if(g.hasOwnProperty(j._oData,i)){k[i]=j._oData[i]}}j._oData=l;this.fireEvent("recordUpdateEvent",{record:j,newData:l,oldData:k});return j}else{return null}},updateKey:function(h,i,j){this.updateRecordValue(h,i,j)},updateRecordValue:function(h,k,n){var j=this.getRecord(h);if(j){var m=null;var l=j._oData[k];if(l&&g.isObject(l)){m={};for(var i in l){if(g.hasOwnProperty(l,i)){m[i]=l[i]}}}else{m=l}j._oData[k]=n;this.fireEvent("keyUpdateEvent",{record:j,key:k,newData:n,oldData:m});this.fireEvent("recordValueUpdateEvent",{record:j,key:k,newData:n,oldData:m})}else{}},replaceRecords:function(h){this.reset();return this.addRecords(h)},sortRecords:function(h,j,i){return this._records.sort(function(l,k){return h(l,k,j,i)})},reverseRecords:function(){return this._records.reverse()},deleteRecord:function(h){if(g.isNumber(h)&&(h>-1)&&(h<this.getLength())){var i=this.getRecord(h).getData();this._deleteRecord(h);this.fireEvent("recordDeleteEvent",{data:i,index:h});return i}else{return null}},deleteRecords:function(k,h){if(!g.isNumber(h)){h=1}if(g.isNumber(k)&&(k>-1)&&(k<this.getLength())){var m=this.getRecords(k,h);var j=[],n=[];for(var l=0;l<m.length;l++){j[j.length]=m[l];n[n.length]=m[l].getData()}this._deleteRecord(k,h);this.fireEvent("recordsDeleteEvent",{data:j,deletedData:n,index:k});return j}else{return null}},reset:function(){this._records=[];this.fireEvent("resetEvent")}};g.augmentProto(b,a.EventProvider);YAHOO.widget.Record=function(h){this._nCount=e.Record._nCount;this._sId=c.generateId(null,"yui-rec");e.Record._nCount++;this._oData={};if(g.isObject(h)){for(var i in h){if(g.hasOwnProperty(h,i)){this._oData[i]=h[i]}}}};YAHOO.widget.Record._nCount=0;YAHOO.widget.Record.prototype={_nCount:null,_sId:null,_oData:null,getCount:function(){return this._nCount},getId:function(){return this._sId},getData:function(h){if(g.isString(h)){return this._oData[h]}else{return this._oData}},setData:function(h,i){this._oData[h]=i}}})();(function(){var h=YAHOO.lang,a=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,c=a.Dom,g=a.Event,f=a.DataSourceBase;YAHOO.widget.DataTable=function(i,m,o,k){var l=e.DataTable;if(k&&k.scrollable){return new YAHOO.widget.ScrollingDataTable(i,m,o,k)}this._nIndex=l._nCount;this._sId=c.generateId(null,"yui-dt");this._oChainRender=new YAHOO.util.Chain();this._oChainRender.subscribe("end",this._onRenderChainEnd,this,true);this._initConfigs(k);this._initDataSource(o);if(!this._oDataSource){return}this._initColumnSet(m);if(!this._oColumnSet){return}this._initRecordSet();if(!this._oRecordSet){}l.superclass.constructor.call(this,i,this.configs);var q=this._initDomElements(i);if(!q){return}this.showTableMessage(this.get("MSG_LOADING"),l.CLASS_LOADING);this._initEvents();l._nCount++;l._nCurrentCount++;var n={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,scope:this,argument:this.getState()};var p=this.get("initialLoad");if(p===true){this._oDataSource.sendRequest(this.get("initialRequest"),n)}else{if(p===false){this.showTableMessage(this.get("MSG_EMPTY"),l.CLASS_EMPTY)}else{var j=p||{};n.argument=j.argument||{};this._oDataSource.sendRequest(j.request,n)}}};var d=e.DataTable;h.augmentObject(d,{CLASS_DATATABLE:"yui-dt",CLASS_LINER:"yui-dt-liner",CLASS_LABEL:"yui-dt-label",CLASS_MESSAGE:"yui-dt-message",CLASS_MASK:"yui-dt-mask",CLASS_DATA:"yui-dt-data",CLASS_COLTARGET:"yui-dt-coltarget",CLASS_RESIZER:"yui-dt-resizer",CLASS_RESIZERLINER:"yui-dt-resizerliner",CLASS_RESIZERPROXY:"yui-dt-resizerproxy",CLASS_EDITOR:"yui-dt-editor",CLASS_EDITOR_SHIM:"yui-dt-editor-shim",CLASS_PAGINATOR:"yui-dt-paginator",CLASS_PAGE:"yui-dt-page",CLASS_DEFAULT:"yui-dt-default",CLASS_PREVIOUS:"yui-dt-previous",CLASS_NEXT:"yui-dt-next",CLASS_FIRST:"yui-dt-first",CLASS_LAST:"yui-dt-last",CLASS_REC:"yui-dt-rec",CLASS_EVEN:"yui-dt-even",CLASS_ODD:"yui-dt-odd",CLASS_SELECTED:"yui-dt-selected",CLASS_HIGHLIGHTED:"yui-dt-highlighted",CLASS_HIDDEN:"yui-dt-hidden",CLASS_DISABLED:"yui-dt-disabled",CLASS_EMPTY:"yui-dt-empty",CLASS_LOADING:"yui-dt-loading",CLASS_ERROR:"yui-dt-error",CLASS_EDITABLE:"yui-dt-editable",CLASS_DRAGGABLE:"yui-dt-draggable",CLASS_RESIZEABLE:"yui-dt-resizeable",CLASS_SCROLLABLE:"yui-dt-scrollable",CLASS_SORTABLE:"yui-dt-sortable",CLASS_ASC:"yui-dt-asc",CLASS_DESC:"yui-dt-desc",CLASS_BUTTON:"yui-dt-button",CLASS_CHECKBOX:"yui-dt-checkbox",CLASS_DROPDOWN:"yui-dt-dropdown",CLASS_RADIO:"yui-dt-radio",_nCount:0,_nCurrentCount:0,_elDynStyleNode:null,_bDynStylesFallback:(b.ie)?true:false,_oDynStyles:{},_cloneObject:function(m){if(!h.isValue(m)){return m}var p={};if(m instanceof YAHOO.widget.BaseCellEditor){p=m}else{if(Object.prototype.toString.apply(m)==="[object RegExp]"){p=m}else{if(h.isFunction(m)){p=m}else{if(h.isArray(m)){var n=[];for(var l=0,k=m.length;l<k;l++){n[l]=d._cloneObject(m[l])}p=n}else{if(h.isObject(m)){for(var j in m){if(h.hasOwnProperty(m,j)){if(h.isValue(m[j])&&h.isObject(m[j])||h.isArray(m[j])){p[j]=d._cloneObject(m[j])}else{p[j]=m[j]}}}}else{p=m}}}}}return p},formatButton:function(i,j,k,n,m){var l=h.isValue(n)?n:"Click";i.innerHTML='<button type="button" class="'+d.CLASS_BUTTON+'">'+l+"</button>"},formatCheckbox:function(i,j,k,n,m){var l=n;l=(l)?' checked="checked"':"";i.innerHTML='<input type="checkbox"'+l+' class="'+d.CLASS_CHECKBOX+'" />'},formatCurrency:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.currencyOptions||i.get("currencyOptions"))},formatDate:function(j,l,m,o,n){var i=n||this,k=m.dateOptions||i.get("dateOptions");j.innerHTML=a.Date.format(o,k,k.locale)},formatDropdown:function(l,u,q,j,t){var s=t||this,r=(h.isValue(j))?j:u.getData(q.field),v=(h.isArray(q.dropdownOptions))?q.dropdownOptions:null,k,p=l.getElementsByTagName("select");if(p.length===0){k=document.createElement("select");k.className=d.CLASS_DROPDOWN;k=l.appendChild(k);g.addListener(k,"change",s._onDropdownChange,s)}k=p[0];if(k){k.innerHTML="";if(v){for(var n=0;n<v.length;n++){var o=v[n];var m=document.createElement("option");m.value=(h.isValue(o.value))?o.value:o;m.innerHTML=(h.isValue(o.text))?o.text:(h.isValue(o.label))?o.label:o;m=k.appendChild(m);if(m.value==r){m.selected=true}}}else{k.innerHTML='<option selected value="'+r+'">'+r+"</option>"}}else{l.innerHTML=h.isValue(j)?j:""}},formatEmail:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="mailto:'+m+'">'+m+"</a>"}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):""}},formatLink:function(i,j,k,m,l){if(h.isString(m)){m=h.escapeHTML(m);i.innerHTML='<a href="'+m+'">'+m+"</a>"}else{i.innerHTML=h.isValue(m)?h.escapeHTML(m.toString()):""}},formatNumber:function(j,k,l,n,m){var i=m||this;j.innerHTML=a.Number.format(n,l.numberOptions||i.get("numberOptions"))},formatRadio:function(j,k,l,o,n){var i=n||this,m=o;m=(m)?' checked="checked"':"";j.innerHTML='<input type="radio"'+m+' name="'+i.getId()+"-col-"+l.getSanitizedKey()+'" class="'+d.CLASS_RADIO+'" />'},formatText:function(i,j,l,n,m){var k=(h.isValue(n))?n:"";i.innerHTML=h.escapeHTML(k.toString())},formatTextarea:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i="<textarea>"+l+"</textarea>";j.innerHTML=i},formatTextbox:function(j,k,m,o,n){var l=(h.isValue(o))?h.escapeHTML(o.toString()):"",i='<input type="text" value="'+l+'" />';j.innerHTML=i},formatDefault:function(i,j,k,m,l){i.innerHTML=(h.isValue(m)&&m!=="")?m.toString():"&#160;"},validateNumber:function(j){var i=j*1;if(h.isNumber(i)){return i}else{return undefined}}});d.Formatter={button:d.formatButton,checkbox:d.formatCheckbox,currency:d.formatCurrency,date:d.formatDate,dropdown:d.formatDropdown,email:d.formatEmail,link:d.formatLink,number:d.formatNumber,radio:d.formatRadio,text:d.formatText,textarea:d.formatTextarea,textbox:d.formatTextbox,defaultFormatter:d.formatDefault};h.extend(d,a.Element,{initAttributes:function(i){i=i||{};d.superclass.initAttributes.call(this,i);this.setAttributeConfig("summary",{value:"",validator:h.isString,method:function(j){if(this._elTable){this._elTable.summary=j}}});this.setAttributeConfig("selectionMode",{value:"standard",validator:h.isString});this.setAttributeConfig("sortedBy",{value:null,validator:function(j){if(j){return(h.isObject(j)&&j.key)}else{return(j===null)}},method:function(k){var r=this.get("sortedBy");this._configs.sortedBy.value=k;var j,o,m,q;if(this._elThead){if(r&&r.key&&r.dir){j=this._oColumnSet.getColumn(r.key);o=j.getKeyIndex();var u=j.getThEl();c.removeClass(u,r.dir);this.formatTheadCell(j.getThLinerEl().firstChild,j,k)}if(k){m=(k.column)?k.column:this._oColumnSet.getColumn(k.key);q=m.getKeyIndex();var v=m.getThEl();if(k.dir&&((k.dir=="asc")||(k.dir=="desc"))){var p=(k.dir=="desc")?d.CLASS_DESC:d.CLASS_ASC;c.addClass(v,p)}else{var l=k.dir||d.CLASS_ASC;c.addClass(v,l)}this.formatTheadCell(m.getThLinerEl().firstChild,m,k)}}if(this._elTbody){this._elTbody.style.display="none";var s=this._elTbody.rows,t;for(var n=s.length-1;n>-1;n--){t=s[n].childNodes;if(t[o]){c.removeClass(t[o],r.dir)}if(t[q]){c.addClass(t[q],k.dir)}}this._elTbody.style.display=""}this._clearTrTemplateEl()}});this.setAttributeConfig("paginator",{value:null,validator:function(j){return j===null||j instanceof e.Paginator},method:function(){this._updatePaginator.apply(this,arguments)}});this.setAttributeConfig("caption",{value:null,validator:h.isString,method:function(j){this._initCaptionEl(j)}});this.setAttributeConfig("draggableColumns",{value:false,validator:h.isBoolean,method:function(j){if(this._elThead){if(j){this._initDraggableColumns()}else{this._destroyDraggableColumns()}}}});this.setAttributeConfig("renderLoopSize",{value:0,validator:h.isNumber});this.setAttributeConfig("sortFunction",{value:function(k,j,o,n){var m=YAHOO.util.Sort.compare,l=m(k.getData(n),j.getData(n),o);if(l===0){return m(k.getCount(),j.getCount(),o)}else{return l}}});this.setAttributeConfig("formatRow",{value:null,validator:h.isFunction});this.setAttributeConfig("generateRequest",{value:function(k,n){k=k||{pagination:null,sortedBy:null};var m=encodeURIComponent((k.sortedBy)?k.sortedBy.key:n.getColumnSet().keys[0].getKey());var j=(k.sortedBy&&k.sortedBy.dir===YAHOO.widget.DataTable.CLASS_DESC)?"desc":"asc";var o=(k.pagination)?k.pagination.recordOffset:0;var l=(k.pagination)?k.pagination.rowsPerPage:null;return"sort="+m+"&dir="+j+"&startIndex="+o+((l!==null)?"&results="+l:"")},validator:h.isFunction});this.setAttributeConfig("initialRequest",{value:null});this.setAttributeConfig("initialLoad",{value:true});this.setAttributeConfig("dynamicData",{value:false,validator:h.isBoolean});this.setAttributeConfig("MSG_EMPTY",{value:"No records found.",validator:h.isString});this.setAttributeConfig("MSG_LOADING",{value:"Loading...",validator:h.isString});this.setAttributeConfig("MSG_ERROR",{value:"Data error.",validator:h.isString});this.setAttributeConfig("MSG_SORTASC",{value:"Click to sort ascending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_ASC){m[l]._elThLabel.firstChild.title=k}}}}});this.setAttributeConfig("MSG_SORTDESC",{value:"Click to sort descending",validator:h.isString,method:function(k){if(this._elThead){for(var l=0,m=this.getColumnSet().keys,j=m.length;l<j;l++){if(m[l].sortable&&this.getColumnSortDir(m[l])===d.CLASS_DESC){m[l]._elThLabel.firstChild.title=k}}}}});this.setAttributeConfig("currencySymbol",{value:"$",validator:h.isString});this.setAttributeConfig("currencyOptions",{value:{prefix:this.get("currencySymbol"),decimalPlaces:2,decimalSeparator:".",thousandsSeparator:","}});this.setAttributeConfig("dateOptions",{value:{format:"%m/%d/%Y",locale:"en"}});this.setAttributeConfig("numberOptions",{value:{decimalPlaces:0,thousandsSeparator:","}})},_bInit:true,_nIndex:null,_nTrCount:0,_nTdCount:0,_sId:null,_oChainRender:null,_elContainer:null,_elMask:null,_elTable:null,_elCaption:null,_elColgroup:null,_elThead:null,_elTbody:null,_elMsgTbody:null,_elMsgTr:null,_elMsgTd:null,_elColumnDragTarget:null,_elColumnResizerProxy:null,_oDataSource:null,_oColumnSet:null,_oRecordSet:null,_oCellEditor:null,_sFirstTrId:null,_sLastTrId:null,_elTrTemplate:null,_aDynFunctions:[],_disabled:false,clearTextSelection:function(){var i;if(window.getSelection){i=window.getSelection()}else{if(document.getSelection){i=document.getSelection()}else{if(document.selection){i=document.selection}}}if(i){if(i.empty){i.empty()}else{if(i.removeAllRanges){i.removeAllRanges()}else{if(i.collapse){i.collapse()}}}}},_focusEl:function(i){i=i||this._elTbody;setTimeout(function(){try{i.focus()}catch(j){}},0)},_repaintGecko:(b.gecko)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k)}:function(){},_repaintOpera:(b.opera)?function(){if(b.opera){document.documentElement.className+=" ";document.documentElement.className=YAHOO.lang.trim(document.documentElement.className)}}:function(){},_repaintWebkit:(b.webkit)?function(j){j=j||this._elContainer;var i=j.parentNode;var k=j.nextSibling;i.insertBefore(i.removeChild(j),k)}:function(){},_initConfigs:function(i){if(!i||!h.isObject(i)){i={}}this.configs=i},_initColumnSet:function(n){var m,k,j;if(this._oColumnSet){for(k=0,j=this._oColumnSet.keys.length;k<j;k++){m=this._oColumnSet.keys[k];d._oDynStyles["."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER]=undefined;if(m.editor&&m.editor.unsubscribeAll){m.editor.unsubscribeAll()}}this._oColumnSet=null;this._clearTrTemplateEl()}if(h.isArray(n)){this._oColumnSet=new YAHOO.widget.ColumnSet(n)}else{if(n instanceof YAHOO.widget.ColumnSet){this._oColumnSet=n}}var l=this._oColumnSet.keys;for(k=0,j=l.length;k<j;k++){m=l[k];if(m.editor&&m.editor.subscribe){m.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);m.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);m.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);m.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);m.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);m.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);m.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);m.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true)}}},_initDataSource:function(j){this._oDataSource=null;if(j&&(h.isFunction(j.sendRequest))){this._oDataSource=j}else{var k=null;var o=this._elContainer;var l=0;if(o.hasChildNodes()){var n=o.childNodes;for(l=0;l<n.length;l++){if(n[l].nodeName&&n[l].nodeName.toLowerCase()=="table"){k=n[l];break}}if(k){var m=[];for(;l<this._oColumnSet.keys.length;l++){m.push({key:this._oColumnSet.keys[l].key})}this._oDataSource=new f(k);this._oDataSource.responseType=f.TYPE_HTMLTABLE;this._oDataSource.responseSchema={fields:m}}}}},_initRecordSet:function(){if(this._oRecordSet){this._oRecordSet.reset()}else{this._oRecordSet=new YAHOO.widget.RecordSet()}},_initDomElements:function(i){this._initContainerEl(i);this._initTableEl(this._elContainer);this._initColgroupEl(this._elTable);this._initTheadEl(this._elTable);this._initMsgTbodyEl(this._elTable);this._initTbodyEl(this._elTable);if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody){return false}else{return true}},_destroyContainerEl:function(m){var k=this._oColumnSet.keys,l,j;c.removeClass(m,d.CLASS_DATATABLE);g.purgeElement(m);g.purgeElement(this._elThead,true);g.purgeElement(this._elTbody);g.purgeElement(this._elMsgTbody);l=m.getElementsByTagName("select");if(l.length){g.detachListener(l,"change")}for(j=k.length-1;j>=0;--j){if(k[j].editor){g.purgeElement(k[j].editor._elContainer)}}m.innerHTML="";this._elContainer=null;this._elColgroup=null;this._elThead=null;this._elTbody=null},_initContainerEl:function(j){j=c.get(j);if(j&&j.nodeName&&(j.nodeName.toLowerCase()=="div")){this._destroyContainerEl(j);c.addClass(j,d.CLASS_DATATABLE);g.addListener(j,"focus",this._onTableFocus,this);g.addListener(j,"dblclick",this._onTableDblclick,this);this._elContainer=j;var i=document.createElement("div");i.className=d.CLASS_MASK;i.style.display="none";this._elMask=j.appendChild(i)}},_destroyTableEl:function(){var i=this._elTable;if(i){g.purgeElement(i,true);i.parentNode.removeChild(i);this._elCaption=null;this._elColgroup=null;this._elThead=null;this._elTbody=null}},_initCaptionEl:function(i){if(this._elTable&&i){if(!this._elCaption){this._elCaption=this._elTable.createCaption()}this._elCaption.innerHTML=i}else{if(this._elCaption){this._elCaption.parentNode.removeChild(this._elCaption)}}},_initTableEl:function(i){if(i){this._destroyTableEl();this._elTable=i.appendChild(document.createElement("table"));this._elTable.summary=this.get("summary");if(this.get("caption")){this._initCaptionEl(this.get("caption"))}g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"thead ."+d.CLASS_LABEL,this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-data>tr>td",this);g.delegate(this._elTable,"mouseenter",this._onTableMouseover,"tbody.yui-dt-message>tr>td",this);g.delegate(this._elTable,"mouseleave",this._onTableMouseout,"tbody.yui-dt-message>tr>td",this)}},_destroyColgroupEl:function(){var i=this._elColgroup;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elColgroup=null}},_initColgroupEl:function(s){if(s){this._destroyColgroupEl();var l=this._aColIds||[],r=this._oColumnSet.keys,m=0,p=l.length,j,o,q=document.createDocumentFragment(),n=document.createElement("col");for(m=0,p=r.length;m<p;m++){o=r[m];j=q.appendChild(n.cloneNode(false))}var k=s.insertBefore(document.createElement("colgroup"),s.firstChild);k.appendChild(q);this._elColgroup=k}},_insertColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup){var j=this._elColgroup.childNodes[i]||null;this._elColgroup.insertBefore(document.createElement("col"),j)}},_removeColgroupColEl:function(i){if(h.isNumber(i)&&this._elColgroup&&this._elColgroup.childNodes[i]){this._elColgroup.removeChild(this._elColgroup.childNodes[i])}},_reorderColgroupColEl:function(l,k){if(h.isArray(l)&&h.isNumber(k)&&this._elColgroup&&(this._elColgroup.childNodes.length>l[l.length-1])){var j,n=[];for(j=l.length-1;j>-1;j--){n.push(this._elColgroup.removeChild(this._elColgroup.childNodes[l[j]]))}var m=this._elColgroup.childNodes[k]||null;for(j=n.length-1;j>-1;j--){this._elColgroup.insertBefore(n[j],m)}}},_destroyTheadEl:function(){var j=this._elThead;if(j){var i=j.parentNode;g.purgeElement(j,true);this._destroyColumnHelpers();i.removeChild(j);this._elThead=null}},_initTheadEl:function(v){v=v||this._elTable;if(v){this._destroyTheadEl();var q=(this._elColgroup)?v.insertBefore(document.createElement("thead"),this._elColgroup.nextSibling):v.appendChild(document.createElement("thead"));g.addListener(q,"focus",this._onTheadFocus,this);g.addListener(q,"keydown",this._onTheadKeydown,this);g.addListener(q,"mousedown",this._onTableMousedown,this);g.addListener(q,"mouseup",this._onTableMouseup,this);g.addListener(q,"click",this._onTheadClick,this);var x=this._oColumnSet,t,r,p,n;var w=x.tree;var o;for(r=0;r<w.length;r++){var m=q.appendChild(document.createElement("tr"));for(p=0;p<w[r].length;p++){t=w[r][p];o=m.appendChild(document.createElement("th"));this._initThEl(o,t)}if(r===0){c.addClass(m,d.CLASS_FIRST)}if(r===(w.length-1)){c.addClass(m,d.CLASS_LAST)}}var k=x.headers[0]||[];for(r=0;r<k.length;r++){c.addClass(c.get(this.getId()+"-th-"+k[r]),d.CLASS_FIRST)}var s=x.headers[x.headers.length-1]||[];for(r=0;r<s.length;r++){c.addClass(c.get(this.getId()+"-th-"+s[r]),d.CLASS_LAST)}if(b.webkit&&b.webkit<420){var u=this;setTimeout(function(){q.style.display=""},0);q.style.display="none"}this._elThead=q;this._initColumnHelpers()}},_initThEl:function(m,l){m.id=this.getId()+"-th-"+l.getSanitizedKey();m.innerHTML="";m.rowSpan=l.getRowspan();m.colSpan=l.getColspan();l._elTh=m;var i=m.appendChild(document.createElement("div"));i.id=m.id+"-liner";i.className=d.CLASS_LINER;l._elThLiner=i;var j=i.appendChild(document.createElement("span"));j.className=d.CLASS_LABEL;if(l.abbr){m.abbr=l.abbr}if(l.hidden){this._clearMinWidth(l)}m.className=this._getColumnClassNames(l);if(l.width){var k=(l.minWidth&&(l.width<l.minWidth))?l.minWidth:l.width;if(d._bDynStylesFallback){m.firstChild.style.overflow="hidden";m.firstChild.style.width=k+"px"}else{this._setColumnWidthDynStyles(l,k+"px","hidden")}}this.formatTheadCell(j,l,this.get("sortedBy"));l._elThLabel=j},formatTheadCell:function(i,m,k){var q=m.getKey();var p=h.isValue(m.label)?m.label:q;if(m.sortable){var n=this.getColumnSortDir(m,k);var j=(n===d.CLASS_DESC);if(k&&(m.key===k.key)){j=!(k.dir===d.CLASS_DESC)}var l=this.getId()+"-href-"+m.getSanitizedKey();var o=(j)?this.get("MSG_SORTDESC"):this.get("MSG_SORTASC");i.innerHTML='<a href="'+l+'" title="'+o+'" class="'+d.CLASS_SORTABLE+'">'+p+"</a>"}else{i.innerHTML=p}},_destroyDraggableColumns:function(){var l,m;for(var k=0,j=this._oColumnSet.tree[0].length;k<j;k++){l=this._oColumnSet.tree[0][k];if(l._dd){l._dd=l._dd.unreg();c.removeClass(l.getThEl(),d.CLASS_DRAGGABLE)}}this._destroyColumnDragTargetEl()},_initDraggableColumns:function(){this._destroyDraggableColumns();if(a.DD){var m,n,k;for(var l=0,j=this._oColumnSet.tree[0].length;l<j;l++){m=this._oColumnSet.tree[0][l];n=m.getThEl();c.addClass(n,d.CLASS_DRAGGABLE);k=this._initColumnDragTargetEl();m._dd=new YAHOO.widget.ColumnDD(this,m,n,k)}}else{}},_destroyColumnDragTargetEl:function(){if(this._elColumnDragTarget){var i=this._elColumnDragTarget;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnDragTarget=null}},_initColumnDragTargetEl:function(){if(!this._elColumnDragTarget){var i=document.createElement("div");i.id=this.getId()+"-coltarget";i.className=d.CLASS_COLTARGET;i.style.display="none";document.body.insertBefore(i,document.body.firstChild);this._elColumnDragTarget=i}return this._elColumnDragTarget},_destroyResizeableColumns:function(){var k=this._oColumnSet.keys;for(var l=0,j=k.length;l<j;l++){if(k[l]._ddResizer){k[l]._ddResizer=k[l]._ddResizer.unreg();c.removeClass(k[l].getThEl(),d.CLASS_RESIZEABLE)}}this._destroyColumnResizerProxyEl()},_initResizeableColumns:function(){this._destroyResizeableColumns();if(a.DD){var p,k,n,q,j,r,m;for(var l=0,o=this._oColumnSet.keys.length;l<o;l++){p=this._oColumnSet.keys[l];if(p.resizeable){k=p.getThEl();c.addClass(k,d.CLASS_RESIZEABLE);n=p.getThLinerEl();q=k.appendChild(document.createElement("div"));q.className=d.CLASS_RESIZERLINER;q.appendChild(n);j=q.appendChild(document.createElement("div"));j.id=k.id+"-resizer";j.className=d.CLASS_RESIZER;p._elResizer=j;r=this._initColumnResizerProxyEl();p._ddResizer=new YAHOO.util.ColumnResizer(this,p,k,j,r);m=function(i){g.stopPropagation(i)};g.addListener(j,"click",m)}}}else{}},_destroyColumnResizerProxyEl:function(){if(this._elColumnResizerProxy){var i=this._elColumnResizerProxy;YAHOO.util.Event.purgeElement(i);i.parentNode.removeChild(i);this._elColumnResizerProxy=null}},_initColumnResizerProxyEl:function(){if(!this._elColumnResizerProxy){var i=document.createElement("div");i.id=this.getId()+"-colresizerproxy";i.className=d.CLASS_RESIZERPROXY;document.body.insertBefore(i,document.body.firstChild);this._elColumnResizerProxy=i}return this._elColumnResizerProxy},_destroyColumnHelpers:function(){this._destroyDraggableColumns();this._destroyResizeableColumns()},_initColumnHelpers:function(){if(this.get("draggableColumns")){this._initDraggableColumns()}this._initResizeableColumns()},_destroyTbodyEl:function(){var i=this._elTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null}},_initTbodyEl:function(j){if(j){this._destroyTbodyEl();var i=j.appendChild(document.createElement("tbody"));i.tabIndex=0;i.className=d.CLASS_DATA;g.addListener(i,"focus",this._onTbodyFocus,this);g.addListener(i,"mousedown",this._onTableMousedown,this);g.addListener(i,"mouseup",this._onTableMouseup,this);g.addListener(i,"keydown",this._onTbodyKeydown,this);g.addListener(i,"click",this._onTbodyClick,this);if(b.ie){i.hideFocus=true}this._elTbody=i}},_destroyMsgTbodyEl:function(){var i=this._elMsgTbody;if(i){var j=i.parentNode;g.purgeElement(i,true);j.removeChild(i);this._elTbody=null}},_initMsgTbodyEl:function(l){if(l){var k=document.createElement("tbody");k.className=d.CLASS_MESSAGE;var j=k.appendChild(document.createElement("tr"));j.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTr=j;var m=j.appendChild(document.createElement("td"));m.colSpan=this._oColumnSet.keys.length||1;m.className=d.CLASS_FIRST+" "+d.CLASS_LAST;this._elMsgTd=m;k=l.insertBefore(k,this._elTbody);var i=m.appendChild(document.createElement("div"));i.className=d.CLASS_LINER;this._elMsgTbody=k;g.addListener(k,"focus",this._onTbodyFocus,this);g.addListener(k,"mousedown",this._onTableMousedown,this);g.addListener(k,"mouseup",this._onTableMouseup,this);g.addListener(k,"keydown",this._onTbodyKeydown,this);g.addListener(k,"click",this._onTbodyClick,this)}},_initEvents:function(){this._initColumnSort();YAHOO.util.Event.addListener(document,"click",this._onDocumentClick,this);this.subscribe("paginatorChange",function(){this._handlePaginatorChange.apply(this,arguments)});this.subscribe("initEvent",function(){this.renderPaginator()});this._initCellEditing()},_initColumnSort:function(){this.subscribe("theadCellClickEvent",this.onEventSortColumn);var i=this.get("sortedBy");if(i){if(i.dir=="desc"){this._configs.sortedBy.value.dir=d.CLASS_DESC}else{if(i.dir=="asc"){this._configs.sortedBy.value.dir=d.CLASS_ASC}}}},_initCellEditing:function(){this.subscribe("editorBlurEvent",function(){this.onEditorBlurEvent.apply(this,arguments)});this.subscribe("editorBlockEvent",function(){this.onEditorBlockEvent.apply(this,arguments)});this.subscribe("editorUnblockEvent",function(){this.onEditorUnblockEvent.apply(this,arguments)})},_getColumnClassNames:function(l,k){var i;if(h.isString(l.className)){i=[l.className]}else{if(h.isArray(l.className)){i=l.className}else{i=[]}}i[i.length]=this.getId()+"-col-"+l.getSanitizedKey();i[i.length]="yui-dt-col-"+l.getSanitizedKey();var j=this.get("sortedBy")||{};if(l.key===j.key){i[i.length]=j.dir||""}if(l.hidden){i[i.length]=d.CLASS_HIDDEN}if(l.selected){i[i.length]=d.CLASS_SELECTED}if(l.sortable){i[i.length]=d.CLASS_SORTABLE}if(l.resizeable){i[i.length]=d.CLASS_RESIZEABLE}if(l.editor){i[i.length]=d.CLASS_EDITABLE}if(k){i=i.concat(k)}return i.join(" ")},_clearTrTemplateEl:function(){this._elTrTemplate=null},_getTrTemplateEl:function(u,o){if(this._elTrTemplate){return this._elTrTemplate}else{var q=document,s=q.createElement("tr"),l=q.createElement("td"),k=q.createElement("div");l.appendChild(k);var t=document.createDocumentFragment(),r=this._oColumnSet.keys,n;var p;for(var m=0,j=r.length;m<j;m++){n=l.cloneNode(true);n=this._formatTdEl(r[m],n,m,(m===j-1));t.appendChild(n)}s.appendChild(t);s.className=d.CLASS_REC;this._elTrTemplate=s;return s}},_formatTdEl:function(n,p,q,m){var t=this._oColumnSet;var i=t.headers,k=i[q],o="",v;for(var l=0,u=k.length;l<u;l++){v=this._sId+"-th-"+k[l]+" ";o+=v}p.headers=o;var s=[];if(q===0){s[s.length]=d.CLASS_FIRST}if(m){s[s.length]=d.CLASS_LAST}p.className=this._getColumnClassNames(n,s);p.firstChild.className=d.CLASS_LINER;if(n.width&&d._bDynStylesFallback){var r=(n.minWidth&&(n.width<n.minWidth))?n.minWidth:n.width;p.firstChild.style.overflow="hidden";p.firstChild.style.width=r+"px"}return p},_addTrEl:function(k){var j=this._getTrTemplateEl();var i=j.cloneNode(true);return this._updateTrEl(i,k)},_updateTrEl:function(q,r){var p=this.get("formatRow")?this.get("formatRow").call(this,q,r):true;if(p){q.style.display="none";var o=q.childNodes,m;for(var l=0,n=o.length;l<n;++l){m=o[l];this.formatCell(o[l].firstChild,r,this._oColumnSet.keys[l])}q.style.display=""}var j=q.id,k=r.getId();if(this._sFirstTrId===j){this._sFirstTrId=k}if(this._sLastTrId===j){this._sLastTrId=k}q.id=k;return q},_deleteTrEl:function(i){var j;if(!h.isNumber(i)){j=c.get(i).sectionRowIndex}else{j=i}if(h.isNumber(j)&&(j>-2)&&(j<this._elTbody.rows.length)){return this._elTbody.removeChild(this._elTbody.rows[i])}else{return null}},_unsetFirstRow:function(){if(this._sFirstTrId){c.removeClass(this._sFirstTrId,d.CLASS_FIRST);this._sFirstTrId=null}},_setFirstRow:function(){this._unsetFirstRow();var i=this.getFirstTrEl();if(i){c.addClass(i,d.CLASS_FIRST);this._sFirstTrId=i.id}},_unsetLastRow:function(){if(this._sLastTrId){c.removeClass(this._sLastTrId,d.CLASS_LAST);this._sLastTrId=null}},_setLastRow:function(){this._unsetLastRow();var i=this.getLastTrEl();if(i){c.addClass(i,d.CLASS_LAST);this._sLastTrId=i.id}},_setRowStripes:function(t,l){var m=this._elTbody.rows,q=0,s=m.length,p=[],r=0,n=[],j=0;if((t!==null)&&(t!==undefined)){var o=this.getTrEl(t);if(o){q=o.sectionRowIndex;if(h.isNumber(l)&&(l>1)){s=q+l}}}for(var k=q;k<s;k++){if(k%2){p[r++]=m[k]}else{n[j++]=m[k]}}if(p.length){c.replaceClass(p,d.CLASS_EVEN,d.CLASS_ODD)}if(n.length){c.replaceClass(n,d.CLASS_ODD,d.CLASS_EVEN)}},_setSelections:function(){var l=this.getSelectedRows();var n=this.getSelectedCells();if((l.length>0)||(n.length>0)){var m=this._oColumnSet,k;for(var j=0;j<l.length;j++){k=c.get(l[j]);if(k){c.addClass(k,d.CLASS_SELECTED)}}for(j=0;j<n.length;j++){k=c.get(n[j].recordId);if(k){c.addClass(k.childNodes[m.getColumn(n[j].columnKey).getKeyIndex()],d.CLASS_SELECTED)}}}},_onRenderChainEnd:function(){this.hideTableMessage();if(this._elTbody.rows.length===0){this.showTableMessage(this.get("MSG_EMPTY"),d.CLASS_EMPTY)}var i=this;setTimeout(function(){if((i instanceof d)&&i._sId){if(i._bInit){i._bInit=false;i.fireEvent("initEvent")}i.fireEvent("renderEvent");i.fireEvent("refreshEvent");i.validateColumnWidths();i.fireEvent("postRenderEvent")}},0)},_onDocumentClick:function(l,j){var m=g.getTarget(l);var i=m.nodeName.toLowerCase();if(!c.isAncestor(j._elContainer,m)){j.fireEvent("tableBlurEvent");if(j._oCellEditor){if(j._oCellEditor.getContainerEl){var k=j._oCellEditor.getContainerEl();if(!c.isAncestor(k,m)&&(k.id!==m.id)){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor})}}else{if(j._oCellEditor.isActive){if(!c.isAncestor(j._oCellEditor.container,m)&&(j._oCellEditor.container.id!==m.id)){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor})}}}}}},_onTableFocus:function(j,i){i.fireEvent("tableFocusEvent")},_onTheadFocus:function(j,i){i.fireEvent("theadFocusEvent");i.fireEvent("tableFocusEvent")},_onTbodyFocus:function(j,i){i.fireEvent("tbodyFocusEvent");i.fireEvent("tableFocusEvent")},_onTableMouseover:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoverEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoverEvent",{target:o,event:n})}break;case"th":l=k.fireEvent("theadCellMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoverEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoverEvent",{target:o,event:n});l=k.fireEvent("headerRowMouseoverEvent",{target:o,event:n})}else{l=k.fireEvent("rowMouseoverEvent",{target:o,event:n})}break;default:break}if(l===false){return}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase()}}}k.fireEvent("tableMouseoverEvent",{target:(o||k._elContainer),event:n})},_onTableMouseout:function(n,m,i,k){var o=m;var j=o.nodeName&&o.nodeName.toLowerCase();var l=true;while(o&&(j!="table")){switch(j){case"body":return;case"a":break;case"td":l=k.fireEvent("cellMouseoutEvent",{target:o,event:n});break;case"span":if(c.hasClass(o,d.CLASS_LABEL)){l=k.fireEvent("theadLabelMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerLabelMouseoutEvent",{target:o,event:n})}break;case"th":l=k.fireEvent("theadCellMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerCellMouseoutEvent",{target:o,event:n});break;case"tr":if(o.parentNode.nodeName.toLowerCase()=="thead"){l=k.fireEvent("theadRowMouseoutEvent",{target:o,event:n});l=k.fireEvent("headerRowMouseoutEvent",{target:o,event:n})}else{l=k.fireEvent("rowMouseoutEvent",{target:o,event:n})}break;default:break}if(l===false){return}else{o=o.parentNode;if(o){j=o.nodeName.toLowerCase()}}}k.fireEvent("tableMouseoutEvent",{target:(o||k._elContainer),event:n})},_onTableMousedown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMousedownEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMousedownEvent",{target:m,event:l});k=j.fireEvent("headerLabelMousedownEvent",{target:m,event:l})}break;case"th":k=j.fireEvent("theadCellMousedownEvent",{target:m,event:l});k=j.fireEvent("headerCellMousedownEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMousedownEvent",{target:m,event:l});k=j.fireEvent("headerRowMousedownEvent",{target:m,event:l})}else{k=j.fireEvent("rowMousedownEvent",{target:m,event:l})}break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableMousedownEvent",{target:(m||j._elContainer),event:l})},_onTableMouseup:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"a":break;case"td":k=j.fireEvent("cellMouseupEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelMouseupEvent",{target:m,event:l});k=j.fireEvent("headerLabelMouseupEvent",{target:m,event:l})}break;case"th":k=j.fireEvent("theadCellMouseupEvent",{target:m,event:l});k=j.fireEvent("headerCellMouseupEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowMouseupEvent",{target:m,event:l});k=j.fireEvent("headerRowMouseupEvent",{target:m,event:l})}else{k=j.fireEvent("rowMouseupEvent",{target:m,event:l})}break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableMouseupEvent",{target:(m||j._elContainer),event:l})},_onTableDblclick:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"td":k=j.fireEvent("cellDblclickEvent",{target:m,event:l});break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelDblclickEvent",{target:m,event:l});k=j.fireEvent("headerLabelDblclickEvent",{target:m,event:l})}break;case"th":k=j.fireEvent("theadCellDblclickEvent",{target:m,event:l});k=j.fireEvent("headerCellDblclickEvent",{target:m,event:l});break;case"tr":if(m.parentNode.nodeName.toLowerCase()=="thead"){k=j.fireEvent("theadRowDblclickEvent",{target:m,event:l});k=j.fireEvent("headerRowDblclickEvent",{target:m,event:l})}else{k=j.fireEvent("rowDblclickEvent",{target:m,event:l})}break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableDblclickEvent",{target:(m||j._elContainer),event:l})},_onTheadKeydown:function(l,j){var m=g.getTarget(l);var i=m.nodeName&&m.nodeName.toLowerCase();var k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":case"textarea":break;case"thead":k=j.fireEvent("theadKeyEvent",{target:m,event:l});break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableKeyEvent",{target:(m||j._elContainer),event:l})},_onTbodyKeydown:function(m,k){var j=k.get("selectionMode");if(j=="standard"){k._handleStandardSelectionByKey(m)}else{if(j=="single"){k._handleSingleSelectionByKey(m)}else{if(j=="cellblock"){k._handleCellBlockSelectionByKey(m)}else{if(j=="cellrange"){k._handleCellRangeSelectionByKey(m)}else{if(j=="singlecell"){k._handleSingleCellSelectionByKey(m)}}}}}if(k._oCellEditor){if(k._oCellEditor.fireEvent){k._oCellEditor.fireEvent("blurEvent",{editor:k._oCellEditor})}else{if(k._oCellEditor.isActive){k.fireEvent("editorBlurEvent",{editor:k._oCellEditor})}}}var n=g.getTarget(m);var i=n.nodeName&&n.nodeName.toLowerCase();var l=true;while(n&&(i!="table")){switch(i){case"body":return;case"tbody":l=k.fireEvent("tbodyKeyEvent",{target:n,event:m});break;default:break}if(l===false){return}else{n=n.parentNode;if(n){i=n.nodeName.toLowerCase()}}}k.fireEvent("tableKeyEvent",{target:(n||k._elContainer),event:m})},_onTheadClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor})}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor})}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("theadCheckboxClickEvent",{target:m,event:l})}else{if(n=="radio"){k=j.fireEvent("theadRadioClickEvent",{target:m,event:l})}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l})}else{k=false}}else{if(m.disabled){k=false}}}}break;case"a":k=j.fireEvent("theadLinkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("theadButtonClickEvent",{target:m,event:l})}else{k=false}break;case"span":if(c.hasClass(m,d.CLASS_LABEL)){k=j.fireEvent("theadLabelClickEvent",{target:m,event:l});k=j.fireEvent("headerLabelClickEvent",{target:m,event:l})}break;case"th":k=j.fireEvent("theadCellClickEvent",{target:m,event:l});k=j.fireEvent("headerCellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("theadRowClickEvent",{target:m,event:l});k=j.fireEvent("headerRowClickEvent",{target:m,event:l});break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l})},_onTbodyClick:function(l,j){if(j._oCellEditor){if(j._oCellEditor.fireEvent){j._oCellEditor.fireEvent("blurEvent",{editor:j._oCellEditor})}else{if(j._oCellEditor.isActive){j.fireEvent("editorBlurEvent",{editor:j._oCellEditor})}}}var m=g.getTarget(l),i=m.nodeName&&m.nodeName.toLowerCase(),k=true;while(m&&(i!="table")){switch(i){case"body":return;case"input":var n=m.type.toLowerCase();if(n=="checkbox"){k=j.fireEvent("checkboxClickEvent",{target:m,event:l})}else{if(n=="radio"){k=j.fireEvent("radioClickEvent",{target:m,event:l})}else{if((n=="button")||(n=="image")||(n=="submit")||(n=="reset")){if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l})}else{k=false}}else{if(m.disabled){k=false}}}}break;case"a":k=j.fireEvent("linkClickEvent",{target:m,event:l});break;case"button":if(!m.disabled){k=j.fireEvent("buttonClickEvent",{target:m,event:l})}else{k=false}break;case"td":k=j.fireEvent("cellClickEvent",{target:m,event:l});break;case"tr":k=j.fireEvent("rowClickEvent",{target:m,event:l});break;default:break}if(k===false){return}else{m=m.parentNode;if(m){i=m.nodeName.toLowerCase()}}}j.fireEvent("tableClickEvent",{target:(m||j._elContainer),event:l})},_onDropdownChange:function(j,i){var k=g.getTarget(j);i.fireEvent("dropdownChangeEvent",{event:j,target:k})},configs:null,getId:function(){return this._sId},toString:function(){return"DataTable instance "+this._sId},getDataSource:function(){return this._oDataSource},getColumnSet:function(){return this._oColumnSet},getRecordSet:function(){return this._oRecordSet},getState:function(){return{totalRecords:this.get("paginator")?this.get("paginator").get("totalRecords"):this._oRecordSet.getLength(),pagination:this.get("paginator")?this.get("paginator").getState():null,sortedBy:this.get("sortedBy"),selectedRows:this.getSelectedRows(),selectedCells:this.getSelectedCells()}},getContainerEl:function(){return this._elContainer},getTableEl:function(){return this._elTable},getTheadEl:function(){return this._elThead},getTbodyEl:function(){return this._elTbody},getMsgTbodyEl:function(){return this._elMsgTbody},getMsgTdEl:function(){return this._elMsgTd},getTrEl:function(k){if(k instanceof YAHOO.widget.Record){return document.getElementById(k.getId())}else{if(h.isNumber(k)){var j=c.getElementsByClassName(d.CLASS_REC,"tr",this._elTbody);return j&&j[k]?j[k]:null}else{if(k){var i=(h.isString(k))?document.getElementById(k):k;if(i&&i.ownerDocument==document){if(i.nodeName.toLowerCase()!="tr"){i=c.getAncestorByTagName(i,"tr")}return i}}}}return null},getFirstTrEl:function(){var k=this._elTbody.rows,j=0;while(k[j]){if(this.getRecord(k[j])){return k[j]}j++}return null},getLastTrEl:function(){var k=this._elTbody.rows,j=k.length-1;while(j>-1){if(this.getRecord(k[j])){return k[j]}j--}return null},getNextTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j<k.length-1){l=k[j+1];if(this.getRecord(l)){return l}j++}}else{if(j<k.length-1){return k[j+1]}}}return null},getPreviousTrEl:function(l,i){var j=this.getTrIndex(l);if(j!==null){var k=this._elTbody.rows;if(i){while(j>0){l=k[j-1];if(this.getRecord(l)){return l}j--}}else{if(j>0){return k[j-1]}}}return null},getCellIndex:function(k){k=this.getTdEl(k);if(k){if(b.ie>0){var l=0,n=k.parentNode,m=n.childNodes,j=m.length;for(;l<j;l++){if(m[l]==k){return l}}}else{return k.cellIndex}}},getTdLinerEl:function(i){var j=this.getTdEl(i);return j.firstChild||null},getTdEl:function(i){var n;var l=c.get(i);if(l&&(l.ownerDocument==document)){if(l.nodeName.toLowerCase()!="td"){n=c.getAncestorByTagName(l,"td")}else{n=l}if(n&&((n.parentNode.parentNode==this._elTbody)||(n.parentNode.parentNode===null)||(n.parentNode.parentNode.nodeType===11))){return n}}else{if(i){var m,k;if(h.isString(i.columnKey)&&h.isString(i.recordId)){m=this.getRecord(i.recordId);var o=this.getColumn(i.columnKey);if(o){k=o.getKeyIndex()}}if(i.record&&i.column&&i.column.getKeyIndex){m=i.record;k=i.column.getKeyIndex()}var j=this.getTrEl(m);if((k!==null)&&j&&j.cells&&j.cells.length>0){return j.cells[k]||null}}}return null},getFirstTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getFirstTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[0]}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[0]}}}return null},getLastTdEl:function(j){var i=h.isValue(j)?this.getTrEl(j):this.getLastTrEl();if(i){if(i.cells&&i.cells.length>0){return i.cells[i.cells.length-1]}else{if(i.childNodes&&i.childNodes.length>0){return i.childNodes[i.childNodes.length-1]}}}return null},getNextTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(j.cells&&(j.cells.length)>0&&(k<j.cells.length-1)){return j.cells[k+1]}else{if(j.childNodes&&(j.childNodes.length)>0&&(k<j.childNodes.length-1)){return j.childNodes[k+1]}else{var l=this.getNextTrEl(j);if(l){return l.cells[0]}}}}return null},getPreviousTdEl:function(i){var m=this.getTdEl(i);if(m){var k=this.getCellIndex(m);var j=this.getTrEl(m);if(k>0){if(j.cells&&j.cells.length>0){return j.cells[k-1]}else{if(j.childNodes&&j.childNodes.length>0){return j.childNodes[k-1]}}}else{var l=this.getPreviousTrEl(j);if(l){return this.getLastTdEl(l)}}}return null},getAboveTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getPreviousTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null}}}}return null},getBelowTdEl:function(j,i){var m=this.getTdEl(j);if(m){var l=this.getNextTrEl(m,i);if(l){var k=this.getCellIndex(m);if(l.cells&&l.cells.length>0){return l.cells[k]?l.cells[k]:null}else{if(l.childNodes&&l.childNodes.length>0){return l.childNodes[k]?l.childNodes[k]:null}}}}return null},getThLinerEl:function(j){var i=this.getColumn(j);return(i)?i.getThLinerEl():null},getThEl:function(k){var l;if(k instanceof YAHOO.widget.Column){var j=k;l=j.getThEl();if(l){return l}}else{var i=c.get(k);if(i&&(i.ownerDocument==document)){if(i.nodeName.toLowerCase()!="th"){l=c.getAncestorByTagName(i,"th")}else{l=i}return l}}return null},getTrIndex:function(m){var i=this.getRecord(m),k=this.getRecordIndex(i),l;if(i){l=this.getTrEl(i);if(l){return l.sectionRowIndex}else{var j=this.get("paginator");if(j){return j.get("recordOffset")+k}else{return k}}}return null},load:function(i){i=i||{};(i.datasource||this._oDataSource).sendRequest(i.request||this.get("initialRequest"),i.callback||{success:this.onDataReturnInitializeTable,failure:this.onDataReturnInitializeTable,scope:this,argument:this.getState()})},initializeTable:function(){this._bInit=true;this._oRecordSet.reset();var i=this.get("paginator");if(i){i.set("totalRecords",0)}this._unselectAllTrEls();this._unselectAllTdEls();this._aSelections=null;this._oAnchorRecord=null;this._oAnchorCell=null;this.set("sortedBy",null)},_runRenderChain:function(){this._oChainRender.run()},_getViewRecords:function(){var i=this.get("paginator");if(i){return this._oRecordSet.getRecords(i.getStartIndex(),i.getRowsPerPage())}else{return this._oRecordSet.getRecords()}},render:function(){this._oChainRender.stop();this.fireEvent("beforeRenderEvent");var r,p,o,s,l=this._getViewRecords();var m=this._elTbody,q=this.get("renderLoopSize"),t=l.length;if(t>0){m.style.display="none";while(m.lastChild){m.removeChild(m.lastChild)}m.style.display="";this._oChainRender.add({method:function(u){if((this instanceof d)&&this._sId){var k=u.nCurrentRecord,w=((u.nCurrentRecord+u.nLoopLength)>t)?t:(u.nCurrentRecord+u.nLoopLength),j,v;m.style.display="none";for(;k<w;k++){j=c.get(l[k].getId());j=j||this._addTrEl(l[k]);v=m.childNodes[k]||null;m.insertBefore(j,v)}m.style.display="";u.nCurrentRecord=k}},scope:this,iterations:(q>0)?Math.ceil(t/q):1,argument:{nCurrentRecord:0,nLoopLength:(q>0)?q:t},timeout:(q>0)?0:-1});this._oChainRender.add({method:function(i){if((this instanceof d)&&this._sId){while(m.rows.length>t){m.removeChild(m.lastChild)}this._setFirstRow();this._setLastRow();this._setRowStripes();this._setSelections()}},scope:this,timeout:(q>0)?0:-1})}else{var n=m.rows.length;if(n>0){this._oChainRender.add({method:function(k){if((this instanceof d)&&this._sId){var j=k.nCurrent,v=k.nLoopLength,u=(j-v<0)?0:j-v;m.style.display="none";for(;j>u;j--){m.deleteRow(-1)}m.style.display="";k.nCurrent=j}},scope:this,iterations:(q>0)?Math.ceil(n/q):1,argument:{nCurrent:n,nLoopLength:(q>0)?q:n},timeout:(q>0)?0:-1})}}this._runRenderChain()},disable:function(){this._disabled=true;var i=this._elTable;var j=this._elMask;j.style.width=i.offsetWidth+"px";j.style.height=i.offsetHeight+"px";j.style.left=i.offsetLeft+"px";j.style.display="";this.fireEvent("disableEvent")},undisable:function(){this._disabled=false;this._elMask.style.display="none";this.fireEvent("undisableEvent")},isDisabled:function(){return this._disabled},destroy:function(){var k=this.toString();this._oChainRender.stop();this._destroyColumnHelpers();var m;for(var l=0,j=this._oColumnSet.flat.length;l<j;l++){m=this._oColumnSet.flat[l].editor;if(m&&m.destroy){m.destroy();this._oColumnSet.flat[l].editor=null}}this._destroyPaginator();this._oRecordSet.unsubscribeAll();this.unsubscribeAll();g.removeListener(document,"click",this._onDocumentClick);this._destroyContainerEl(this._elContainer);for(var n in this){if(h.hasOwnProperty(this,n)){this[n]=null}}d._nCurrentCount--;if(d._nCurrentCount<1){if(d._elDynStyleNode){document.getElementsByTagName("head")[0].removeChild(d._elDynStyleNode);d._elDynStyleNode=null}}},showTableMessage:function(j,i){var k=this._elMsgTd;if(h.isString(j)){k.firstChild.innerHTML=j}if(h.isString(i)){k.className=i}this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:j,className:i})},hideTableMessage:function(){if(this._elMsgTbody.style.display!="none"){this._elMsgTbody.style.display="none";this._elMsgTbody.parentNode.style.width="";this.fireEvent("tableMsgHideEvent")}},focus:function(){this.focusTbodyEl()},focusTheadEl:function(){this._focusEl(this._elThead)},focusTbodyEl:function(){this._focusEl(this._elTbody)},onShow:function(){this.validateColumnWidths();for(var m=this._oColumnSet.keys,l=0,j=m.length,k;l<j;l++){k=m[l];if(k._ddResizer){k._ddResizer.resetResizerEl()}}},getRecordIndex:function(l){var k;if(!h.isNumber(l)){if(l instanceof YAHOO.widget.Record){return this._oRecordSet.getRecordIndex(l)}else{var j=this.getTrEl(l);if(j){k=j.sectionRowIndex}}}else{k=l}if(h.isNumber(k)){var i=this.get("paginator");if(i){return i.get("recordOffset")+k}else{return k}}return null},getRecord:function(k){var j=this._oRecordSet.getRecord(k);if(!j){var i=this.getTrEl(k);if(i){j=this._oRecordSet.getRecord(i.id)}}if(j instanceof YAHOO.widget.Record){return this._oRecordSet.getRecord(j)}else{return null}},getColumn:function(m){var o=this._oColumnSet.getColumn(m);if(!o){var n=this.getTdEl(m);if(n){o=this._oColumnSet.getColumn(this.getCellIndex(n))}else{n=this.getThEl(m);if(n){var k=this._oColumnSet.flat;for(var l=0,j=k.length;l<j;l++){if(k[l].getThEl().id===n.id){o=k[l]}}}}}if(!o){}return o},getColumnById:function(i){return this._oColumnSet.getColumnById(i)},getColumnSortDir:function(k,l){if(k.sortOptions&&k.sortOptions.defaultDir){if(k.sortOptions.defaultDir=="asc"){k.sortOptions.defaultDir=d.CLASS_ASC}else{if(k.sortOptions.defaultDir=="desc"){k.sortOptions.defaultDir=d.CLASS_DESC}}}var j=(k.sortOptions&&k.sortOptions.defaultDir)?k.sortOptions.defaultDir:d.CLASS_ASC;var i=false;l=l||this.get("sortedBy");if(l&&(l.key===k.key)){i=true;if(l.dir){j=(l.dir===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC}else{j=(j===d.CLASS_ASC)?d.CLASS_DESC:d.CLASS_ASC}}return j},doBeforeSortColumn:function(j,i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true},sortColumn:function(m,j){if(m&&(m instanceof YAHOO.widget.Column)){if(!m.sortable){c.addClass(this.getThEl(m),d.CLASS_SORTABLE)}if(j&&(j!==d.CLASS_ASC)&&(j!==d.CLASS_DESC)){j=null}var n=j||this.getColumnSortDir(m);var l=this.get("sortedBy")||{};var t=(l.key===m.key)?true:false;var p=this.doBeforeSortColumn(m,n);if(p){if(this.get("dynamicData")){var s=this.getState();if(s.pagination){s.pagination.recordOffset=0}s.sortedBy={key:m.key,dir:n};var k=this.get("generateRequest")(s,this);this.unselectAllRows();this.unselectAllCells();var r={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:s,scope:this};this._oDataSource.sendRequest(k,r)}else{var i=(m.sortOptions&&h.isFunction(m.sortOptions.sortFunction))?m.sortOptions.sortFunction:null;if(!t||j||i){i=i||this.get("sortFunction");var q=(m.sortOptions&&m.sortOptions.field)?m.sortOptions.field:m.field;this._oRecordSet.sortRecords(i,((n==d.CLASS_DESC)?true:false),q)}else{this._oRecordSet.reverseRecords()}var o=this.get("paginator");if(o){o.setPage(1,true)}this.render();this.set("sortedBy",{key:m.key,dir:n,column:m})}this.fireEvent("columnSortEvent",{column:m,dir:n});return}}},setColumnWidth:function(j,i){if(!(j instanceof YAHOO.widget.Column)){j=this.getColumn(j)}if(j){if(h.isNumber(i)){i=(i>j.minWidth)?i:j.minWidth;j.width=i;this._setColumnWidth(j,i+"px");this.fireEvent("columnSetWidthEvent",{column:j,width:i})}else{if(i===null){j.width=i;this._setColumnWidth(j,"auto");this.validateColumnWidths(j);this.fireEvent("columnUnsetWidthEvent",{column:j})}}this._clearTrTemplateEl()}else{}},_setColumnWidth:function(j,i,k){if(j&&(j.getKeyIndex()!==null)){k=k||(((i==="")||(i==="auto"))?"visible":"hidden");if(!d._bDynStylesFallback){this._setColumnWidthDynStyles(j,i,k)}else{this._setColumnWidthDynFunction(j,i,k)}}else{}},_setColumnWidthDynStyles:function(m,l,n){var j=d._elDynStyleNode,k;if(!j){j=document.createElement("style");j.type="text/css";j=document.getElementsByTagName("head").item(0).appendChild(j);d._elDynStyleNode=j}if(j){var i="."+this.getId()+"-col-"+m.getSanitizedKey()+" ."+d.CLASS_LINER;if(this._elTbody){this._elTbody.style.display="none"}k=d._oDynStyles[i];if(!k){if(j.styleSheet&&j.styleSheet.addRule){j.styleSheet.addRule(i,"overflow:"+n);j.styleSheet.addRule(i,"width:"+l);k=j.styleSheet.rules[j.styleSheet.rules.length-1];d._oDynStyles[i]=k}else{if(j.sheet&&j.sheet.insertRule){j.sheet.insertRule(i+" {overflow:"+n+";width:"+l+";}",j.sheet.cssRules.length);k=j.sheet.cssRules[j.sheet.cssRules.length-1];d._oDynStyles[i]=k}}}else{k.style.overflow=n;k.style.width=l}if(this._elTbody){this._elTbody.style.display=""}}if(!k){d._bDynStylesFallback=true;this._setColumnWidthDynFunction(m,l)}},_setColumnWidthDynFunction:function(r,m,s){if(m=="auto"){m=""}var l=this._elTbody?this._elTbody.rows.length:0;if(!this._aDynFunctions[l]){var q,p,o;var t=["var colIdx=oColumn.getKeyIndex();","oColumn.getThLinerEl().style.overflow="];for(q=l-1,p=2;q>=0;--q){t[p++]="this._elTbody.rows[";t[p++]=q;t[p++]="].cells[colIdx].firstChild.style.overflow="}t[p]="sOverflow;";t[p+1]="oColumn.getThLinerEl().style.width=";for(q=l-1,o=p+2;q>=0;--q){t[o++]="this._elTbody.rows[";t[o++]=q;t[o++]="].cells[colIdx].firstChild.style.width="}t[o]="sWidth;";this._aDynFunctions[l]=new Function("oColumn","sWidth","sOverflow",t.join(""))}var n=this._aDynFunctions[l];if(n){n.call(this,r,m,s)}},validateColumnWidths:function(o){var l=this._elColgroup;var q=l.cloneNode(true);var p=false;var n=this._oColumnSet.keys;var k;if(o&&!o.hidden&&!o.width&&(o.getKeyIndex()!==null)){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[o.getKeyIndex()].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden")}}}else{for(var m=0,j=n.length;m<j;m++){o=n[m];if(!o.hidden&&!o.width){k=o.getThLinerEl();if((o.minWidth>0)&&(k.offsetWidth<o.minWidth)){q.childNodes[m].style.width=o.minWidth+(parseInt(c.getStyle(k,"paddingLeft"),10)|0)+(parseInt(c.getStyle(k,"paddingRight"),10)|0)+"px";p=true}else{if((o.maxAutoWidth>0)&&(k.offsetWidth>o.maxAutoWidth)){this._setColumnWidth(o,o.maxAutoWidth+"px","hidden")}}}}}if(p){l.parentNode.replaceChild(q,l);this._elColgroup=q}},_clearMinWidth:function(i){if(i.getKeyIndex()!==null){this._elColgroup.childNodes[i.getKeyIndex()].style.width=""}},_restoreMinWidth:function(i){if(i.minWidth&&(i.getKeyIndex()!==null)){this._elColgroup.childNodes[i.getKeyIndex()].style.width=i.minWidth+"px"}},hideColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r)}if(r&&!r.hidden&&r.getTreeIndex()!==null){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=true;c.addClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._clearMinWidth(r);for(var p=0;p<n;p++){c.addClass(o[p].cells[k],d.CLASS_HIDDEN)}}this.fireEvent("columnHideEvent",{column:t})}this._repaintOpera();this._clearTrTemplateEl()}else{}},showColumn:function(r){if(!(r instanceof YAHOO.widget.Column)){r=this.getColumn(r)}if(r&&r.hidden&&(r.getTreeIndex()!==null)){var o=this.getTbodyEl().rows;var n=o.length;var m=this._oColumnSet.getDescendants(r);for(var q=0,s=m.length;q<s;q++){var t=m[q];t.hidden=false;c.removeClass(t.getThEl(),d.CLASS_HIDDEN);var k=t.getKeyIndex();if(k!==null){this._restoreMinWidth(r);for(var p=0;p<n;p++){c.removeClass(o[p].cells[k],d.CLASS_HIDDEN)}}this.fireEvent("columnShowEvent",{column:t})}this._clearTrTemplateEl()}else{}},removeColumn:function(p){if(!(p instanceof YAHOO.widget.Column)){p=this.getColumn(p)}if(p){var m=p.getTreeIndex();if(m!==null){var o,r,q=p.getKeyIndex();if(q===null){var u=[];var j=this._oColumnSet.getDescendants(p);for(o=0,r=j.length;o<r;o++){var s=j[o].getKeyIndex();if(s!==null){u[u.length]=s}}if(u.length>0){q=u}}else{q=[q]}if(q!==null){q.sort(function(v,i){return YAHOO.util.Sort.compare(v,i)});this._destroyTheadEl();var k=this._oColumnSet.getDefinitions();p=k.splice(m,1)[0];this._initColumnSet(k);this._initTheadEl();for(o=q.length-1;o>-1;o--){this._removeColgroupColEl(q[o])}var t=this._elTbody.rows;if(t.length>0){var n=this.get("renderLoopSize"),l=t.length;this._oChainRender.add({method:function(y){if((this instanceof d)&&this._sId){var x=y.nCurrentRow,v=n>0?Math.min(x+n,t.length):t.length,z=y.aIndexes,w;for(;x<v;++x){for(w=z.length-1;w>-1;w--){t[x].removeChild(t[x].childNodes[z[w]])}}y.nCurrentRow=x}},iterations:(n>0)?Math.ceil(l/n):1,argument:{nCurrentRow:0,aIndexes:q},scope:this,timeout:(n>0)?0:-1});this._runRenderChain()}this.fireEvent("columnRemoveEvent",{column:p});return p}}}},insertColumn:function(r,s){if(r instanceof YAHOO.widget.Column){r=r.getDefinition()}else{if(r.constructor!==Object){return}}var x=this._oColumnSet;if(!h.isValue(s)||!h.isNumber(s)){s=x.tree[0].length}this._destroyTheadEl();var z=this._oColumnSet.getDefinitions();z.splice(s,0,r);this._initColumnSet(z);this._initTheadEl();x=this._oColumnSet;var n=x.tree[0][s];var p,t,w=[];var l=x.getDescendants(n);for(p=0,t=l.length;p<t;p++){var u=l[p].getKeyIndex();if(u!==null){w[w.length]=u}}if(w.length>0){var y=w.sort(function(A,i){return YAHOO.util.Sort.compare(A,i)})[0];for(p=w.length-1;p>-1;p--){this._insertColgroupColEl(w[p])}var v=this._elTbody.rows;if(v.length>0){var o=this.get("renderLoopSize"),m=v.length;var k=[],q;for(p=0,t=w.length;p<t;p++){var j=w[p];q=this._getTrTemplateEl().childNodes[p].cloneNode(true);q=this._formatTdEl(this._oColumnSet.keys[j],q,j,(j===this._oColumnSet.keys.length-1));k[j]=q}this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F=D.descKeyIndexes,A=o>0?Math.min(C+o,v.length):v.length,E;for(;C<A;++C){E=v[C].childNodes[y]||null;for(B=F.length-1;B>-1;B--){v[C].insertBefore(D.aTdTemplates[F[B]].cloneNode(true),E)}}D.nCurrentRow=C}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aTdTemplates:k,descKeyIndexes:w},scope:this,timeout:(o>0)?0:-1});this._runRenderChain()}this.fireEvent("columnInsertEvent",{column:r,index:s});return n}},reorderColumn:function(q,r){if(!(q instanceof YAHOO.widget.Column)){q=this.getColumn(q)}if(q&&YAHOO.lang.isNumber(r)){var z=q.getTreeIndex();if((z!==null)&&(z!==r)){var p,s,l=q.getKeyIndex(),k,v=[],t;if(l===null){k=this._oColumnSet.getDescendants(q);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t}}if(v.length>0){l=v}}else{l=[l]}if(l!==null){l.sort(function(A,i){return YAHOO.util.Sort.compare(A,i)});this._destroyTheadEl();var w=this._oColumnSet.getDefinitions();var j=w.splice(z,1)[0];w.splice(r,0,j);this._initColumnSet(w);this._initTheadEl();var n=this._oColumnSet.tree[0][r];var y=n.getKeyIndex();if(y===null){v=[];k=this._oColumnSet.getDescendants(n);for(p=0,s=k.length;p<s;p++){t=k[p].getKeyIndex();if(t!==null){v[v.length]=t}}if(v.length>0){y=v}}else{y=[y]}var x=y.sort(function(A,i){return YAHOO.util.Sort.compare(A,i)})[0];this._reorderColgroupColEl(l,x);var u=this._elTbody.rows;if(u.length>0){var o=this.get("renderLoopSize"),m=u.length;this._oChainRender.add({method:function(D){if((this instanceof d)&&this._sId){var C=D.nCurrentRow,B,F,E,A=o>0?Math.min(C+o,u.length):u.length,H=D.aIndexes,G;for(;C<A;++C){F=[];G=u[C];for(B=H.length-1;B>-1;B--){F.push(G.removeChild(G.childNodes[H[B]]))}E=G.childNodes[x]||null;for(B=F.length-1;B>-1;B--){G.insertBefore(F[B],E)}}D.nCurrentRow=C}},iterations:(o>0)?Math.ceil(m/o):1,argument:{nCurrentRow:0,aIndexes:l},scope:this,timeout:(o>0)?0:-1});this._runRenderChain()}this.fireEvent("columnReorderEvent",{column:n,oldIndex:z});return n}}}},selectColumn:function(k){k=this.getColumn(k);if(k&&!k.selected){if(k.getKeyIndex()!==null){k.selected=true;var l=k.getThEl();c.addClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.addClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED)}m.rowIndex++},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnSelectEvent",{column:k})}else{}}},unselectColumn:function(k){k=this.getColumn(k);if(k&&k.selected){if(k.getKeyIndex()!==null){k.selected=false;var l=k.getThEl();c.removeClass(l,d.CLASS_SELECTED);var j=this.getTbodyEl().rows;var i=this._oChainRender;i.add({method:function(m){if((this instanceof d)&&this._sId&&j[m.rowIndex]&&j[m.rowIndex].cells[m.cellIndex]){c.removeClass(j[m.rowIndex].cells[m.cellIndex],d.CLASS_SELECTED)}m.rowIndex++},scope:this,iterations:j.length,argument:{rowIndex:0,cellIndex:k.getKeyIndex()}});this._clearTrTemplateEl();this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnselectEvent",{column:k})}else{}}},getSelectedColumns:function(n){var k=[];var l=this._oColumnSet.keys;for(var m=0,j=l.length;m<j;m++){if(l[m].selected){k[k.length]=l[m]}}return k},highlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.addClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.addClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED)}n.rowIndex++},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnHighlightEvent",{column:l})}else{}},unhighlightColumn:function(i){var l=this.getColumn(i);if(l&&(l.getKeyIndex()!==null)){var m=l.getThEl();c.removeClass(m,d.CLASS_HIGHLIGHTED);var k=this.getTbodyEl().rows;var j=this._oChainRender;j.add({method:function(n){if((this instanceof d)&&this._sId&&k[n.rowIndex]&&k[n.rowIndex].cells[n.cellIndex]){c.removeClass(k[n.rowIndex].cells[n.cellIndex],d.CLASS_HIGHLIGHTED)}n.rowIndex++},scope:this,iterations:k.length,argument:{rowIndex:0,cellIndex:l.getKeyIndex()},timeout:-1});this._elTbody.style.display="none";this._runRenderChain();this._elTbody.style.display="";this.fireEvent("columnUnhighlightEvent",{column:l})}else{}},addRow:function(o,k){if(h.isNumber(k)&&(k<0||k>this._oRecordSet.getLength())){return}if(o&&h.isObject(o)){var m=this._oRecordSet.addRecord(o,k);if(m){var i;var j=this.get("paginator");if(j){var n=j.get("totalRecords");if(n!==e.Paginator.VALUE_UNLIMITED){j.set("totalRecords",n+1)}i=this.getRecordIndex(m);var l=(j.getPageRecords())[1];if(i<=l){this.render()}this.fireEvent("rowAddEvent",{record:m});return}else{i=this.getRecordIndex(m);if(h.isNumber(i)){this._oChainRender.add({method:function(r){if((this instanceof d)&&this._sId){var s=r.record;var p=r.recIndex;var t=this._addTrEl(s);if(t){var q=(this._elTbody.rows[p])?this._elTbody.rows[p]:null;this._elTbody.insertBefore(t,q);if(p===0){this._setFirstRow()}if(q===null){this._setLastRow()}this._setRowStripes();this.hideTableMessage();this.fireEvent("rowAddEvent",{record:s})}}},argument:{record:m,recIndex:i},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return}}}}},addRows:function(k,n){if(h.isNumber(n)&&(n<0||n>this._oRecordSet.getLength())){return}if(h.isArray(k)){var o=this._oRecordSet.addRecords(k,n);if(o){var s=this.getRecordIndex(o[0]);var r=this.get("paginator");if(r){var p=r.get("totalRecords");if(p!==e.Paginator.VALUE_UNLIMITED){r.set("totalRecords",p+o.length)}var q=(r.getPageRecords())[1];if(s<=q){this.render()}this.fireEvent("rowsAddEvent",{records:o});return}else{var m=this.get("renderLoopSize");var j=s+k.length;var i=(j-s);var l=(s>=this._elTbody.rows.length);this._oChainRender.add({method:function(x){if((this instanceof d)&&this._sId){var y=x.aRecords,w=x.nCurrentRow,v=x.nCurrentRecord,t=m>0?Math.min(w+m,j):j,z=document.createDocumentFragment(),u=(this._elTbody.rows[w])?this._elTbody.rows[w]:null;for(;w<t;w++,v++){z.appendChild(this._addTrEl(y[v]))}this._elTbody.insertBefore(z,u);x.nCurrentRow=w;x.nCurrentRecord=v}},iterations:(m>0)?Math.ceil(j/m):1,argument:{nCurrentRow:s,nCurrentRecord:0,aRecords:o},scope:this,timeout:(m>0)?0:-1});this._oChainRender.add({method:function(u){var t=u.recIndex;if(t===0){this._setFirstRow()}if(u.isLast){this._setLastRow()}this._setRowStripes();this.fireEvent("rowsAddEvent",{records:o})},argument:{recIndex:s,isLast:l},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return}}}},updateRow:function(u,k){var r=u;if(!h.isNumber(r)){r=this.getRecordIndex(u)}if(h.isNumber(r)&&(r>=0)){var s=this._oRecordSet,q=s.getRecord(r);if(q){var o=this._oRecordSet.setRecord(k,r),j=this.getTrEl(q),p=q?q.getData():null;if(o){var t=this._aSelections||[],n=0,l=q.getId(),m=o.getId();for(;n<t.length;n++){if((t[n]===l)){t[n]=m}else{if(t[n].recordId===l){t[n].recordId=m}}}if(this._oAnchorRecord&&this._oAnchorRecord.getId()===l){this._oAnchorRecord=o}if(this._oAnchorCell&&this._oAnchorCell.record.getId()===l){this._oAnchorCell.record=o}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var v=this.get("paginator");if(v){var i=(v.getPageRecords())[0],w=(v.getPageRecords())[1];if((r>=i)||(r<=w)){this.render()}}else{if(j){this._updateTrEl(j,o)}else{this.getTbodyEl().appendChild(this._addTrEl(o))}}this.fireEvent("rowUpdateEvent",{record:o,oldData:p})}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return}}}return},updateRows:function(A,m){if(h.isArray(m)){var s=A,l=this._oRecordSet,o=l.getLength();if(!h.isNumber(A)){s=this.getRecordIndex(A)}if(h.isNumber(s)&&(s>=0)&&(s<l.getLength())){var E=s+m.length,B=l.getRecords(s,m.length),G=l.setRecords(m,s);if(G){var t=this._aSelections||[],D=0,C,u,x,z,y=this._oAnchorRecord?this._oAnchorRecord.getId():null,n=this._oAnchorCell?this._oAnchorCell.record.getId():null;for(;D<B.length;D++){z=B[D].getId();u=G[D];x=u.getId();for(C=0;C<t.length;C++){if((t[C]===z)){t[C]=x}else{if(t[C].recordId===z){t[C].recordId=x}}}if(y&&y===z){this._oAnchorRecord=u}if(n&&n===z){this._oAnchorCell.record=u}}var F=this.get("paginator");if(F){var r=(F.getPageRecords())[0],p=(F.getPageRecords())[1];if((s>=r)||(E<=p)){this.render()}this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B});return}else{var k=this.get("renderLoopSize"),v=m.length,w=(E>=o),q=(E>o);this._oChainRender.add({method:function(K){if((this instanceof d)&&this._sId){var L=K.aRecords,J=K.nCurrentRow,I=K.nDataPointer,H=k>0?Math.min(J+k,s+L.length):s+L.length;for(;J<H;J++,I++){if(q&&(J>=o)){this._elTbody.appendChild(this._addTrEl(L[I]))}else{this._updateTrEl(this._elTbody.rows[J],L[I])}}K.nCurrentRow=J;K.nDataPointer=I}},iterations:(k>0)?Math.ceil(v/k):1,argument:{nCurrentRow:s,aRecords:G,nDataPointer:0,isAdding:q},scope:this,timeout:(k>0)?0:-1});this._oChainRender.add({method:function(j){var i=j.recIndex;if(i===0){this._setFirstRow()}if(j.isLast){this._setLastRow()}this._setRowStripes();this.fireEvent("rowsAddEvent",{newRecords:G,oldRecords:B})},argument:{recIndex:s,isLast:w},scope:this,timeout:-1});this._runRenderChain();this.hideTableMessage();return}}}}},deleteRow:function(s){var k=(h.isNumber(s))?s:this.getRecordIndex(s);if(h.isNumber(k)){var t=this.getRecord(k);if(t){var m=this.getTrIndex(k);var p=t.getId();var r=this._aSelections||[];for(var n=r.length-1;n>-1;n--){if((h.isString(r[n])&&(r[n]===p))||(h.isObject(r[n])&&(r[n].recordId===p))){r.splice(n,1)}}var l=this._oRecordSet.deleteRecord(k);if(l){var q=this.get("paginator");if(q){var o=q.get("totalRecords"),i=q.getPageRecords();if(o!==e.Paginator.VALUE_UNLIMITED){q.set("totalRecords",o-1)}if(!i||k<=i[1]){this.render()}this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m})}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain()}else{if(h.isNumber(m)){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){var j=(k===this._oRecordSet.getLength());this._deleteTrEl(m);if(this._elTbody.rows.length>0){if(m===0){this._setFirstRow()}if(j){this._setLastRow()}if(m!=this._elTbody.rows.length){this._setRowStripes(m)}}this.fireEvent("rowDeleteEvent",{recordIndex:k,oldData:l,trElIndex:m})}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});this._runRenderChain();return}}}}}return null},deleteRows:function(y,s){var l=(h.isNumber(y))?y:this.getRecordIndex(y);if(h.isNumber(l)){var z=this.getRecord(l);if(z){var m=this.getTrIndex(l);var u=z.getId();var x=this._aSelections||[];for(var q=x.length-1;q>-1;q--){if((h.isString(x[q])&&(x[q]===u))||(h.isObject(x[q])&&(x[q].recordId===u))){x.splice(q,1)}}var n=l;var w=l;if(s&&h.isNumber(s)){n=(s>0)?l+s-1:l;w=(s>0)?l:l+s+1;s=(s>0)?s:s*-1;if(w<0){w=0;s=n-w+1}}else{s=1}var p=this._oRecordSet.deleteRecords(w,s);if(p){var v=this.get("paginator"),r=this.get("renderLoopSize");if(v){var t=v.get("totalRecords"),k=v.getPageRecords();if(t!==e.Paginator.VALUE_UNLIMITED){v.set("totalRecords",t-p.length)}if(!k||w<=k[1]){this.render()}this._oChainRender.add({method:function(j){if((this instanceof d)&&this._sId){this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s})}},scope:this,timeout:(r>0)?0:-1});this._runRenderChain();return}else{if(h.isNumber(m)){var o=w;var i=s;this._oChainRender.add({method:function(B){if((this instanceof d)&&this._sId){var A=B.nCurrentRow,j=(r>0)?(Math.max(A-r,o)-1):o-1;for(;A>j;--A){this._deleteTrEl(A)}B.nCurrentRow=A}},iterations:(r>0)?Math.ceil(s/r):1,argument:{nCurrentRow:n},scope:this,timeout:(r>0)?0:-1});this._oChainRender.add({method:function(){if(this._elTbody.rows.length>0){this._setFirstRow();this._setLastRow();this._setRowStripes()}this.fireEvent("rowsDeleteEvent",{recordIndex:w,oldData:p,count:s})},scope:this,timeout:-1});this._runRenderChain();return}}}}}return null},formatCell:function(j,l,m){if(!l){l=this.getRecord(j)}if(!m){m=this.getColumn(this.getCellIndex(j.parentNode))}if(l&&m){var i=m.field;var n=l.getData(i);var k=typeof m.formatter==="function"?m.formatter:d.Formatter[m.formatter+""]||d.Formatter.defaultFormatter;if(k){k.call(this,j,l,m,n)}else{j.innerHTML=n}this.fireEvent("cellFormatEvent",{record:l,column:m,key:m.key,el:j})}else{}},updateCell:function(k,m,o,j){m=(m instanceof YAHOO.widget.Column)?m:this.getColumn(m);if(m&&m.getField()&&(k instanceof YAHOO.widget.Record)){var l=m.getField(),n=k.getData(l);this._oRecordSet.updateRecordValue(k,l,o);var i=this.getTdEl({record:k,column:m});if(i){this._oChainRender.add({method:function(){if((this instanceof d)&&this._sId){this.formatCell(i.firstChild,k,m);this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n})}},scope:this,timeout:(this.get("renderLoopSize")>0)?0:-1});if(!j){this._runRenderChain()}}else{this.fireEvent("cellUpdateEvent",{record:k,column:m,oldData:n})}}},_updatePaginator:function(j){var i=this.get("paginator");if(i&&j!==i){i.unsubscribe("changeRequest",this.onPaginatorChangeRequest,this,true)}if(j){j.subscribe("changeRequest",this.onPaginatorChangeRequest,this,true)}},_handlePaginatorChange:function(l){if(l.prevValue===l.newValue){return}var n=l.newValue,m=l.prevValue,k=this._defaultPaginatorContainers();if(m){if(m.getContainerNodes()[0]==k[0]){m.set("containers",[])}m.destroy();if(k[0]){if(n&&!n.getContainerNodes().length){n.set("containers",k)}else{for(var j=k.length-1;j>=0;--j){if(k[j]){k[j].parentNode.removeChild(k[j])}}}}}if(!this._bInit){this.render()}if(n){this.renderPaginator()}},_defaultPaginatorContainers:function(l){var j=this._sId+"-paginator0",k=this._sId+"-paginator1",i=c.get(j),m=c.get(k);if(l&&(!i||!m)){if(!i){i=document.createElement("div");i.id=j;c.addClass(i,d.CLASS_PAGINATOR);this._elContainer.insertBefore(i,this._elContainer.firstChild)}if(!m){m=document.createElement("div");m.id=k;c.addClass(m,d.CLASS_PAGINATOR);this._elContainer.appendChild(m)}}return[i,m]},_destroyPaginator:function(){var i=this.get("paginator");if(i){i.destroy()}},renderPaginator:function(){var i=this.get("paginator");if(!i){return}if(!i.getContainerNodes().length){i.set("containers",this._defaultPaginatorContainers(true))}i.render()},doBeforePaginatorChange:function(i){this.showTableMessage(this.get("MSG_LOADING"),d.CLASS_LOADING);return true},onPaginatorChangeRequest:function(l){var j=this.doBeforePaginatorChange(l);if(j){if(this.get("dynamicData")){var i=this.getState();i.pagination=l;var k=this.get("generateRequest")(i,this);this.unselectAllRows();this.unselectAllCells();var m={success:this.onDataReturnSetRows,failure:this.onDataReturnSetRows,argument:i,scope:this};this._oDataSource.sendRequest(k,m)}else{l.paginator.setStartIndex(l.recordOffset,true);l.paginator.setRowsPerPage(l.rowsPerPage,true);this.render()}}else{}},_elLastHighlightedTd:null,_aSelections:null,_oAnchorRecord:null,_oAnchorCell:null,_unselectAllTrEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody);c.removeClass(i,d.CLASS_SELECTED)},_getSelectionTrigger:function(){var l=this.get("selectionMode");var k={};var o,i,j,n,m;if((l=="cellblock")||(l=="cellrange")||(l=="singlecell")){o=this.getLastSelectedCell();if(!o){return null}else{i=this.getRecord(o.recordId);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null}else{k.record=i;k.recordIndex=j;k.el=this.getTdEl(o);k.trIndex=m;k.column=this.getColumn(o.columnKey);k.colKeyIndex=k.column.getKeyIndex();k.cell=o;return k}}}else{i=this.getLastSelectedRecord();if(!i){return null}else{i=this.getRecord(i);j=this.getRecordIndex(i);n=this.getTrEl(i);m=this.getTrIndex(n);if(m===null){return null}else{k.record=i;k.recordIndex=j;k.el=n;k.trIndex=m;return k}}}},_getSelectionAnchor:function(k){var j=this.get("selectionMode");var l={};var m,o,i;if((j=="cellblock")||(j=="cellrange")||(j=="singlecell")){var n=this._oAnchorCell;if(!n){if(k){n=this._oAnchorCell=k.cell}else{return null}}m=this._oAnchorCell.record;o=this._oRecordSet.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0}else{i=this.getRecordIndex(this.getLastTrEl())}}l.record=m;l.recordIndex=o;l.trIndex=i;l.column=this._oAnchorCell.column;l.colKeyIndex=l.column.getKeyIndex();l.cell=n;return l}else{m=this._oAnchorRecord;if(!m){if(k){m=this._oAnchorRecord=k.record}else{return null}}o=this.getRecordIndex(m);i=this.getTrIndex(m);if(i===null){if(o<this.getRecordIndex(this.getFirstTrEl())){i=0}else{i=this.getRecordIndex(this.getLastTrEl())}}l.record=m;l.recordIndex=o;l.trIndex=i;return l}},_handleStandardSelectionByMouse:function(k){var j=k.target;var m=this.getTrEl(j);if(m){var p=k.event;var s=p.shiftKey;var o=p.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&p.metaKey);var r=this.getRecord(m);var l=this._oRecordSet.getRecordIndex(r);var q=this._getSelectionAnchor();var n;if(s&&o){if(q){if(this.isSelected(q.record)){if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l;n++){if(!this.isSelected(n)){this.selectRow(n)}}}else{for(n=q.recordIndex-1;n>=l;n--){if(!this.isSelected(n)){this.selectRow(n)}}}}else{if(q.recordIndex<l){for(n=q.recordIndex+1;n<=l-1;n++){if(this.isSelected(n)){this.unselectRow(n)}}}else{for(n=l+1;n<=q.recordIndex-1;n++){if(this.isSelected(n)){this.unselectRow(n)}}}this.selectRow(r)}}else{this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r)}else{this.selectRow(r)}}}else{if(s){this.unselectAllRows();if(q){if(q.recordIndex<l){for(n=q.recordIndex;n<=l;n++){this.selectRow(n)}}else{for(n=q.recordIndex;n>=l;n--){this.selectRow(n)}}}else{this._oAnchorRecord=r;this.selectRow(r)}}else{if(o){this._oAnchorRecord=r;if(this.isSelected(r)){this.unselectRow(r)}else{this.selectRow(r)}}else{this._handleSingleSelectionByMouse(k);return}}}}},_handleStandardSelectionByKey:function(m){var i=g.getCharCode(m);if((i==38)||(i==40)){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null}g.stopEvent(m);var l=this._getSelectionAnchor(j);if(k){if((i==40)&&(l.recordIndex<=j.trIndex)){this.selectRow(this.getNextTrEl(j.el))}else{if((i==38)&&(l.recordIndex>=j.trIndex)){this.selectRow(this.getPreviousTrEl(j.el))}else{this.unselectRow(j.el)}}}else{this._handleSingleSelectionByKey(m)}}},_handleSingleSelectionByMouse:function(k){var l=k.target;var j=this.getTrEl(l);if(j){var i=this.getRecord(j);this._oAnchorRecord=i;this.unselectAllRows();this.selectRow(i)}},_handleSingleSelectionByKey:function(l){var i=g.getCharCode(l);if((i==38)||(i==40)){var j=this._getSelectionTrigger();if(!j){return null}g.stopEvent(l);var k;if(i==38){k=this.getPreviousTrEl(j.el);if(k===null){k=this.getFirstTrEl()}}else{if(i==40){k=this.getNextTrEl(j.el);if(k===null){k=this.getLastTrEl()}}}this.unselectAllRows();this.selectRow(k);this._oAnchorRecord=this.getRecord(k)}},_handleCellBlockSelectionByMouse:function(A){var B=A.target;var l=this.getTdEl(B);if(l){var z=A.event;var q=z.shiftKey;var m=z.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&z.metaKey);var s=this.getTrEl(l);var r=this.getTrIndex(s);var v=this.getColumn(l);var w=v.getKeyIndex();var u=this.getRecord(s);var D=this._oRecordSet.getRecordIndex(u);var p={record:u,column:v};var t=this._getSelectionAnchor();var o=this.getTbodyEl().rows;var n,k,C,y,x;if(q&&m){if(t){if(this.isSelected(t.cell)){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<=w;y++){this.selectCell(s.cells[y])}}else{if(w<t.colKeyIndex){for(y=w;y<t.colKeyIndex;y++){this.selectCell(s.cells[y])}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x])}}}else{n=Math.min(t.trIndex,w);k=Math.max(t.trIndex,w);for(y=t.trIndex;y>=r;y--){for(x=k;x>=n;x--){this.selectCell(o[y].cells[x])}}}}}else{if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex+1;y<w;y++){this.unselectCell(s.cells[y])}}else{if(w<t.colKeyIndex){for(y=w+1;y<t.colKeyIndex;y++){this.unselectCell(s.cells[y])}}}}if(t.recordIndex<D){for(y=t.trIndex;y<=r;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex===t.trIndex){if(x>t.colKeyIndex){this.unselectCell(C.cells[x])}}else{if(C.sectionRowIndex===r){if(x<w){this.unselectCell(C.cells[x])}}else{this.unselectCell(C.cells[x])}}}}}else{for(y=r;y<=t.trIndex;y++){C=o[y];for(x=0;x<C.cells.length;x++){if(C.sectionRowIndex==r){if(x>w){this.unselectCell(C.cells[x])}}else{if(C.sectionRowIndex==t.trIndex){if(x<t.colKeyIndex){this.unselectCell(C.cells[x])}}else{this.unselectCell(C.cells[x])}}}}}this.selectCell(l)}}else{this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p)}else{this.selectCell(p)}}}else{if(q){this.unselectAllCells();if(t){if(t.recordIndex===D){if(t.colKeyIndex<w){for(y=t.colKeyIndex;y<=w;y++){this.selectCell(s.cells[y])}}else{if(w<t.colKeyIndex){for(y=w;y<=t.colKeyIndex;y++){this.selectCell(s.cells[y])}}}}else{if(t.recordIndex<D){n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=t.trIndex;y<=r;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x])}}}else{n=Math.min(t.colKeyIndex,w);k=Math.max(t.colKeyIndex,w);for(y=r;y<=t.trIndex;y++){for(x=n;x<=k;x++){this.selectCell(o[y].cells[x])}}}}}else{this._oAnchorCell=p;this.selectCell(p)}}else{if(m){this._oAnchorCell=p;if(this.isSelected(p)){this.unselectCell(p)}else{this.selectCell(p)}}else{this._handleSingleCellSelectionByMouse(A)}}}}},_handleCellBlockSelectionByKey:function(o){var j=g.getCharCode(o);var t=o.shiftKey;if((j==9)||!t){this._handleSingleCellSelectionByKey(o);return}if((j>36)&&(j<41)){var u=this._getSelectionTrigger();if(!u){return null}g.stopEvent(o);var r=this._getSelectionAnchor(u);var k,s,l,q,m;var p=this.getTbodyEl().rows;var n=u.el.parentNode;if(j==40){if(r.recordIndex<=u.recordIndex){m=this.getNextTrEl(u.el);if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q)}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q)}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k])}}}else{if(j==38){if(r.recordIndex>=u.recordIndex){m=this.getPreviousTrEl(u.el);if(m){s=r.colKeyIndex;l=u.colKeyIndex;if(s>l){for(k=s;k>=l;k--){q=m.cells[k];this.selectCell(q)}}else{for(k=s;k<=l;k++){q=m.cells[k];this.selectCell(q)}}}}else{s=Math.min(r.colKeyIndex,u.colKeyIndex);l=Math.max(r.colKeyIndex,u.colKeyIndex);for(k=s;k<=l;k++){this.unselectCell(n.cells[k])}}}else{if(j==39){if(r.colKeyIndex<=u.colKeyIndex){if(u.colKeyIndex<n.cells.length-1){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q)}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex+1];this.selectCell(q)}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex])}}}else{if(j==37){if(r.colKeyIndex>=u.colKeyIndex){if(u.colKeyIndex>0){s=r.trIndex;l=u.trIndex;if(s>l){for(k=s;k>=l;k--){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q)}}else{for(k=s;k<=l;k++){q=p[k].cells[u.colKeyIndex-1];this.selectCell(q)}}}}else{s=Math.min(r.trIndex,u.trIndex);l=Math.max(r.trIndex,u.trIndex);for(k=s;k<=l;k++){this.unselectCell(p[k].cells[u.colKeyIndex])}}}}}}}},_handleCellRangeSelectionByMouse:function(y){var z=y.target;var k=this.getTdEl(z);if(k){var x=y.event;var o=x.shiftKey;var l=x.ctrlKey||((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&x.metaKey);var q=this.getTrEl(k);var p=this.getTrIndex(q);var t=this.getColumn(k);var u=t.getKeyIndex();var s=this.getRecord(q);var B=this._oRecordSet.getRecordIndex(s);var n={record:s,column:t};var r=this._getSelectionAnchor();var m=this.getTbodyEl().rows;var A,w,v;if(o&&l){if(r){if(this.isSelected(r.cell)){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<=u;w++){this.selectCell(q.cells[w])}}else{if(u<r.colKeyIndex){for(w=u;w<r.colKeyIndex;w++){this.selectCell(q.cells[w])}}}}else{if(r.recordIndex<B){for(w=r.colKeyIndex+1;w<q.cells.length;w++){this.selectCell(q.cells[w])}for(w=r.trIndex+1;w<p;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v])}}for(w=0;w<=u;w++){this.selectCell(q.cells[w])}}else{for(w=u;w<q.cells.length;w++){this.selectCell(q.cells[w])}for(w=p+1;w<r.trIndex;w++){for(v=0;v<m[w].cells.length;v++){this.selectCell(m[w].cells[v])}}for(w=0;w<r.colKeyIndex;w++){this.selectCell(q.cells[w])}}}}else{if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex+1;w<u;w++){this.unselectCell(q.cells[w])}}else{if(u<r.colKeyIndex){for(w=u+1;w<r.colKeyIndex;w++){this.unselectCell(q.cells[w])}}}}if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex===r.trIndex){if(v>r.colKeyIndex){this.unselectCell(A.cells[v])}}else{if(A.sectionRowIndex===p){if(v<u){this.unselectCell(A.cells[v])}}else{this.unselectCell(A.cells[v])}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>u){this.unselectCell(A.cells[v])}}else{if(A.sectionRowIndex==r.trIndex){if(v<r.colKeyIndex){this.unselectCell(A.cells[v])}}else{this.unselectCell(A.cells[v])}}}}}this.selectCell(k)}}else{this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n)}else{this.selectCell(n)}}}else{if(o){this.unselectAllCells();if(r){if(r.recordIndex===B){if(r.colKeyIndex<u){for(w=r.colKeyIndex;w<=u;w++){this.selectCell(q.cells[w])}}else{if(u<r.colKeyIndex){for(w=u;w<=r.colKeyIndex;w++){this.selectCell(q.cells[w])}}}}else{if(r.recordIndex<B){for(w=r.trIndex;w<=p;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==r.trIndex){if(v>=r.colKeyIndex){this.selectCell(A.cells[v])}}else{if(A.sectionRowIndex==p){if(v<=u){this.selectCell(A.cells[v])}}else{this.selectCell(A.cells[v])}}}}}else{for(w=p;w<=r.trIndex;w++){A=m[w];for(v=0;v<A.cells.length;v++){if(A.sectionRowIndex==p){if(v>=u){this.selectCell(A.cells[v])}}else{if(A.sectionRowIndex==r.trIndex){if(v<=r.colKeyIndex){this.selectCell(A.cells[v])}}else{this.selectCell(A.cells[v])}}}}}}}else{this._oAnchorCell=n;this.selectCell(n)}}else{if(l){this._oAnchorCell=n;if(this.isSelected(n)){this.unselectCell(n)}else{this.selectCell(n)}}else{this._handleSingleCellSelectionByMouse(y)}}}}},_handleCellRangeSelectionByKey:function(n){var j=g.getCharCode(n);var r=n.shiftKey;if((j==9)||!r){this._handleSingleCellSelectionByKey(n);return}if((j>36)&&(j<41)){var s=this._getSelectionTrigger();if(!s){return null}g.stopEvent(n);var q=this._getSelectionAnchor(s);var k,l,p;var o=this.getTbodyEl().rows;var m=s.el.parentNode;if(j==40){l=this.getNextTrEl(s.el);if(q.recordIndex<=s.recordIndex){for(k=s.colKeyIndex+1;k<m.cells.length;k++){p=m.cells[k];this.selectCell(p)}if(l){for(k=0;k<=s.colKeyIndex;k++){p=l.cells[k];this.selectCell(p)}}}else{for(k=s.colKeyIndex;k<m.cells.length;k++){this.unselectCell(m.cells[k])}if(l){for(k=0;k<s.colKeyIndex;k++){this.unselectCell(l.cells[k])}}}}else{if(j==38){l=this.getPreviousTrEl(s.el);if(q.recordIndex>=s.recordIndex){for(k=s.colKeyIndex-1;k>-1;k--){p=m.cells[k];this.selectCell(p)}if(l){for(k=m.cells.length-1;k>=s.colKeyIndex;k--){p=l.cells[k];this.selectCell(p)}}}else{for(k=s.colKeyIndex;k>-1;k--){this.unselectCell(m.cells[k])}if(l){for(k=m.cells.length-1;k>s.colKeyIndex;k--){this.unselectCell(l.cells[k])}}}}else{if(j==39){l=this.getNextTrEl(s.el);if(q.recordIndex<s.recordIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p)}else{if(l){p=l.cells[0];this.selectCell(p)}}}else{if(q.recordIndex>s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex<m.cells.length-1){}else{}}else{if(q.colKeyIndex<=s.colKeyIndex){if(s.colKeyIndex<m.cells.length-1){p=m.cells[s.colKeyIndex+1];this.selectCell(p)}else{if(s.trIndex<o.length-1){p=l.cells[0];this.selectCell(p)}}}else{this.unselectCell(m.cells[s.colKeyIndex])}}}}else{if(j==37){l=this.getPreviousTrEl(s.el);if(q.recordIndex<s.recordIndex){this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}else{if(q.recordIndex>s.recordIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p)}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p)}}}else{if(q.colKeyIndex>=s.colKeyIndex){if(s.colKeyIndex>0){p=m.cells[s.colKeyIndex-1];this.selectCell(p)}else{if(s.trIndex>0){p=l.cells[l.cells.length-1];this.selectCell(p)}}}else{this.unselectCell(m.cells[s.colKeyIndex]);if(s.colKeyIndex>0){}else{}}}}}}}}}},_handleSingleCellSelectionByMouse:function(n){var o=n.target;var k=this.getTdEl(o);if(k){var j=this.getTrEl(k);var i=this.getRecord(j);var m=this.getColumn(k);var l={record:i,column:m};this._oAnchorCell=l;this.unselectAllCells();this.selectCell(l)}},_handleSingleCellSelectionByKey:function(m){var i=g.getCharCode(m);if((i==9)||((i>36)&&(i<41))){var k=m.shiftKey;var j=this._getSelectionTrigger();if(!j){return null}var l;if(i==40){l=this.getBelowTdEl(j.el);if(l===null){l=j.el}}else{if(i==38){l=this.getAboveTdEl(j.el);if(l===null){l=j.el}}else{if((i==39)||(!k&&(i==9))){l=this.getNextTdEl(j.el);if(l===null){return}}else{if((i==37)||(k&&(i==9))){l=this.getPreviousTdEl(j.el);if(l===null){return}}}}}g.stopEvent(m);this.unselectAllCells();this.selectCell(l);this._oAnchorCell={record:this.getRecord(l),column:this.getColumn(l)}}},getSelectedTrEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"tr",this._elTbody)},selectRow:function(p){var o,i;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p);i=this.getTrEl(o)}else{if(h.isNumber(p)){o=this.getRecord(p);i=this.getTrEl(o)}else{i=this.getTrEl(p);o=this.getRecord(i)}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m)}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break}}}if(l>-1){n.splice(l,1)}n.push(m);this._aSelections=n;if(!this._oAnchorRecord){this._oAnchorRecord=o}if(i){c.addClass(i,d.CLASS_SELECTED)}this.fireEvent("rowSelectEvent",{record:o,el:i})}else{}},unselectRow:function(p){var i=this.getTrEl(p);var o;if(p instanceof YAHOO.widget.Record){o=this._oRecordSet.getRecord(p)}else{if(h.isNumber(p)){o=this.getRecord(p)}else{o=this.getRecord(i)}}if(o){var n=this._aSelections||[];var m=o.getId();var l=-1;if(n.indexOf){l=n.indexOf(m)}else{for(var k=n.length-1;k>-1;k--){if(n[k]===m){l=k;break}}}if(l>-1){n.splice(l,1);this._aSelections=n;c.removeClass(i,d.CLASS_SELECTED);this.fireEvent("rowUnselectEvent",{record:o,el:i});return}}},unselectAllRows:function(){var k=this._aSelections||[],m,l=[];for(var i=k.length-1;i>-1;i--){if(h.isString(k[i])){m=k.splice(i,1);l[l.length]=this.getRecord(h.isArray(m)?m[0]:m)}}this._aSelections=k;this._unselectAllTrEls();this.fireEvent("unselectAllRowsEvent",{records:l})},_unselectAllTdEls:function(){var i=c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody);c.removeClass(i,d.CLASS_SELECTED)},getSelectedTdEls:function(){return c.getElementsByClassName(d.CLASS_SELECTED,"td",this._elTbody)},selectCell:function(i){var p=this.getTdEl(i);if(p){var o=this.getRecord(p);var q=this.getColumn(this.getCellIndex(p));var m=q.getKey();if(o&&m){var n=this._aSelections||[];var l=o.getId();for(var k=n.length-1;k>-1;k--){if((n[k].recordId===l)&&(n[k].columnKey===m)){n.splice(k,1);break}}n.push({recordId:l,columnKey:m});this._aSelections=n;if(!this._oAnchorCell){this._oAnchorCell={record:o,column:q}}c.addClass(p,d.CLASS_SELECTED);this.fireEvent("cellSelectEvent",{record:o,column:q,key:m,el:p});return}}},unselectCell:function(i){var o=this.getTdEl(i);if(o){var n=this.getRecord(o);var p=this.getColumn(this.getCellIndex(o));var l=p.getKey();if(n&&l){var m=this._aSelections||[];var q=n.getId();for(var k=m.length-1;k>-1;k--){if((m[k].recordId===q)&&(m[k].columnKey===l)){m.splice(k,1);this._aSelections=m;c.removeClass(o,d.CLASS_SELECTED);this.fireEvent("cellUnselectEvent",{record:n,column:p,key:l,el:o});return}}}}},unselectAllCells:function(){var k=this._aSelections||[];for(var i=k.length-1;i>-1;i--){if(h.isObject(k[i])){k.splice(i,1)}}this._aSelections=k;this._unselectAllTdEls();this.fireEvent("unselectAllCellsEvent")},isSelected:function(p){if(p&&(p.ownerDocument==document)){return(c.hasClass(this.getTdEl(p),d.CLASS_SELECTED)||c.hasClass(this.getTrEl(p),d.CLASS_SELECTED))}else{var n,k,i;var m=this._aSelections;if(m&&m.length>0){if(p instanceof YAHOO.widget.Record){n=p}else{if(h.isNumber(p)){n=this.getRecord(p)}}if(n){k=n.getId();if(m.indexOf){if(m.indexOf(k)>-1){return true}}else{for(i=m.length-1;i>-1;i--){if(m[i]===k){return true}}}}else{if(p.record&&p.column){k=p.record.getId();var l=p.column.getKey();for(i=m.length-1;i>-1;i--){if((m[i].recordId===k)&&(m[i].columnKey===l)){return true}}}}}}return false},getSelectedRows:function(){var i=[];var l=this._aSelections||[];for(var k=0;k<l.length;k++){if(h.isString(l[k])){i.push(l[k])}}return i},getSelectedCells:function(){var k=[];var l=this._aSelections||[];for(var i=0;i<l.length;i++){if(l[i]&&h.isObject(l[i])){k.push(l[i])}}return k},getLastSelectedRecord:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(h.isString(k[j])){return k[j]}}}},getLastSelectedCell:function(){var k=this._aSelections;if(k&&k.length>0){for(var j=k.length-1;j>-1;j--){if(k[j].recordId&&k[j].columnKey){return k[j]}}}},highlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.addClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowHighlightEvent",{record:j,el:i});return}},unhighlightRow:function(k){var i=this.getTrEl(k);if(i){var j=this.getRecord(i);c.removeClass(i,d.CLASS_HIGHLIGHTED);this.fireEvent("rowUnhighlightEvent",{record:j,el:i});return}},highlightCell:function(i){var l=this.getTdEl(i);if(l){if(this._elLastHighlightedTd){this.unhighlightCell(this._elLastHighlightedTd)}var k=this.getRecord(l);var m=this.getColumn(this.getCellIndex(l));var j=m.getKey();c.addClass(l,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=l;this.fireEvent("cellHighlightEvent",{record:k,column:m,key:j,el:l});return}},unhighlightCell:function(i){var k=this.getTdEl(i);if(k){var j=this.getRecord(k);c.removeClass(k,d.CLASS_HIGHLIGHTED);this._elLastHighlightedTd=null;this.fireEvent("cellUnhighlightEvent",{record:j,column:this.getColumn(this.getCellIndex(k)),key:this.getColumn(this.getCellIndex(k)).getKey(),el:k});return}},addCellEditor:function(j,i){j.editor=i;j.editor.subscribe("showEvent",this._onEditorShowEvent,this,true);j.editor.subscribe("keydownEvent",this._onEditorKeydownEvent,this,true);j.editor.subscribe("revertEvent",this._onEditorRevertEvent,this,true);j.editor.subscribe("saveEvent",this._onEditorSaveEvent,this,true);j.editor.subscribe("cancelEvent",this._onEditorCancelEvent,this,true);j.editor.subscribe("blurEvent",this._onEditorBlurEvent,this,true);j.editor.subscribe("blockEvent",this._onEditorBlockEvent,this,true);j.editor.subscribe("unblockEvent",this._onEditorUnblockEvent,this,true)},getCellEditor:function(){return this._oCellEditor},showCellEditor:function(p,q,l){p=this.getTdEl(p);if(p){l=this.getColumn(p);if(l&&l.editor){var j=this._oCellEditor;if(j){if(this._oCellEditor.cancel){this._oCellEditor.cancel()}else{if(j.isActive){this.cancelCellEditor()}}}if(l.editor instanceof YAHOO.widget.BaseCellEditor){j=l.editor;var n=j.attach(this,p);if(n){j.render();j.move();n=this.doBeforeShowCellEditor(j);if(n){j.show();this._oCellEditor=j}}}else{if(!q||!(q instanceof YAHOO.widget.Record)){q=this.getRecord(p)}if(!l||!(l instanceof YAHOO.widget.Column)){l=this.getColumn(p)}if(q&&l){if(!this._oCellEditor||this._oCellEditor.container){this._initCellEditorEl()}j=this._oCellEditor;j.cell=p;j.record=q;j.column=l;j.validator=(l.editorOptions&&h.isFunction(l.editorOptions.validator))?l.editorOptions.validator:null;j.value=q.getData(l.key);j.defaultValue=null;var k=j.container;var o=c.getX(p);var m=c.getY(p);if(isNaN(o)||isNaN(m)){o=p.offsetLeft+c.getX(this._elTbody.parentNode)-this._elTbody.scrollLeft;m=p.offsetTop+c.getY(this._elTbody.parentNode)-this._elTbody.scrollTop+this._elThead.offsetHeight}k.style.left=o+"px";k.style.top=m+"px";this.doBeforeShowCellEditor(this._oCellEditor);k.style.display="";g.addListener(k,"keydown",function(s,r){if((s.keyCode==27)){r.cancelCellEditor();r.focusTbodyEl()}else{r.fireEvent("editorKeydownEvent",{editor:r._oCellEditor,event:s})}},this);var i;if(h.isString(l.editor)){switch(l.editor){case"checkbox":i=d.editCheckbox;break;case"date":i=d.editDate;break;case"dropdown":i=d.editDropdown;break;case"radio":i=d.editRadio;break;case"textarea":i=d.editTextarea;break;case"textbox":i=d.editTextbox;break;default:i=null}}else{if(h.isFunction(l.editor)){i=l.editor}}if(i){i(this._oCellEditor,this);if(!l.editorOptions||!l.editorOptions.disableBtns){this.showCellEditorBtns(k)}j.isActive=true;this.fireEvent("editorShowEvent",{editor:j});return}}}}}},_initCellEditorEl:function(){var i=document.createElement("div");i.id=this._sId+"-celleditor";i.style.display="none";i.tabIndex=0;c.addClass(i,d.CLASS_EDITOR);var k=c.getFirstChild(document.body);if(k){i=c.insertBefore(i,k)}else{i=document.body.appendChild(i)}var j={};j.container=i;j.value=null;j.isActive=false;this._oCellEditor=j},doBeforeShowCellEditor:function(i){return true},saveCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save()}else{if(this._oCellEditor.isActive){var i=this._oCellEditor.value;var j=this._oCellEditor.record.getData(this._oCellEditor.column.key);if(this._oCellEditor.validator){i=this._oCellEditor.value=this._oCellEditor.validator.call(this,i,j,this._oCellEditor);if(i===null){this.resetCellEditor();this.fireEvent("editorRevertEvent",{editor:this._oCellEditor,oldData:j,newData:i});return}}this._oRecordSet.updateRecordValue(this._oCellEditor.record,this._oCellEditor.column.key,this._oCellEditor.value);this.formatCell(this._oCellEditor.cell.firstChild,this._oCellEditor.record,this._oCellEditor.column);this._oChainRender.add({method:function(){this.validateColumnWidths()},scope:this});this._oChainRender.run();this.resetCellEditor();this.fireEvent("editorSaveEvent",{editor:this._oCellEditor,oldData:j,newData:i})}}}},cancelCellEditor:function(){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel()}else{if(this._oCellEditor.isActive){this.resetCellEditor();this.fireEvent("editorCancelEvent",{editor:this._oCellEditor})}}}},destroyCellEditor:function(){if(this._oCellEditor){this._oCellEditor.destroy();this._oCellEditor=null}},_onEditorShowEvent:function(i){this.fireEvent("editorShowEvent",i)},_onEditorKeydownEvent:function(i){this.fireEvent("editorKeydownEvent",i)},_onEditorRevertEvent:function(i){this.fireEvent("editorRevertEvent",i)},_onEditorSaveEvent:function(i){this.fireEvent("editorSaveEvent",i)},_onEditorCancelEvent:function(i){this.fireEvent("editorCancelEvent",i)},_onEditorBlurEvent:function(i){this.fireEvent("editorBlurEvent",i)},_onEditorBlockEvent:function(i){this.fireEvent("editorBlockEvent",i)},_onEditorUnblockEvent:function(i){this.fireEvent("editorUnblockEvent",i)},onEditorBlurEvent:function(i){if(i.editor.disableBtns){if(i.editor.save){i.editor.save()}}else{if(i.editor.cancel){i.editor.cancel()}}},onEditorBlockEvent:function(i){this.disable()},onEditorUnblockEvent:function(i){this.undisable()},doBeforeLoadData:function(i,j,k){return true},onEventSortColumn:function(k){var i=k.event;var m=k.target;var j=this.getThEl(m)||this.getTdEl(m);if(j){var l=this.getColumn(j);if(l.sortable){g.stopEvent(i);this.sortColumn(l)}}else{}},onEventSelectColumn:function(i){this.selectColumn(i.target)},onEventHighlightColumn:function(i){this.highlightColumn(i.target)},onEventUnhighlightColumn:function(i){this.unhighlightColumn(i.target)},onEventSelectRow:function(j){var i=this.get("selectionMode");if(i=="single"){this._handleSingleSelectionByMouse(j)}else{this._handleStandardSelectionByMouse(j)}},onEventSelectCell:function(j){var i=this.get("selectionMode");if(i=="cellblock"){this._handleCellBlockSelectionByMouse(j)}else{if(i=="cellrange"){this._handleCellRangeSelectionByMouse(j)}else{this._handleSingleCellSelectionByMouse(j)}}},onEventHighlightRow:function(i){this.highlightRow(i.target)},onEventUnhighlightRow:function(i){this.unhighlightRow(i.target)},onEventHighlightCell:function(i){this.highlightCell(i.target)},onEventUnhighlightCell:function(i){this.unhighlightCell(i.target)},onEventFormatCell:function(i){var l=i.target;var j=this.getTdEl(l);if(j){var k=this.getColumn(this.getCellIndex(j));this.formatCell(j.firstChild,this.getRecord(j),k)}else{}},onEventShowCellEditor:function(i){if(!this.isDisabled()){this.showCellEditor(i.target)}},onEventSaveCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.save){this._oCellEditor.save()}else{this.saveCellEditor()}}},onEventCancelCellEditor:function(i){if(this._oCellEditor){if(this._oCellEditor.cancel){this._oCellEditor.cancel()}else{this.cancelCellEditor()}}},onDataReturnInitializeTable:function(i,j,k){if((this instanceof d)&&this._sId){this.initializeTable();this.onDataReturnSetRows(i,j,k)}},onDataReturnReplaceRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){this._oRecordSet.reset();if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset}else{if(k){i=k.getStartIndex()}}}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render()}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR)}}}},onDataReturnAppendRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results);this._handleDataReturnPayload(j,k,l)}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR)}}}},onDataReturnInsertRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.addRows(k.results,(l?l.insertIndex:0));this._handleDataReturnPayload(j,k,l)}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR)}}}},onDataReturnUpdateRows:function(j,k,l){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:j,response:k,payload:l});var i=this.doBeforeLoadData(j,k,l);if(i&&k&&!k.error&&h.isArray(k.results)){this.updateRows((l?l.updateIndex:0),k.results);this._handleDataReturnPayload(j,k,l)}else{if(i&&k.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR)}}}},onDataReturnSetRows:function(m,l,n){if((this instanceof d)&&this._sId){this.fireEvent("dataReturnEvent",{request:m,response:l,payload:n});var j=this.doBeforeLoadData(m,l,n),k=this.get("paginator"),i=0;if(j&&l&&!l.error&&h.isArray(l.results)){if(this.get("dynamicData")){if(n&&n.pagination&&h.isNumber(n.pagination.recordOffset)){i=n.pagination.recordOffset}else{if(k){i=k.getStartIndex()}}this._oRecordSet.reset()}this._oRecordSet.setRecords(l.results,i|0);this._handleDataReturnPayload(m,l,n);this.render()}else{if(j&&l.error){this.showTableMessage(this.get("MSG_ERROR"),d.CLASS_ERROR)}}}else{}},handleDataReturnPayload:function(j,i,k){return k||{}},_handleDataReturnPayload:function(k,j,l){l=this.handleDataReturnPayload(k,j,l);if(l){var i=this.get("paginator");if(i){if(this.get("dynamicData")){if(e.Paginator.isNumeric(l.totalRecords)){i.set("totalRecords",l.totalRecords)}}else{i.set("totalRecords",this._oRecordSet.getLength())}if(h.isObject(l.pagination)){i.set("rowsPerPage",l.pagination.rowsPerPage);i.set("recordOffset",l.pagination.recordOffset)}}if(l.sortedBy){this.set("sortedBy",l.sortedBy)}else{if(l.sorting){this.set("sortedBy",l.sorting)}}}},showCellEditorBtns:function(k){var l=k.appendChild(document.createElement("div"));c.addClass(l,d.CLASS_BUTTON);var j=l.appendChild(document.createElement("button"));c.addClass(j,d.CLASS_DEFAULT);j.innerHTML="OK";g.addListener(j,"click",function(n,m){m.onEventSaveCellEditor(n,m);m.focusTbodyEl()},this,true);var i=l.appendChild(document.createElement("button"));i.innerHTML="Cancel";g.addListener(i,"click",function(n,m){m.onEventCancelCellEditor(n,m);m.focusTbodyEl()},this,true)},resetCellEditor:function(){var i=this._oCellEditor.container;i.style.display="none";g.purgeElement(i,true);i.innerHTML="";this._oCellEditor.value=null;this._oCellEditor.isActive=false},getBody:function(){return this.getTbodyEl()},getCell:function(i){return this.getTdEl(i)},getRow:function(i){return this.getTrEl(i)},refreshView:function(){this.render()},select:function(k){if(!h.isArray(k)){k=[k]}for(var j=0;j<k.length;j++){this.selectRow(k[j])}},onEventEditCell:function(i){this.onEventShowCellEditor(i)},_syncColWidths:function(){this.validateColumnWidths()}});d.prototype.onDataReturnSetRecords=d.prototype.onDataReturnSetRows;d.prototype.onPaginatorChange=d.prototype.onPaginatorChangeRequest;d.editCheckbox=function(){};d.editDate=function(){};d.editDropdown=function(){};d.editRadio=function(){};d.editTextarea=function(){};d.editTextbox=function(){}})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,a=YAHOO.env.ua,d=f.Dom,j=f.Event,i=f.DataSourceBase,g=e.DataTable,b=e.Paginator;e.ScrollingDataTable=function(n,m,k,l){l=l||{};if(l.scrollable){l.scrollable=false}this._init();e.ScrollingDataTable.superclass.constructor.call(this,n,m,k,l);this.subscribe("columnShowEvent",this._onColumnChange)};var h=e.ScrollingDataTable;c.augmentObject(h,{CLASS_HEADER:"yui-dt-hd",CLASS_BODY:"yui-dt-bd"});c.extend(h,g,{_elHdContainer:null,_elHdTable:null,_elBdContainer:null,_elBdThead:null,_elTmpContainer:null,_elTmpTable:null,_bScrollbarX:null,initAttributes:function(k){k=k||{};h.superclass.initAttributes.call(this,k);this.setAttributeConfig("width",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elHdContainer.style.width=l;this._elBdContainer.style.width=l;this._syncScrollX();this._syncScrollOverhang()}}});this.setAttributeConfig("height",{value:null,validator:c.isString,method:function(l){if(this._elHdContainer&&this._elBdContainer){this._elBdContainer.style.height=l;this._syncScrollX();this._syncScrollY();this._syncScrollOverhang()}}});this.setAttributeConfig("COLOR_COLUMNFILLER",{value:"#F2F2F2",validator:c.isString,method:function(l){if(this._elHdContainer){this._elHdContainer.style.backgroundColor=l}}})},_init:function(){this._elHdContainer=null;this._elHdTable=null;this._elBdContainer=null;this._elBdThead=null;this._elTmpContainer=null;this._elTmpTable=null},_initDomElements:function(k){this._initContainerEl(k);if(this._elContainer&&this._elHdContainer&&this._elBdContainer){this._initTableEl();if(this._elHdTable&&this._elTable){this._initColgroupEl(this._elHdTable);this._initTheadEl(this._elHdTable,this._elTable);this._initTbodyEl(this._elTable);this._initMsgTbodyEl(this._elTable)}}if(!this._elContainer||!this._elTable||!this._elColgroup||!this._elThead||!this._elTbody||!this._elMsgTbody||!this._elHdTable||!this._elBdThead){return false}else{return true}},_destroyContainerEl:function(k){d.removeClass(k,g.CLASS_SCROLLABLE);h.superclass._destroyContainerEl.call(this,k);this._elHdContainer=null;this._elBdContainer=null},_initContainerEl:function(l){h.superclass._initContainerEl.call(this,l);if(this._elContainer){l=this._elContainer;d.addClass(l,g.CLASS_SCROLLABLE);var k=document.createElement("div");k.style.width=this.get("width")||"";k.style.backgroundColor=this.get("COLOR_COLUMNFILLER");d.addClass(k,h.CLASS_HEADER);this._elHdContainer=k;l.appendChild(k);var m=document.createElement("div");m.style.width=this.get("width")||"";m.style.height=this.get("height")||"";d.addClass(m,h.CLASS_BODY);j.addListener(m,"scroll",this._onScroll,this);this._elBdContainer=m;l.appendChild(m)}},_initCaptionEl:function(k){},_destroyHdTableEl:function(){var k=this._elHdTable;if(k){j.purgeElement(k,true);k.parentNode.removeChild(k);this._elBdThead=null}},_initTableEl:function(){if(this._elHdContainer){this._destroyHdTableEl();this._elHdTable=this._elHdContainer.appendChild(document.createElement("table"));j.delegate(this._elHdTable,"mouseenter",this._onTableMouseover,"thead ."+g.CLASS_LABEL,this);j.delegate(this._elHdTable,"mouseleave",this._onTableMouseout,"thead ."+g.CLASS_LABEL,this)}h.superclass._initTableEl.call(this,this._elBdContainer)},_initTheadEl:function(l,k){l=l||this._elHdTable;k=k||this._elTable;this._initBdTheadEl(k);h.superclass._initTheadEl.call(this,l)},_initThEl:function(l,k){h.superclass._initThEl.call(this,l,k);l.id=this.getId()+"-fixedth-"+k.getSanitizedKey()},_destroyBdTheadEl:function(){var k=this._elBdThead;if(k){var l=k.parentNode;j.purgeElement(k,true);l.removeChild(k);this._elBdThead=null;this._destroyColumnHelpers()}},_initBdTheadEl:function(t){if(t){this._destroyBdTheadEl();var p=t.insertBefore(document.createElement("thead"),t.firstChild);var v=this._oColumnSet,u=v.tree,o,l,s,q,n,m,r;for(q=0,m=u.length;q<m;q++){l=p.appendChild(document.createElement("tr"));for(n=0,r=u[q].length;n<r;n++){s=u[q][n];o=l.appendChild(document.createElement("th"));this._initBdThEl(o,s,q,n)}}this._elBdThead=p}},_initBdThEl:function(n,m){n.id=this.getId()+"-th-"+m.getSanitizedKey();n.rowSpan=m.getRowspan();n.colSpan=m.getColspan();if(m.abbr){n.abbr=m.abbr}var l=m.getKey();var k=c.isValue(m.label)?m.label:l;n.innerHTML=k},_initTbodyEl:function(k){h.superclass._initTbodyEl.call(this,k);k.style.marginTop=(this._elTbody.offsetTop>0)?"-"+this._elTbody.offsetTop+"px":0},_focusEl:function(l){l=l||this._elTbody;var k=this;this._storeScrollPositions();setTimeout(function(){setTimeout(function(){try{l.focus();k._restoreScrollPositions()}catch(m){}},0)},0)},_runRenderChain:function(){this._storeScrollPositions();this._oChainRender.run()},_storeScrollPositions:function(){this._nScrollTop=this._elBdContainer.scrollTop;this._nScrollLeft=this._elBdContainer.scrollLeft},clearScrollPositions:function(){this._nScrollTop=0;this._nScrollLeft=0},_restoreScrollPositions:function(){if(this._nScrollTop){this._elBdContainer.scrollTop=this._nScrollTop;this._nScrollTop=null}if(this._nScrollLeft){this._elBdContainer.scrollLeft=this._nScrollLeft;this._elHdContainer.scrollLeft=this._nScrollLeft;this._nScrollLeft=null}},_validateColumnWidth:function(n,k){if(!n.width&&!n.hidden){var p=n.getThEl();if(n._calculatedWidth){this._setColumnWidth(n,"auto","visible")}if(p.offsetWidth!==k.offsetWidth){var m=(p.offsetWidth>k.offsetWidth)?n.getThLinerEl():k.firstChild;var l=Math.max(0,(m.offsetWidth-(parseInt(d.getStyle(m,"paddingLeft"),10)|0)-(parseInt(d.getStyle(m,"paddingRight"),10)|0)),n.minWidth);var o="visible";if((n.maxAutoWidth>0)&&(l>n.maxAutoWidth)){l=n.maxAutoWidth;o="hidden"}this._elTbody.style.display="none";this._setColumnWidth(n,l+"px",o);n._calculatedWidth=l;this._elTbody.style.display=""}}},validateColumnWidths:function(s){var u=this._oColumnSet.keys,w=u.length,l=this.getFirstTrEl();if(a.ie){this._setOverhangValue(1)}if(u&&l&&(l.childNodes.length===w)){var m=this.get("width");if(m){this._elHdContainer.style.width="";this._elBdContainer.style.width=""}this._elContainer.style.width="";if(s&&c.isNumber(s.getKeyIndex())){this._validateColumnWidth(s,l.childNodes[s.getKeyIndex()])}else{var t,k=[],o,q,r;for(q=0;q<w;q++){s=u[q];if(!s.width&&!s.hidden&&s._calculatedWidth){k[k.length]=s}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){this._setColumnWidth(k[q],"auto","visible")}this._elTbody.style.display="";k=[];for(q=0;q<w;q++){s=u[q];t=l.childNodes[q];if(!s.width&&!s.hidden){var n=s.getThEl();if(n.offsetWidth!==t.offsetWidth){var v=(n.offsetWidth>t.offsetWidth)?s.getThLinerEl():t.firstChild;var p=Math.max(0,(v.offsetWidth-(parseInt(d.getStyle(v,"paddingLeft"),10)|0)-(parseInt(d.getStyle(v,"paddingRight"),10)|0)),s.minWidth);var x="visible";if((s.maxAutoWidth>0)&&(p>s.maxAutoWidth)){p=s.maxAutoWidth;x="hidden"}k[k.length]=[s,p,x]}}}this._elTbody.style.display="none";for(q=0,r=k.length;q<r;q++){o=k[q];this._setColumnWidth(o[0],o[1]+"px",o[2]);o[0]._calculatedWidth=o[1]}this._elTbody.style.display=""}if(m){this._elHdContainer.style.width=m;this._elBdContainer.style.width=m}}this._syncScroll();this._restoreScrollPositions()},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(a.opera){this._elHdContainer.scrollLeft=this._elBdContainer.scrollLeft;if(!this.get("width")){document.body.style+=""}}},_syncScrollY:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("width")){this._elContainer.style.width=(l.scrollHeight>l.clientHeight)?(k.parentNode.clientWidth+19)+"px":(k.parentNode.clientWidth+2)+"px"}},_syncScrollX:function(){var k=this._elTbody,l=this._elBdContainer;if(!this.get("height")&&(a.ie)){l.style.height=(l.scrollWidth>l.offsetWidth)?(k.parentNode.offsetHeight+18)+"px":k.parentNode.offsetHeight+"px"}if(this._elTbody.rows.length===0){this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px"}else{this._elMsgTbody.parentNode.style.width=""}},_syncScrollOverhang:function(){var l=this._elBdContainer,k=1;if((l.scrollHeight>l.clientHeight)&&(l.scrollWidth>l.clientWidth)){k=18}this._setOverhangValue(k)},_setOverhangValue:function(n){var p=this._oColumnSet.headers[this._oColumnSet.headers.length-1]||[],l=p.length,k=this._sId+"-fixedth-",o=n+"px solid "+this.get("COLOR_COLUMNFILLER");this._elThead.style.display="none";for(var m=0;m<l;m++){d.get(k+p[m]).style.borderRight=o}this._elThead.style.display=""},getHdContainerEl:function(){return this._elHdContainer},getBdContainerEl:function(){return this._elBdContainer},getHdTableEl:function(){return this._elHdTable},getBdTableEl:function(){return this._elTable},disable:function(){var k=this._elMask;k.style.width=this._elBdContainer.offsetWidth+"px";k.style.height=this._elHdContainer.offsetHeight+this._elBdContainer.offsetHeight+"px";k.style.display="";this.fireEvent("disableEvent")},removeColumn:function(m){var k=this._elHdContainer.scrollLeft;var l=this._elBdContainer.scrollLeft;m=h.superclass.removeColumn.call(this,m);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=l;return m},insertColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.insertColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o},reorderColumn:function(n,l){var k=this._elHdContainer.scrollLeft;var m=this._elBdContainer.scrollLeft;var o=h.superclass.reorderColumn.call(this,n,l);this._elHdContainer.scrollLeft=k;this._elBdContainer.scrollLeft=m;return o},setColumnWidth:function(l,k){l=this.getColumn(l);if(l){this._storeScrollPositions();if(c.isNumber(k)){k=(k>l.minWidth)?k:l.minWidth;l.width=k;this._setColumnWidth(l,k+"px");this._syncScroll();this.fireEvent("columnSetWidthEvent",{column:l,width:k})}else{if(k===null){l.width=k;this._setColumnWidth(l,"auto");this.validateColumnWidths(l);this.fireEvent("columnUnsetWidthEvent",{column:l})}}this._clearTrTemplateEl()}else{}},scrollTo:function(m){var l=this.getTdEl(m);if(l){this.clearScrollPositions();this.getBdContainerEl().scrollLeft=l.offsetLeft;this.getBdContainerEl().scrollTop=l.parentNode.offsetTop}else{var k=this.getTrEl(m);if(k){this.clearScrollPositions();this.getBdContainerEl().scrollTop=k.offsetTop}}},showTableMessage:function(o,k){var p=this._elMsgTd;if(c.isString(o)){p.firstChild.innerHTML=o}if(c.isString(k)){d.addClass(p.firstChild,k)}var n=this.getTheadEl();var l=n.parentNode;var m=l.offsetWidth;this._elMsgTbody.parentNode.style.width=this.getTheadEl().parentNode.offsetWidth+"px";this._elMsgTbody.style.display="";this.fireEvent("tableMsgShowEvent",{html:o,className:k})},_onColumnChange:function(k){var l=(k.column)?k.column:(k.editor)?k.editor.column:null;this._storeScrollPositions();this.validateColumnWidths(l)},_onScroll:function(m,l){l._elHdContainer.scrollLeft=l._elBdContainer.scrollLeft;if(l._oCellEditor&&l._oCellEditor.isActive){l.fireEvent("editorBlurEvent",{editor:l._oCellEditor});l.cancelCellEditor()}var n=j.getTarget(m);var k=n.nodeName.toLowerCase();l.fireEvent("tableScrollEvent",{event:m,target:n})},_onTheadKeydown:function(n,l){if(j.getCharCode(n)===9){setTimeout(function(){if((l instanceof h)&&l._sId){l._elBdContainer.scrollLeft=l._elHdContainer.scrollLeft}},0)}var o=j.getTarget(n);var k=o.nodeName.toLowerCase();var m=true;while(o&&(k!="table")){switch(k){case"body":return;case"input":case"textarea":break;case"thead":m=l.fireEvent("theadKeyEvent",{target:o,event:n});break;default:break}if(m===false){return}else{o=o.parentNode;if(o){k=o.nodeName.toLowerCase()}}}l.fireEvent("tableKeyEvent",{target:(o||l._elContainer),event:n})}})})();(function(){var c=YAHOO.lang,f=YAHOO.util,e=YAHOO.widget,b=YAHOO.env.ua,d=f.Dom,i=f.Event,h=e.DataTable;e.BaseCellEditor=function(k,j){this._sId=this._sId||d.generateId(null,"yui-ceditor");YAHOO.widget.BaseCellEditor._nCount++;this._sType=k;this._initConfigs(j);this._initEvents();this._needsRender=true};var a=e.BaseCellEditor;c.augmentObject(a,{_nCount:0,CLASS_CELLEDITOR:"yui-ceditor"});a.prototype={_sId:null,_sType:null,_oDataTable:null,_oColumn:null,_oRecord:null,_elTd:null,_elContainer:null,_elCancelBtn:null,_elSaveBtn:null,_initConfigs:function(k){if(k&&YAHOO.lang.isObject(k)){for(var j in k){if(j){this[j]=k[j]}}}},_initEvents:function(){this.createEvent("showEvent");this.createEvent("keydownEvent");this.createEvent("invalidDataEvent");this.createEvent("revertEvent");this.createEvent("saveEvent");this.createEvent("cancelEvent");this.createEvent("blurEvent");this.createEvent("blockEvent");this.createEvent("unblockEvent")},_initContainerEl:function(){if(this._elContainer){YAHOO.util.Event.purgeElement(this._elContainer,true);this._elContainer.innerHTML=""}var j=document.createElement("div");j.id=this.getId()+"-container";j.style.display="none";j.tabIndex=0;this.className=c.isArray(this.className)?this.className:this.className?[this.className]:[];this.className[this.className.length]=h.CLASS_EDITOR;j.className=this.className.join(" ");document.body.insertBefore(j,document.body.firstChild);this._elContainer=j},_initShimEl:function(){if(this.useIFrame){if(!this._elIFrame){var j=document.createElement("iframe");j.src="javascript:false";j.frameBorder=0;j.scrolling="no";j.style.display="none";j.className=h.CLASS_EDITOR_SHIM;j.tabIndex=-1;j.role="presentation";j.title="Presentational iframe shim";document.body.insertBefore(j,document.body.firstChild);this._elIFrame=j}}},_hide:function(){this.getContainerEl().style.display="none";if(this._elIFrame){this._elIFrame.style.display="none"}this.isActive=false;this.getDataTable()._oCellEditor=null},asyncSubmitter:null,value:null,defaultValue:null,validator:null,resetInvalidData:true,isActive:false,LABEL_SAVE:"Save",LABEL_CANCEL:"Cancel",disableBtns:false,useIFrame:false,className:null,toString:function(){return"CellEditor instance "+this._sId},getId:function(){return this._sId},getDataTable:function(){return this._oDataTable},getColumn:function(){return this._oColumn},getRecord:function(){return this._oRecord},getTdEl:function(){return this._elTd},getContainerEl:function(){return this._elContainer},destroy:function(){this.unsubscribeAll();var k=this.getColumn();if(k){k.editor=null}var j=this.getContainerEl();if(j){i.purgeElement(j,true);j.parentNode.removeChild(j)}},render:function(){if(!this._needsRender){return}this._initContainerEl();this._initShimEl();i.addListener(this.getContainerEl(),"keydown",function(l,j){if((l.keyCode==27)){var k=i.getTarget(l);if(k.nodeName&&k.nodeName.toLowerCase()==="select"){k.blur()}j.cancel()}j.fireEvent("keydownEvent",{editor:j,event:l})},this);this.renderForm();if(!this.disableBtns){this.renderBtns()}this.doAfterRender();this._needsRender=false},renderBtns:function(){var l=this.getContainerEl().appendChild(document.createElement("div"));l.className=h.CLASS_BUTTON;var k=l.appendChild(document.createElement("button"));k.className=h.CLASS_DEFAULT;k.innerHTML=this.LABEL_SAVE;i.addListener(k,"click",function(m){this.save()},this,true);this._elSaveBtn=k;var j=l.appendChild(document.createElement("button"));j.innerHTML=this.LABEL_CANCEL;i.addListener(j,"click",function(m){this.cancel()},this,true);this._elCancelBtn=j},attach:function(n,l){if(n instanceof YAHOO.widget.DataTable){this._oDataTable=n;l=n.getTdEl(l);if(l){this._elTd=l;var m=n.getColumn(l);if(m){this._oColumn=m;var j=n.getRecord(l);if(j){this._oRecord=j;var k=j.getData(this.getColumn().getField());this.value=(k!==undefined)?k:this.defaultValue;return true}}}}return false},move:function(){var m=this.getContainerEl(),l=this.getTdEl(),j=d.getX(l),n=d.getY(l);if(isNaN(j)||isNaN(n)){var k=this.getDataTable().getTbodyEl();j=l.offsetLeft+d.getX(k.parentNode)-k.scrollLeft;n=l.offsetTop+d.getY(k.parentNode)-k.scrollTop+this.getDataTable().getTheadEl().offsetHeight}m.style.left=j+"px";m.style.top=n+"px";if(this._elIFrame){this._elIFrame.style.left=j+"px";this._elIFrame.style.top=n+"px"}},show:function(){var k=this.getContainerEl(),j=this._elIFrame;this.resetForm();this.isActive=true;k.style.display="";if(j){j.style.width=k.offsetWidth+"px";j.style.height=k.offsetHeight+"px";j.style.display=""}this.focus();this.fireEvent("showEvent",{editor:this})},block:function(){this.fireEvent("blockEvent",{editor:this})},unblock:function(){this.fireEvent("unblockEvent",{editor:this})},save:function(){var k=this.getInputValue();var l=k;if(this.validator){l=this.validator.call(this.getDataTable(),k,this.value,this);if(l===undefined){if(this.resetInvalidData){this.resetForm()}this.fireEvent("invalidDataEvent",{editor:this,oldData:this.value,newData:k});return}}var m=this;var j=function(o,n){var p=m.value;if(o){m.value=n;m.getDataTable().updateCell(m.getRecord(),m.getColumn(),n);m._hide();m.fireEvent("saveEvent",{editor:m,oldData:p,newData:m.value})}else{m.resetForm();m.fireEvent("revertEvent",{editor:m,oldData:p,newData:n})}m.unblock()};this.block();if(c.isFunction(this.asyncSubmitter)){this.asyncSubmitter.call(this,j,l)}else{j(true,l)}},cancel:function(){if(this.isActive){this._hide();this.fireEvent("cancelEvent",{editor:this})}else{}},renderForm:function(){},doAfterRender:function(){},handleDisabledBtns:function(){},resetForm:function(){},focus:function(){},getInputValue:function(){}};c.augmentProto(a,f.EventProvider);e.CheckboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-checkboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.CheckboxCellEditor.superclass.constructor.call(this,j.type||"checkbox",j)};c.extend(e.CheckboxCellEditor,a,{checkboxOptions:null,checkboxes:null,value:null,renderForm:function(){if(c.isArray(this.checkboxOptions)){var n,o,q,l,m,k;for(m=0,k=this.checkboxOptions.length;m<k;m++){n=this.checkboxOptions[m];o=c.isValue(n.value)?n.value:n;q=this.getId()+"-chk"+m;this.getContainerEl().innerHTML+='<input type="checkbox" id="'+q+'" value="'+o+'" />';l=this.getContainerEl().appendChild(document.createElement("label"));l.htmlFor=q;l.innerHTML=c.isValue(n.label)?n.label:n}var p=[];for(m=0;m<k;m++){p[p.length]=this.getContainerEl().childNodes[m*2]}this.checkboxes=p;if(this.disableBtns){this.handleDisabledBtns()}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save()}},this,true)},resetForm:function(){var p=c.isArray(this.value)?this.value:[this.value];for(var o=0,n=this.checkboxes.length;o<n;o++){this.checkboxes[o].checked=false;for(var m=0,l=p.length;m<l;m++){if(this.checkboxes[o].value==p[m]){this.checkboxes[o].checked=true}}}},focus:function(){this.checkboxes[0].focus()},getInputValue:function(){var k=[];for(var m=0,l=this.checkboxes.length;m<l;m++){if(this.checkboxes[m].checked){k[k.length]=this.checkboxes[m].value}}return k}});c.augmentObject(e.CheckboxCellEditor,a);e.DateCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dateceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DateCellEditor.superclass.constructor.call(this,j.type||"date",j)};c.extend(e.DateCellEditor,a,{calendar:null,calendarOptions:null,defaultValue:new Date(),renderForm:function(){if(YAHOO.widget.Calendar){var k=this.getContainerEl().appendChild(document.createElement("div"));k.id=this.getId()+"-dateContainer";var l=new YAHOO.widget.Calendar(this.getId()+"-date",k.id,this.calendarOptions);l.render();k.style.cssFloat="none";l.hideEvent.subscribe(function(){this.cancel()},this,true);if(b.ie){var j=this.getContainerEl().appendChild(document.createElement("div"));j.style.clear="both"}this.calendar=l;if(this.disableBtns){this.handleDisabledBtns()}}else{}},handleDisabledBtns:function(){this.calendar.selectEvent.subscribe(function(j){this.save()},this,true)},resetForm:function(){var j=this.value||(new Date());this.calendar.select(j);this.calendar.cfg.setProperty("pagedate",j,false);this.calendar.render();this.calendar.show()},focus:function(){},getInputValue:function(){return this.calendar.getSelectedDates()[0]}});c.augmentObject(e.DateCellEditor,a);e.DropdownCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-dropdownceditor");YAHOO.widget.BaseCellEditor._nCount++;e.DropdownCellEditor.superclass.constructor.call(this,j.type||"dropdown",j)};c.extend(e.DropdownCellEditor,a,{dropdownOptions:null,dropdown:null,multiple:false,size:null,renderForm:function(){var n=this.getContainerEl().appendChild(document.createElement("select"));n.style.zoom=1;if(this.multiple){n.multiple="multiple"}if(c.isNumber(this.size)){n.size=this.size}this.dropdown=n;if(c.isArray(this.dropdownOptions)){var o,m;for(var l=0,k=this.dropdownOptions.length;l<k;l++){o=this.dropdownOptions[l];m=document.createElement("option");m.value=(c.isValue(o.value))?o.value:o;m.innerHTML=(c.isValue(o.label))?o.label:o;m=n.appendChild(m)}if(this.disableBtns){this.handleDisabledBtns()}}},handleDisabledBtns:function(){if(this.multiple){i.addListener(this.dropdown,"blur",function(j){this.save()},this,true)}else{if(!b.ie){i.addListener(this.dropdown,"change",function(j){this.save()},this,true)}else{i.addListener(this.dropdown,"blur",function(j){this.save()},this,true);i.addListener(this.dropdown,"click",function(j){this.save()},this,true)}}},resetForm:function(){var s=this.dropdown.options,p=0,o=s.length;if(c.isArray(this.value)){var l=this.value,k=0,r=l.length,q={};for(;p<o;p++){s[p].selected=false;q[s[p].value]=s[p]}for(;k<r;k++){if(q[l[k]]){q[l[k]].selected=true}}}else{for(;p<o;p++){if(this.value==s[p].value){s[p].selected=true}}}},focus:function(){this.getDataTable()._focusEl(this.dropdown)},getInputValue:function(){var n=this.dropdown.options;if(this.multiple){var k=[],m=0,l=n.length;for(;m<l;m++){if(n[m].selected){k.push(n[m].value)}}return k}else{return n[n.selectedIndex].value}}});c.augmentObject(e.DropdownCellEditor,a);e.RadioCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-radioceditor");YAHOO.widget.BaseCellEditor._nCount++;e.RadioCellEditor.superclass.constructor.call(this,j.type||"radio",j)};c.extend(e.RadioCellEditor,a,{radios:null,radioOptions:null,renderForm:function(){if(c.isArray(this.radioOptions)){var k,l,r,o;for(var n=0,p=this.radioOptions.length;n<p;n++){k=this.radioOptions[n];l=c.isValue(k.value)?k.value:k;r=this.getId()+"-radio"+n;this.getContainerEl().innerHTML+='<input type="radio" name="'+this.getId()+'" value="'+l+'" id="'+r+'" />';o=this.getContainerEl().appendChild(document.createElement("label"));o.htmlFor=r;o.innerHTML=(c.isValue(k.label))?k.label:k}var q=[],s;for(var m=0;m<p;m++){s=this.getContainerEl().childNodes[m*2];q[q.length]=s}this.radios=q;if(this.disableBtns){this.handleDisabledBtns()}}else{}},handleDisabledBtns:function(){i.addListener(this.getContainerEl(),"click",function(j){if(i.getTarget(j).tagName.toLowerCase()==="input"){this.save()}},this,true)},resetForm:function(){for(var m=0,l=this.radios.length;m<l;m++){var k=this.radios[m];if(this.value==k.value){k.checked=true;return}}},focus:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){this.radios[l].focus();return}}},getInputValue:function(){for(var l=0,k=this.radios.length;l<k;l++){if(this.radios[l].checked){return this.radios[l].value}}}});c.augmentObject(e.RadioCellEditor,a);e.TextareaCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textareaceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextareaCellEditor.superclass.constructor.call(this,j.type||"textarea",j)};c.extend(e.TextareaCellEditor,a,{textarea:null,renderForm:function(){var j=this.getContainerEl().appendChild(document.createElement("textarea"));this.textarea=j;if(this.disableBtns){this.handleDisabledBtns()}},handleDisabledBtns:function(){i.addListener(this.textarea,"blur",function(j){this.save()},this,true)},move:function(){this.textarea.style.width=this.getTdEl().offsetWidth+"px";this.textarea.style.height="3em";YAHOO.widget.TextareaCellEditor.superclass.move.call(this)},resetForm:function(){this.textarea.value=this.value},focus:function(){this.getDataTable()._focusEl(this.textarea);this.textarea.select()},getInputValue:function(){return this.textarea.value}});c.augmentObject(e.TextareaCellEditor,a);e.TextboxCellEditor=function(j){j=j||{};this._sId=this._sId||d.generateId(null,"yui-textboxceditor");YAHOO.widget.BaseCellEditor._nCount++;e.TextboxCellEditor.superclass.constructor.call(this,j.type||"textbox",j)};c.extend(e.TextboxCellEditor,a,{textbox:null,renderForm:function(){var j;if(b.webkit>420){j=this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"))}else{j=this.getContainerEl().appendChild(document.createElement("input"))}j.type="text";this.textbox=j;i.addListener(j,"keypress",function(k){if((k.keyCode===13)){YAHOO.util.Event.preventDefault(k);this.save()}},this,true);if(this.disableBtns){this.handleDisabledBtns()}},move:function(){this.textbox.style.width=this.getTdEl().offsetWidth+"px";e.TextboxCellEditor.superclass.move.call(this)},resetForm:function(){this.textbox.value=c.isValue(this.value)?this.value.toString():""},focus:function(){this.getDataTable()._focusEl(this.textbox);this.textbox.select()},getInputValue:function(){return this.textbox.value}});c.augmentObject(e.TextboxCellEditor,a);h.Editors={checkbox:e.CheckboxCellEditor,date:e.DateCellEditor,dropdown:e.DropdownCellEditor,radio:e.RadioCellEditor,textarea:e.TextareaCellEditor,textbox:e.TextboxCellEditor};e.CellEditor=function(k,j){if(k&&h.Editors[k]){c.augmentObject(a,h.Editors[k]);return new h.Editors[k](j)}else{return new a(null,j)}};var g=e.CellEditor;c.augmentObject(g,a)})();YAHOO.register("datatable",YAHOO.widget.DataTable,{version:"2.9.0",build:"2800"});(function(){var d=YAHOO.util.Dom,f=YAHOO.lang,b=f.isObject,e=f.isFunction,c=f.isArray,a=f.isString;function g(k){var n=g.VALUE_UNLIMITED,l,h,i,j,m;k=b(k)?k:{};this.initConfig();this.initEvents();this.set("rowsPerPage",k.rowsPerPage,true);if(g.isNumeric(k.totalRecords)){this.set("totalRecords",k.totalRecords,true)}this.initUIComponents();for(l in k){if(k.hasOwnProperty(l)){this.set(l,k[l],true)}}h=this.get("initialPage");i=this.get("totalRecords");j=this.get("rowsPerPage");if(h>1&&j!==n){m=(h-1)*j;if(i===n||m<i){this.set("recordOffset",m,true)}}}f.augmentObject(g,{id:0,ID_BASE:"yui-pg",VALUE_UNLIMITED:-1,TEMPLATE_DEFAULT:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink}",TEMPLATE_ROWS_PER_PAGE:"{FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}",ui:{},isNumeric:function(h){return isFinite(+h)},toNumber:function(h){return isFinite(+h)?+h:null}},true);g.prototype={_containers:[],_batch:false,_pageChanged:false,_state:null,initConfig:function(){var h=g.VALUE_UNLIMITED;this.setAttributeConfig("rowsPerPage",{value:0,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("containers",{value:null,validator:function(l){if(!c(l)){l=[l]}for(var k=0,j=l.length;k<j;++k){if(a(l[k])||(b(l[k])&&l[k].nodeType===1)){continue}return false}return true},method:function(i){i=d.get(i);if(!c(i)){i=[i]}this._containers=i}});this.setAttributeConfig("totalRecords",{value:0,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("recordOffset",{value:0,validator:function(j){var i=this.get("totalRecords");if(g.isNumeric(j)){j=+j;return i===h||i>j||(i===0&&j===0)}return false},setter:g.toNumber});this.setAttributeConfig("initialPage",{value:1,validator:g.isNumeric,setter:g.toNumber});this.setAttributeConfig("template",{value:g.TEMPLATE_DEFAULT,validator:a});this.setAttributeConfig("containerClass",{value:"yui-pg-container",validator:a});this.setAttributeConfig("alwaysVisible",{value:true,validator:f.isBoolean});this.setAttributeConfig("updateOnChange",{value:false,validator:f.isBoolean});this.setAttributeConfig("id",{value:g.id++,readOnly:true});this.setAttributeConfig("rendered",{value:false,readOnly:true})},initUIComponents:function(){var j=g.ui,i,h;for(i in j){if(j.hasOwnProperty(i)){h=j[i];if(b(h)&&e(h.init)){h.init(this)}}}},initEvents:function(){this.createEvent("render");this.createEvent("rendered");this.createEvent("changeRequest");this.createEvent("pageChange");this.createEvent("beforeDestroy");this.createEvent("destroy");this._selfSubscribe()},_selfSubscribe:function(){this.subscribe("totalRecordsChange",this.updateVisibility,this,true);this.subscribe("alwaysVisibleChange",this.updateVisibility,this,true);this.subscribe("totalRecordsChange",this._handleStateChange,this,true);this.subscribe("recordOffsetChange",this._handleStateChange,this,true);this.subscribe("rowsPerPageChange",this._handleStateChange,this,true);this.subscribe("totalRecordsChange",this._syncRecordOffset,this,true)},_syncRecordOffset:function(k){var h=k.newValue,j,i;if(k.prevValue!==h){if(h!==g.VALUE_UNLIMITED){j=this.get("rowsPerPage");if(j&&this.get("recordOffset")>=h){i=this.getState({totalRecords:k.prevValue,recordOffset:this.get("recordOffset")});this.set("recordOffset",i.before.recordOffset);this._firePageChange(i)}}}},_handleStateChange:function(i){if(i.prevValue!==i.newValue){var j=this._state||{},h;j[i.type.replace(/Change$/,"")]=i.prevValue;h=this.getState(j);if(h.page!==h.before.page){if(this._batch){this._pageChanged=true}else{this._firePageChange(h)}}}},_firePageChange:function(h){if(b(h)){var i=h.before;delete h.before;this.fireEvent("pageChange",{type:"pageChange",prevValue:h.page,newValue:i.page,prevState:h,newState:i})}},render:function(){if(this.get("rendered")){return this}var l=this.get("template"),m=this.getState(),k=g.ID_BASE+this.get("id")+"-",j,h;for(j=0,h=this._containers.length;j<h;++j){this._renderTemplate(this._containers[j],l,k+j,true)}this.updateVisibility();if(this._containers.length){this.setAttributeConfig("rendered",{value:true});this.fireEvent("render",m);this.fireEvent("rendered",m)}return this},_renderTemplate:function(j,n,m,l){var p=this.get("containerClass"),o,k,h;if(!j){return}d.setStyle(j,"display","none");d.addClass(j,p);j.innerHTML=n.replace(/\{([a-z0-9_ \-]+)\}/gi,'<span class="yui-pg-ui yui-pg-ui-$1"></span>');o=d.getElementsByClassName("yui-pg-ui","span",j);for(k=0,h=o.length;k<h;++k){this.renderUIComponent(o[k],m)}if(!l){d.setStyle(j,"display","")}},renderUIComponent:function(h,m){var l=h.parentNode,k=/yui-pg-ui-(\w+)/.exec(h.className),j=k&&g.ui[k[1]],i;if(e(j)){i=new j(this);if(e(i.render)){l.replaceChild(i.render(m),h)}}return this},destroy:function(){this.fireEvent("beforeDestroy");this.fireEvent("destroy");this.setAttributeConfig("rendered",{value:false});this.unsubscribeAll()},updateVisibility:function(m){var p=this.get("alwaysVisible"),n,j,q,o,k,l,h;if(!m||m.type==="alwaysVisibleChange"||!p){n=this.get("totalRecords");j=true;q=this.get("rowsPerPage");o=this.get("rowsPerPageOptions");if(c(o)){for(k=0,l=o.length;k<l;++k){h=o[k];if(f.isNumber(h||h.value)){q=Math.min(q,(h.value||h))}}}if(n!==g.VALUE_UNLIMITED&&n<=q){j=false}j=j||p;for(k=0,l=this._containers.length;k<l;++k){d.setStyle(this._containers[k],"display",j?"":"none")}}},getContainerNodes:function(){return this._containers},getTotalPages:function(){var h=this.get("totalRecords"),i=this.get("rowsPerPage");if(!i){return null}if(h===g.VALUE_UNLIMITED){return g.VALUE_UNLIMITED}return Math.ceil(h/i)},hasPage:function(i){if(!f.isNumber(i)||i<1){return false}var h=this.getTotalPages();return(h===g.VALUE_UNLIMITED||h>=i)},getCurrentPage:function(){var h=this.get("rowsPerPage");if(!h||!this.get("totalRecords")){return 0}return Math.floor(this.get("recordOffset")/h)+1},hasNextPage:function(){var h=this.getCurrentPage(),i=this.getTotalPages();return h&&(i===g.VALUE_UNLIMITED||h<i)},getNextPage:function(){return this.hasNextPage()?this.getCurrentPage()+1:null},hasPreviousPage:function(){return(this.getCurrentPage()>1)},getPreviousPage:function(){return(this.hasPreviousPage()?this.getCurrentPage()-1:1)},getPageRecords:function(k){if(!f.isNumber(k)){k=this.getCurrentPage()}var j=this.get("rowsPerPage"),i=this.get("totalRecords"),l,h;if(!k||!j){return null}l=(k-1)*j;if(i!==g.VALUE_UNLIMITED){if(l>=i){return null}h=Math.min(l+j,i)-1}else{h=l+j-1}return[l,h]},setPage:function(i,h){if(this.hasPage(i)&&i!==this.getCurrentPage()){if(this.get("updateOnChange")||h){this.set("recordOffset",(i-1)*this.get("rowsPerPage"))}else{this.fireEvent("changeRequest",this.getState({page:i}))}}},getRowsPerPage:function(){return this.get("rowsPerPage")},setRowsPerPage:function(i,h){if(g.isNumeric(i)&&+i>0&&+i!==this.get("rowsPerPage")){if(this.get("updateOnChange")||h){this.set("rowsPerPage",i)}else{this.fireEvent("changeRequest",this.getState({rowsPerPage:+i}))}}},getTotalRecords:function(){return this.get("totalRecords")},setTotalRecords:function(i,h){if(g.isNumeric(i)&&+i>=0&&+i!==this.get("totalRecords")){if(this.get("updateOnChange")||h){this.set("totalRecords",i)}else{this.fireEvent("changeRequest",this.getState({totalRecords:+i}))}}},getStartIndex:function(){return this.get("recordOffset")},setStartIndex:function(i,h){if(g.isNumeric(i)&&+i>=0&&+i!==this.get("recordOffset")){if(this.get("updateOnChange")||h){this.set("recordOffset",i)}else{this.fireEvent("changeRequest",this.getState({recordOffset:+i}))}}},getState:function(n){var p=g.VALUE_UNLIMITED,l=Math,m=l.max,o=l.ceil,j,h,k;function i(s,q,r){if(s<=0||q===0){return 0}if(q===p||q>s){return s-(s%r)}return q-(q%r||r)}j={paginator:this,totalRecords:this.get("totalRecords"),rowsPerPage:this.get("rowsPerPage"),records:this.getPageRecords()};j.recordOffset=i(this.get("recordOffset"),j.totalRecords,j.rowsPerPage);j.page=o(j.recordOffset/j.rowsPerPage)+1;if(!n){return j}h={paginator:this,before:j,rowsPerPage:n.rowsPerPage||j.rowsPerPage,totalRecords:(g.isNumeric(n.totalRecords)?m(n.totalRecords,p):+j.totalRecords)};if(h.totalRecords===0){h.recordOffset=h.page=0}else{k=g.isNumeric(n.page)?(n.page-1)*h.rowsPerPage:g.isNumeric(n.recordOffset)?+n.recordOffset:j.recordOffset;h.recordOffset=i(k,h.totalRecords,h.rowsPerPage);h.page=o(h.recordOffset/h.rowsPerPage)+1}h.records=[h.recordOffset,h.recordOffset+h.rowsPerPage-1];if(h.totalRecords!==p&&h.recordOffset<h.totalRecords&&h.records&&h.records[1]>h.totalRecords-1){h.records[1]=h.totalRecords-1}return h},setState:function(i){if(b(i)){this._state=this.getState({});i={page:i.page,rowsPerPage:i.rowsPerPage,totalRecords:i.totalRecords,recordOffset:i.recordOffset};if(i.page&&i.recordOffset===undefined){i.recordOffset=(i.page-1)*(i.rowsPerPage||this.get("rowsPerPage"))}this._batch=true;this._pageChanged=false;for(var h in i){if(i.hasOwnProperty(h)&&this._configs.hasOwnProperty(h)){this.set(h,i[h])}}this._batch=false;if(this._pageChanged){this._pageChanged=false;this._firePageChange(this.getState(this._state))}}}};f.augmentProto(g,YAHOO.util.AttributeProvider);YAHOO.widget.Paginator=g})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.CurrentPageReport=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("pageReportTemplateChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("pageReportClassChange",this.update,this,true)};c.ui.CurrentPageReport.init=function(d){d.setAttributeConfig("pageReportClass",{value:"yui-pg-current",validator:b.isString});d.setAttributeConfig("pageReportTemplate",{value:"({currentPage} of {totalPages})",validator:b.isString});d.setAttributeConfig("pageReportValueGenerator",{value:function(g){var f=g.getCurrentPage(),e=g.getPageRecords();return{currentPage:e?f:0,totalPages:g.getTotalPages(),startIndex:e?e[0]:0,endIndex:e?e[1]:0,startRecord:e?e[0]+1:0,endRecord:e?e[1]+1:0,totalRecords:g.get("totalRecords")}},validator:b.isFunction})};c.ui.CurrentPageReport.sprintf=function(e,d){return e.replace(/\{([\w\s\-]+)\}/g,function(f,g){return(g in d)?d[g]:""})};c.ui.CurrentPageReport.prototype={span:null,render:function(d){this.span=document.createElement("span");this.span.className=this.paginator.get("pageReportClass");a(this.span,d+"-page-report");this.update();return this.span},update:function(d){if(d&&d.prevValue===d.newValue){return}this.span.innerHTML=c.ui.CurrentPageReport.sprintf(this.paginator.get("pageReportTemplate"),this.paginator.get("pageReportValueGenerator")(this.paginator))},destroy:function(){this.span.parentNode.removeChild(this.span);this.span=null}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.PageLinks=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("pageLinksChange",this.rebuild,this,true);d.subscribe("pageLinkClassChange",this.rebuild,this,true);d.subscribe("currentPageClassChange",this.rebuild,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("pageLinksContainerClassChange",this.rebuild,this,true)};c.ui.PageLinks.init=function(d){d.setAttributeConfig("pageLinkClass",{value:"yui-pg-page",validator:b.isString});d.setAttributeConfig("currentPageClass",{value:"yui-pg-current-page",validator:b.isString});d.setAttributeConfig("pageLinksContainerClass",{value:"yui-pg-pages",validator:b.isString});d.setAttributeConfig("pageLinks",{value:10,validator:c.isNumeric});d.setAttributeConfig("pageLabelBuilder",{value:function(e,f){return e},validator:b.isFunction});d.setAttributeConfig("pageTitleBuilder",{value:function(e,f){return"Page "+e},validator:b.isFunction})};c.ui.PageLinks.calculateRange=function(f,g,e){var j=c.VALUE_UNLIMITED,i,d,h;if(!f||e===0||g===0||(g===j&&e===j)){return[0,-1]}if(g!==j){e=e===j?g:Math.min(e,g)}i=Math.max(1,Math.ceil(f-(e/2)));if(g===j){d=i+e-1}else{d=Math.min(g,i+e-1)}h=e-(d-i+1);i=Math.max(1,i-h);return[i,d]};c.ui.PageLinks.prototype={current:0,container:null,render:function(d){var e=this.paginator;this.container=document.createElement("span");a(this.container,d+"-pages");this.container.className=e.get("pageLinksContainerClass");YAHOO.util.Event.on(this.container,"click",this.onClick,this,true);this.update({newValue:null,rebuild:true});return this.container},update:function(q){if(q&&q.prevValue===q.newValue){return}var g=this.paginator,m=g.getCurrentPage();if(this.current!==m||!m||q.rebuild){var r=g.get("pageLabelBuilder"),l=g.get("pageTitleBuilder"),k=c.ui.PageLinks.calculateRange(m,g.getTotalPages(),g.get("pageLinks")),f=k[0],h=k[1],o="",d,j,n;d='<a href="#" class="{class}" page="{page}" title="{title}">{label}</a>';n='<span class="{class}">{label}</span>';for(j=f;j<=h;++j){if(j===m){o+=b.substitute(n,{"class":g.get("currentPageClass")+" "+g.get("pageLinkClass"),label:r(j,g)})}else{o+=b.substitute(d,{"class":g.get("pageLinkClass"),page:j,label:r(j,g),title:l(j,g)})}}this.container.innerHTML=o}},rebuild:function(d){d.rebuild=true;this.update(d)},destroy:function(){YAHOO.util.Event.purgeElement(this.container,true);this.container.parentNode.removeChild(this.container);this.container=null},onClick:function(f){var d=YAHOO.util.Event.getTarget(f);if(d&&YAHOO.util.Dom.hasClass(d,this.paginator.get("pageLinkClass"))){YAHOO.util.Event.stopEvent(f);this.paginator.setPage(parseInt(d.getAttribute("page"),10))}}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.FirstPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("firstPageLinkLabelChange",this.update,this,true);d.subscribe("firstPageLinkClassChange",this.update,this,true)};c.ui.FirstPageLink.init=function(d){d.setAttributeConfig("firstPageLinkLabel",{value:"&lt;&lt; first",validator:b.isString});d.setAttributeConfig("firstPageLinkClass",{value:"yui-pg-first",validator:b.isString});d.setAttributeConfig("firstPageLinkTitle",{value:"First Page",validator:b.isString})};c.ui.FirstPageLink.prototype={current:null,link:null,span:null,render:function(e){var f=this.paginator,h=f.get("firstPageLinkClass"),d=f.get("firstPageLinkLabel"),g=f.get("firstPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-first-link");this.link.href="#";this.link.className=h;this.link.innerHTML=d;this.link.title=g;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-first-span");this.span.className=h;this.span.innerHTML=d;this.current=f.getCurrentPage()>1?this.link:this.span;return this.current},update:function(f){if(f&&f.prevValue===f.newValue){return}var d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link}}else{if(d&&this.current===this.link){d.replaceChild(this.span,this.current);this.current=this.span}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(1)}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.LastPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("lastPageLinkLabelChange",this.update,this,true);d.subscribe("lastPageLinkClassChange",this.update,this,true)};c.ui.LastPageLink.init=function(d){d.setAttributeConfig("lastPageLinkLabel",{value:"last &gt;&gt;",validator:b.isString});d.setAttributeConfig("lastPageLinkClass",{value:"yui-pg-last",validator:b.isString});d.setAttributeConfig("lastPageLinkTitle",{value:"Last Page",validator:b.isString})};c.ui.LastPageLink.prototype={current:null,link:null,span:null,na:null,render:function(e){var g=this.paginator,i=g.get("lastPageLinkClass"),d=g.get("lastPageLinkLabel"),f=g.getTotalPages(),h=g.get("lastPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");this.na=this.span.cloneNode(false);a(this.link,e+"-last-link");this.link.href="#";this.link.className=i;this.link.innerHTML=d;this.link.title=h;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-last-span");this.span.className=i;this.span.innerHTML=d;a(this.na,e+"-last-na");switch(f){case c.VALUE_UNLIMITED:this.current=this.na;break;case g.getCurrentPage():this.current=this.span;break;default:this.current=this.link}return this.current},update:function(f){if(f&&f.prevValue===f.newValue){return}var d=this.current?this.current.parentNode:null,g=this.link;if(d){switch(this.paginator.getTotalPages()){case c.VALUE_UNLIMITED:g=this.na;break;case this.paginator.getCurrentPage():g=this.span;break}if(this.current!==g){d.replaceChild(g,this.current);this.current=g}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getTotalPages())}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.NextPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("nextPageLinkLabelChange",this.update,this,true);d.subscribe("nextPageLinkClassChange",this.update,this,true)};c.ui.NextPageLink.init=function(d){d.setAttributeConfig("nextPageLinkLabel",{value:"next &gt;",validator:b.isString});d.setAttributeConfig("nextPageLinkClass",{value:"yui-pg-next",validator:b.isString});d.setAttributeConfig("nextPageLinkTitle",{value:"Next Page",validator:b.isString})};c.ui.NextPageLink.prototype={current:null,link:null,span:null,render:function(e){var g=this.paginator,i=g.get("nextPageLinkClass"),d=g.get("nextPageLinkLabel"),f=g.getTotalPages(),h=g.get("nextPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-next-link");this.link.href="#";this.link.className=i;this.link.innerHTML=d;this.link.title=h;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-next-span");this.span.className=i;this.span.innerHTML=d;this.current=g.getCurrentPage()===f?this.span:this.link;return this.current},update:function(g){if(g&&g.prevValue===g.newValue){return}var f=this.paginator.getTotalPages(),d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()!==f){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link}}else{if(this.current===this.link){if(d){d.replaceChild(this.span,this.current);this.current=this.span}}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getNextPage())}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.PreviousPageLink=function(d){this.paginator=d;d.subscribe("recordOffsetChange",this.update,this,true);d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.update,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("previousPageLinkLabelChange",this.update,this,true);d.subscribe("previousPageLinkClassChange",this.update,this,true)};c.ui.PreviousPageLink.init=function(d){d.setAttributeConfig("previousPageLinkLabel",{value:"&lt; prev",validator:b.isString});d.setAttributeConfig("previousPageLinkClass",{value:"yui-pg-previous",validator:b.isString});d.setAttributeConfig("previousPageLinkTitle",{value:"Previous Page",validator:b.isString})};c.ui.PreviousPageLink.prototype={current:null,link:null,span:null,render:function(e){var f=this.paginator,h=f.get("previousPageLinkClass"),d=f.get("previousPageLinkLabel"),g=f.get("previousPageLinkTitle");this.link=document.createElement("a");this.span=document.createElement("span");a(this.link,e+"-prev-link");this.link.href="#";this.link.className=h;this.link.innerHTML=d;this.link.title=g;YAHOO.util.Event.on(this.link,"click",this.onClick,this,true);a(this.span,e+"-prev-span");this.span.className=h;this.span.innerHTML=d;this.current=f.getCurrentPage()>1?this.link:this.span;return this.current},update:function(f){if(f&&f.prevValue===f.newValue){return}var d=this.current?this.current.parentNode:null;if(this.paginator.getCurrentPage()>1){if(d&&this.current===this.span){d.replaceChild(this.link,this.current);this.current=this.link}}else{if(d&&this.current===this.link){d.replaceChild(this.span,this.current);this.current=this.span}}},destroy:function(){YAHOO.util.Event.purgeElement(this.link);this.current.parentNode.removeChild(this.current);this.link=this.span=null},onClick:function(d){YAHOO.util.Event.stopEvent(d);this.paginator.setPage(this.paginator.getPreviousPage())}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.RowsPerPageDropdown=function(d){this.paginator=d;d.subscribe("rowsPerPageChange",this.update,this,true);d.subscribe("rowsPerPageOptionsChange",this.rebuild,this,true);d.subscribe("totalRecordsChange",this._handleTotalRecordsChange,this,true);d.subscribe("destroy",this.destroy,this,true);d.subscribe("rowsPerPageDropdownClassChange",this.rebuild,this,true)};c.ui.RowsPerPageDropdown.init=function(d){d.setAttributeConfig("rowsPerPageOptions",{value:[],validator:b.isArray});d.setAttributeConfig("rowsPerPageDropdownClass",{value:"yui-pg-rpp-options",validator:b.isString})};c.ui.RowsPerPageDropdown.prototype={select:null,all:null,render:function(d){this.select=document.createElement("select");a(this.select,d+"-rpp");this.select.className=this.paginator.get("rowsPerPageDropdownClass");this.select.title="Rows per page";YAHOO.util.Event.on(this.select,"change",this.onChange,this,true);this.rebuild();return this.select},rebuild:function(m){var d=this.paginator,g=this.select,n=d.get("rowsPerPageOptions"),f,l,h,j,k;this.all=null;for(j=0,k=n.length;j<k;++j){l=n[j];f=g.options[j]||g.appendChild(document.createElement("option"));h=b.isValue(l.value)?l.value:l;f.text=b.isValue(l.text)?l.text:l;if(b.isString(h)&&h.toLowerCase()==="all"){this.all=f;f.value=d.get("totalRecords")}else{f.value=h}}while(g.options.length>n.length){g.removeChild(g.firstChild)}this.update()},update:function(j){if(j&&j.prevValue===j.newValue){return}var h=this.paginator.get("rowsPerPage")+"",f=this.select.options,g,d;for(g=0,d=f.length;g<d;++g){if(f[g].value===h){f[g].selected=true;break}}},onChange:function(d){this.paginator.setRowsPerPage(parseInt(this.select.options[this.select.selectedIndex].value,10))},_handleTotalRecordsChange:function(d){if(!this.all||(d&&d.prevValue===d.newValue)){return}this.all.value=d.newValue;if(this.all.selected){this.paginator.set("rowsPerPage",d.newValue)}},destroy:function(){YAHOO.util.Event.purgeElement(this.select);this.select.parentNode.removeChild(this.select);this.select=null}}})();(function(){var c=YAHOO.widget.Paginator,b=YAHOO.lang,a=YAHOO.util.Dom.generateId;c.ui.JumpToPageDropdown=function(d){this.paginator=d;d.subscribe("rowsPerPageChange",this.rebuild,this,true);d.subscribe("rowsPerPageOptionsChange",this.rebuild,this,true);d.subscribe("pageChange",this.update,this,true);d.subscribe("totalRecordsChange",this.rebuild,this,true);d.subscribe("destroy",this.destroy,this,true)};c.ui.JumpToPageDropdown.init=function(d){d.setAttributeConfig("jumpToPageDropdownClass",{value:"yui-pg-jtp-options",validator:b.isString})};c.ui.JumpToPageDropdown.prototype={select:null,render:function(d){this.select=document.createElement("select");a(this.select,d+"-jtp");this.select.className=this.paginator.get("jumpToPageDropdownClass");this.select.title="Jump to page";YAHOO.util.Event.on(this.select,"change",this.onChange,this,true);this.rebuild();return this.select},rebuild:function(l){var k=this.paginator,j=this.select,f=k.getTotalPages(),h,g,d;this.all=null;for(g=0,d=f;g<d;++g){h=j.options[g]||j.appendChild(document.createElement("option"));h.innerHTML=g+1;h.value=g+1}for(g=f,d=j.options.length;g<d;g++){j.removeChild(j.lastChild)}this.update()},update:function(j){if(j&&j.prevValue===j.newValue){return}var h=this.paginator.getCurrentPage()+"",f=this.select.options,g,d;for(g=0,d=f.length;g<d;++g){if(f[g].value===h){f[g].selected=true;break}}},onChange:function(d){this.paginator.setPage(parseInt(this.select.options[this.select.selectedIndex].value,false))},destroy:function(){YAHOO.util.Event.purgeElement(this.select);this.select.parentNode.removeChild(this.select);this.select=null}}})();YAHOO.register("paginator",YAHOO.widget.Paginator,{version:"2.9.0",build:"2800"});
\ No newline at end of file
--- a/kallithea/public/js/yui.flot.js	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2483 +0,0 @@
-/**
-\file yui.flot.js
-\brief Javascript plotting library for YUI based on Flot v. 0.5.
-\details
-This file contains a port of Flot for YUI
-
-Copyright (c) 2009 Yahoo! Inc.  All rights reserved.  The copyrights embodied
-in the content of this file are licenced by Yahoo! Inc. under the BSD (revised)
-open source license.
-
-Requires yahoo-dom-event and datasource which you can get here:
-<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/datasource/datasource-min.js"></script>
-
-Datasource is optional, you only need it if one of your axes has its mode set to "time"
-*/
-
-(function() {
-	var L = YAHOO.lang;
-	var UA = YAHOO.env.ua;
-	var DOM = YAHOO.util.Dom;
-	var E = YAHOO.util.Event;
-
-	if(!DOM.createElementFromMarkup) {
-		DOM.createElementFromMarkup = function(markup) {
-			var p=document.createElement('div');
-			p.innerHTML = markup;
-			var e = p.firstChild;
-			return p.removeChild(e);
-		};
-	}
-
-	if(!DOM.removeElement) {
-		DOM.removeElement = function(el) {
-			return el.parentNode.removeChild(el);
-		};
-	}
-
-	function Plot(target_, data_, options_) {
-		// data is on the form:
-		//   [ series1, series2 ... ]
-		// where series is either just the data as [ [x1, y1], [x2, y2], ... ]
-		// or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" }
-
-		var series = [],
-			options = {
-				// the color theme used for graphs
-				colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
-				locale: "en",
-				legend: {
-					show: true,
-					noColumns: 1, // number of colums in legend table
-					labelFormatter: null, // fn: string -> string
-					labelBoxBorderColor: "#ccc", // border color for the little label boxes
-					container: null, // container (as jQuery object) to put legend in, null means default on top of graph
-					position: "ne", // position of default legend container within plot
-					margin: 5, // distance from grid edge to default legend container within plot
-					backgroundColor: null, // null means auto-detect
-					backgroundOpacity: 0.85 // set to 0 to avoid background
-				},
-				xaxis: {
-					mode: null, // null or "time"
-					min: null, // min. value to show, null means set automatically
-					max: null, // max. value to show, null means set automatically
-					autoscaleMargin: null, // margin in % to add if auto-setting min/max
-					ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
-					tickFormatter: null, // fn: number -> string
-					label: null,
-					labelWidth: null, // size of tick labels in pixels
-					labelHeight: null,
-
-					scaleType: 'linear',	// may be 'linear' or 'log'
-
-					// mode specific options
-					tickDecimals: null, // no. of decimals, null means auto
-					tickSize: null, // number or [number, "unit"]
-					minTickSize: null, // number or [number, "unit"]
-					timeformat: null // format string to use
-				},
-				yaxis: {
-					label: null,
-					autoscaleMargin: 0.02
-				},
-				x2axis: {
-					label: null,
-					autoscaleMargin: null
-				},
-				y2axis: {
-					label: null,
-					autoscaleMargin: 0.02
-				},			  
-				points: {
-					show: false,
-					radius: 3,
-					lineWidth: 2, // in pixels
-					fill: true,
-					fillColor: "#ffffff"
-				},
-				lines: {
-					// we don't put in show: false so we can see
-					// whether lines were actively disabled 
-					lineWidth: 2, // in pixels
-					fill: false,
-					fillColor: null
-				},
-				bars: {
-					show: false,
-					lineWidth: 2, // in pixels
-					barWidth: 1, // in units of the x axis
-					fill: true,
-					fillColor: null,
-					align: "left" // or "center" 
-				},
-				grid: {
-					show: true,
-					showLines: true,
-					color: "#545454", // primary color used for outline and labels
-					backgroundColor: null, // null for transparent, else color
-					tickColor: "#dddddd", // color used for the ticks
-					labelMargin: 5, // in pixels
-					labelFontSize: 16,
-					borderWidth: 2, // in pixels
-					borderColor: null, // set if different from the grid color
-					markings: null, // array of ranges or fn: axes -> array of ranges
-					markingsColor: "#f4f4f4",
-					markingsLineWidth: 2,
-					// interactive stuff
-					clickable: false,
-					hoverable: false,
-					autoHighlight: true, // highlight in case mouse is near
-					mouseActiveRadius: 10 // how far the mouse can be away to activate an item
-				},
-				selection: {
-					mode: null, // one of null, "x", "y" or "xy"
-					color: "#e8cfac"
-				},
-				crosshair: {
-					mode: null, // one of null, "x", "y" or "xy",
-					color: "#aa0000"
-				},
-				shadowSize: 3
-			},
-		canvas = null,	  // the canvas for the plot itself
-		overlay = null,	 // canvas for interactive stuff on top of plot
-		eventHolder = null, // jQuery object that events should be bound to
-		ctx = null, octx = null,
-		target = DOM.get(target_),
-		axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
-		plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
-		canvasWidth = 0, canvasHeight = 0,
-		plotWidth = 0, plotHeight = 0,
-		// dedicated to storing data for buggy standard compliance cases
-		workarounds = {};
-
-		this.setData = setData;
-		this.setupGrid = setupGrid;
-		this.draw = draw;
-		this.clearSelection = clearSelection;
-		this.setSelection = setSelection;
-		this.getCanvas = function() { return canvas; };
-		this.getPlotOffset = function() { return plotOffset; };
-		this.getData = function() { return series; };
-		this.getAxes = function() { return axes; };
-		this.setCrosshair = setCrosshair;
-		this.clearCrosshair = function () { setCrosshair(null); };
-		this.highlight = highlight;
-		this.unhighlight = unhighlight;
-
-		// initialize
-		parseOptions(options_);
-		setData(data_);
-		constructCanvas();
-		setupGrid();
-		draw();
-
-		var plot = this;
-
-		plot.createEvent('plotclick');
-		plot.createEvent('plothover');
-		plot.createEvent('plotselected');
-		plot.createEvent('plotunselected');
-
-
-
-		function setData(d) {
-			series = parseData(d);
-
-			fillInSeriesOptions();
-			processData();
-		}
-
-		function normalizeData(d) {
-			var possible_controls = ['x', 'time', 'date'];
-
-			if (L.isArray(d)) {
-				d = { data: d };
-			} else {
-				d = L.merge(d);
-			}
-
-			if(d.disabled) {
-				return undefined;
-			}
-
-			if (d.data.length === 0) {
-				return undefined;
-			}
-
-			var j, k;
-
-			// Make a copy so we don't obliterate the caller's data
-			var _data = [];
-
-			if (L.isArray(d.data[0])) {
-				for(j=0; j<d.data.length; j++) {
-					if(d.data[j]) {
-						var x = d.data[j][0];
-						var y = d.data[j][1];
-
-						if(L.isObject(x) && x.getTime) x = x.getTime()/1000;
-						else x = parseFloat(x);
-
-						if(L.isObject(y) && y.getTime) y = y.getTime()/1000;
-						else y = parseFloat(y);
-
-						_data.push({ x: x, y: y});
-					} else {
-						_data.push(d.data[j]);
-					}
-				}
-				d.control='x';
-				d.schema='y';
-			} else {
-				for(j=0; j<d.data.length; j++) {
-					_data.push({});
-					for(k in d.data[j]) {
-						if(L.isObject(d.data[j][k]) && d.data[j][k].getTime)
-							_data[j][k] = d.data[j][k].getTime()/1000;
-						else
-							_data[j][k] = parseFloat(d.data[j][k]);
-					}
-				}
-			}
-
-			d.data = _data;
-
-			if (!d.control) {
-				// try to guess the control field
-				for (j=0; j<possible_controls.length; j++) {
-					if(possible_controls[j] in d.data[0]) {
-						d.control = possible_controls[j];
-						break;
-					}
-				}
-			}
-
-			if (!d.schema) {
-				d.schema = [];
-				for(k in d.data[0]) {
-					if(!d.control) {
-						d.control = k;
-					}
-					if(k !== d.control) {
-						d.schema.push(k);
-					}
-				}
-			}
-
-			return L.merge(d, {dropped: []});
-		}
-
-		function markDroppedPoints(s) {
-			var l=s.data.length;
-
-			if(l <= canvasWidth/10 || options.dontDropPoints) {	// at least 10px per point
-				return s;
-			}
-
-			var dropperiod = 1-canvasWidth/10/l;
-			var drops = 0;
-			var points = l;
-
-			for(var j=0; j<l; j++) {
-				var x = s.data[j].x;
-				var y = s.data[j].y;
-
-				s.dropped[j] = (drops > 1);
-				if(s.dropped[j]) {
-					drops-=1;
-				}
-
-				if(!isNaN(x) && !isNaN(x))
-					drops+=dropperiod;
-				else {
-					drops=0;	// bonus for a null point
-					points--; 
-					dropperiod=1-canvasWidth/10/points;
-				}
-			}
-
-			return s;
-		}
-
-		function splitSeries(s) {
-			var res = [];
-
-			for(var k=0; k<s.schema.length; k++) {
-				res[k] = L.merge(s, {data: []});
-				if(s.label && L.isObject(s.label) && s.label[s.schema[k]]) {
-					res[k].label = s.label[s.schema[k]];
-				}
-				if(s.color && L.isObject(s.color) && s.color[s.schema[k]]) {
-					res[k].color = s.color[s.schema[k]];
-				}
-			}
-
-			for(var i=0; i<s.data.length; i++) {
-				var d = s.data[i];
-				for(k=0; k<s.schema.length; k++) {
-					var tuple = { x: d[s.control], y: d[s.schema[k]] };
-					res[k].data.push(tuple);
-					res[k].control='x';
-					res[k].schema='y';
-				}
-			}
-
-			return res;
-		}
-
-		function parseData(d) {
-			if(d.length === 0) {
-				return null;
-			}
-			
-			// get the canvas width so we know if we have to drop points
-			canvasWidth = parseInt(DOM.getStyle(target, 'width'), 10);
-
-			// First we normalise the data into a standard format
-			var s, res = [];
-			for (var i = 0; i < d.length; ++i) {
-				s = normalizeData(d[i]);
-				if(typeof s === 'undefined') 
-					continue;
-
-				if(L.isArray(s.schema)) {
-					s = splitSeries(s);
-				}
-				else {
-					s = [s];
-				}
-
-				for(var k=0; k<s.length; k++) {
-					s[k] = markDroppedPoints(s[k]);
-					res.push(s[k]);
-				}
-			}
-
-			return res;
-		}
-
-		function parseOptions(o) {
-			if (options.grid.borderColor == null)
-				options.grid.borderColor = options.grid.color;
-
-			if(typeof o === 'undefined') {
-				return;
-			}
-			o = YAHOO.lang.merge(o);
-			for(var k in o)	{
-				if(L.isObject(o[k]) && L.isObject(options[k])) {
-					L.augmentObject(options[k], o[k], true);
-					delete o[k];
-				}
-			}
-			L.augmentObject(options, o, true);
-		}
-
-		function fillInSeriesOptions() {
-			var i;
-
-			// collect what we already got of colors
-			var neededColors = series.length,
-				usedColors = [],
-				assignedColors = [];
-			for (i = 0; i < series.length; ++i) {
-				var sc = series[i].color;
-				if (sc != null) {
-					--neededColors;
-					if (typeof sc == "number")
-						assignedColors.push(sc);
-					else
-						usedColors.push(parseColor(series[i].color));
-				}
-			}
-
-			// we might need to generate more colors if higher indices
-			// are assigned
-			for (i = 0; i < assignedColors.length; ++i) {
-				neededColors = Math.max(neededColors, assignedColors[i] + 1);
-			}
-
-			// produce colors as needed
-			var colors = [], variation = 0;
-			i = 0;
-			while (colors.length < neededColors) {
-				var c;
-				if (options.colors.length == i) // check degenerate case
-					c = new Color(100, 100, 100);
-				else
-					c = parseColor(options.colors[i]);
-
-				// vary color if needed
-				var sign = variation % 2 == 1 ? -1 : 1;
-				var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
-				c.scale(factor, factor, factor);
-
-				// FIXME: if we're getting too close to something else,
-				// we should probably skip this one
-				colors.push(c);
-
-				++i;
-				if (i >= options.colors.length) {
-					i = 0;
-					++variation;
-				}
-			}
-
-			// fill in the options
-			var colori = 0, s;
-			for (i = 0; i < series.length; ++i) {
-				s = series[i];
-
-				// assign colors
-				if (s.color == null) {
-					s.color = colors[colori].toString();
-					++colori;
-				}
-				else if (typeof s.color == "number")
-					s.color = colors[s.color].toString();
-
-				// copy the rest
-				s.lines = L.merge(options.lines, s.lines || {});
-				s.points = L.merge(options.points, s.points || {});
-				s.bars = L.merge(options.bars, s.bars || {});
-
-				// turn on lines automatically in case nothing is set
-				if (s.lines.show == null && !s.bars.show && !s.points.show)
-					s.lines.show = true;
-
-				if (s.shadowSize == null)
-					s.shadowSize = options.shadowSize;
-
-				if (s.xaxis && s.xaxis == 2)
-					s.xaxis = axes.x2axis;
-				else
-					s.xaxis = axes.xaxis;
-				if (s.yaxis && s.yaxis >= 2) {
-					if(!axes['y' + s.yaxis + 'axis'])
-						axes['y' + s.yaxis + 'axis'] = {};
-					if(!options['y' + s.yaxis + 'axis'])
-						options['y' + s.yaxis + 'axis'] = { autoscaleMargin: 0.02 };
-					s.yaxis = axes['y' + s.yaxis + 'axis'];
-				}
-				else
-					s.yaxis = axes.yaxis;
-			}
-		}
-
-		function processData() {
-			var topSentry = Number.POSITIVE_INFINITY,
-				bottomSentry = Number.NEGATIVE_INFINITY,
-				axis;
-
-			for (axis in axes) {
-				axes[axis].datamin = topSentry;
-				axes[axis].datamax = bottomSentry;
-				axes[axis].min = options[axis].min;
-				axes[axis].max = options[axis].max;
-				axes[axis].used = false;
-			}
-
-			for (var i = 0; i < series.length; ++i) {
-				var s = series[i];
-				var data = s.data,
-					axisx = s.xaxis, axisy = s.yaxis,
-					xmin = topSentry, xmax = bottomSentry,
-					ymin = topSentry, ymax = bottomSentry,
-					x, y, p;
-
-				axisx.used = axisy.used = true;
-
-				if (s.bars.show) {
-					// make sure we got room for the bar
-					var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
-					xmin += delta;
-					xmax += delta + s.bars.barWidth;
-				}
-
-				for (var j = 0; j < data.length; ++j) {
-					p = data[j];
-
-					if(data[j] === null)
-						continue;
-
-					x = p.x;
-					y = p.y;
-
-					if(L.isObject(x) && x.getTime) {	// this is a Date object
-						x = x.getTime()/1000;
-					}
-
-					if(L.isObject(y) && y.getTime) {	// this is a Date object
-						y = y.getTime()/1000;
-					}
-
-					// convert to number
-					if (x != null && !isNaN(x = +x)) {
-						if (x < xmin)
-							xmin = x;
-						if (x > xmax)
-							xmax = x;
-					}
-					else
-						x = null;
-
-					if (y != null && !isNaN(y = +y)) {
-						if (y < ymin)
-							ymin = y;
-						if (y > ymax)
-							ymax = y;
-					}
-					else
-						y = null;
-
-					if (x == null || y == null)
-						data[j] = x = y = null; // mark this point invalid
-				}
-
-				axisx.datamin = Math.min(axisx.datamin, xmin);
-				axisx.datamax = Math.max(axisx.datamax, xmax);
-				axisy.datamin = Math.min(axisy.datamin, ymin);
-				axisy.datamax = Math.max(axisy.datamax, ymax);
-			}
-		}
-
-		function constructCanvas() {
-			function makeCanvas(width, height, container, style) {
-				var c = document.createElement('canvas');
-				c.width = width;
-				c.height = height;
-				if (typeof G_vmlCanvasManager !== 'undefined') // excanvas hack
-					c = G_vmlCanvasManager.initElement(c);
-
-				if(style) {
-					for(var k in style) {
-						c.style[k] = style[k];
-					}
-				}
-				container.appendChild(c);
-
-				return c;
-			}
-
-			canvasWidth = parseInt(DOM.getStyle(target, 'width'), 10);
-			canvasHeight = parseInt(DOM.getStyle(target, 'height'), 10);
-			target.innerHTML = ""; // clear target
-			target.style.position = "relative"; // for positioning labels and overlay
-
-			if (canvasWidth <= 0 || canvasHeight <= 0)
-				throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
-
-			if (YAHOO.env.ua.ie) {
-				G_vmlCanvasManager.init_(document);
-			}
-
-			// the canvas
-			canvas = makeCanvas(canvasWidth, canvasHeight, target);
-			ctx = canvas.getContext("2d");
-
-			// overlay canvas for interactive features
-			overlay = makeCanvas(canvasWidth, canvasHeight, target, { position: 'absolute', left: '0px', top: '0px' });
-			octx = overlay.getContext("2d");
-
-			// we include the canvas in the event holder too, because IE 7
-			// sometimes has trouble with the stacking order
-			eventHolder = [overlay, canvas];
-
-			// bind events
-			if (options.selection.mode != null || options.crosshair.mode != null || options.grid.hoverable) {
-				E.on(eventHolder, 'mousemove', onMouseMove);
-
-				if (options.selection.mode != null)
-					E.on(eventHolder, "mousedown", onMouseDown);
-			}
-
-			if (options.crosshair.mode != null)
-				E.on(eventHolder, "mouseout", onMouseOut);
-
-			if (options.grid.clickable)
-				E.on(eventHolder, "click", onClick);
-		}
-
-		function setupGrid() {
-			function setupAxis(axis, options, type) {
-				setRange(axis, options);
-				prepareTickGeneration(axis, options);
-				setTicks(axis, options);
-				// add transformation helpers
-				if (type == 'x') {
-					// data point to canvas coordinate
-					axis.p2c = function (p) { return (p - axis.min) * axis.scale; };
-					// canvas coordinate to data point
-					axis.c2p = function (c) { return axis.min + c / axis.scale; };
-				}
-				else {
-					axis.p2c = function (p) { return (axis.max - p) * axis.scale; };
-					axis.c2p = function (c) { return axis.max - c / axis.scale; };
-				}
-			}
-
-			for (var axis in axes)
-				setupAxis(axes[axis], options[axis], axis.charAt(0));
-
-			setSpacing();
-			if(options.grid.show)
-			  insertLabels();
-			insertLegend();
-			insertAxisLabels();
-		}
-
-		function setRange(axis, axisOptions) {
-			var min = axisOptions.min != null ? (axisOptions.scaleType == 'log' ? Math.log(axisOptions.min<=0?1:axisOptions.min) * Math.LOG10E : axisOptions.min) : axis.datamin;
-			var max = axisOptions.max != null ? (axisOptions.scaleType == 'log' ? Math.log(axisOptions.max) * Math.LOG10E : axisOptions.max) : axis.datamax;
-
-			if(axisOptions.mode === 'time') {
-				if(L.isObject(min) && min.getTime) min = min.getTime()/1000;
-				if(L.isObject(max) && max.getTime) max = max.getTime()/1000;
-			}
-
-			// degenerate case
-			if (min == Number.POSITIVE_INFINITY)
-				min = 0;
-			if (max == Number.NEGATIVE_INFINITY)
-				max = 1;
-
-			if (max - min == 0.0) {
-				// degenerate case
-				var widen = max == 0 ? 1 : 0.01;
-
-				if (axisOptions.min == null)
-					min -= widen;
-				// alway widen max if we couldn't widen min to ensure we
-				// don't fall into min == max which doesn't work
-				if (axisOptions.max == null || axisOptions.min != null)
-					max += widen;
-			}
-			else {
-				// consider autoscaling
-				var margin = axisOptions.autoscaleMargin;
-				if (margin != null) {
-					if (axisOptions.min == null) {
-						min -= (max - min) * margin;
-						// make sure we don't go below zero if all values
-						// are positive
-						if (min < 0 && axis.datamin >= 0)
-							min = 0;
-					}
-					if (axisOptions.max == null) {
-						max += (max - min) * margin;
-						if (max > 0 && axis.datamax <= 0)
-							max = 0;
-					}
-				}
-			}
-			axis.min = min;
-			axis.max = max;
-		}
-
-		function prepareTickGeneration(axis, axisOptions) {
-			// estimate number of ticks
-			var noTicks;
-			if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
-				noTicks = axisOptions.ticks;
-			else if (axis == axes.xaxis || axis == axes.x2axis)
-				noTicks = canvasWidth / 100;
-			else
-				noTicks = canvasHeight / 60;
-
-			var delta = (axis.max - axis.min) / noTicks;
-			var size, generator, unit, formatter, magn, norm;
-
-			if (axisOptions.mode == "time") {
-				// pretty handling of time
-
-				delta*=1000;
-
-				// map of app. size of time units in milliseconds
-				var timeUnitSize = {
-					"second": 1000,
-					"minute": 60 * 1000,
-					"hour": 60 * 60 * 1000,
-					"day": 24 * 60 * 60 * 1000,
-					"month": 30 * 24 * 60 * 60 * 1000,
-					"year": 365.2425 * 24 * 60 * 60 * 1000
-				};
-
-
-				// the allowed tick sizes, after 1 year we use
-				// an integer algorithm
-				var spec = [
-					[1, "second"], [2, "second"], [5, "second"], [10, "second"],
-					[30, "second"], 
-					[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
-					[30, "minute"], 
-					[1, "hour"], [2, "hour"], [4, "hour"],
-					[8, "hour"], [12, "hour"],
-					[1, "day"], [2, "day"], [3, "day"],
-					[0.25, "month"], [0.5, "month"], [1, "month"],
-					[2, "month"], [3, "month"], [6, "month"],
-					[1, "year"]
-				];
-
-				var minSize = 0;
-				if (axisOptions.minTickSize != null) {
-					if (typeof axisOptions.tickSize == "number")
-						minSize = axisOptions.tickSize;
-					else
-						minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
-				}
-
-				for (var i = 0; i < spec.length - 1; ++i)
-					if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
-								 + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
-					   && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
-						break;
-				size = spec[i][0];
-				unit = spec[i][1];
-
-				// special-case the possibility of several years
-				if (unit == "year") {
-					magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
-					norm = (delta / timeUnitSize.year) / magn;
-					if (norm < 1.5)
-						size = 1;
-					else if (norm < 3)
-						size = 2;
-					else if (norm < 7.5)
-						size = 5;
-					else
-						size = 10;
-
-					size *= magn;
-				}
-
-				if (axisOptions.tickSize) {
-					size = axisOptions.tickSize[0];
-					unit = axisOptions.tickSize[1];
-				}
-
-				generator = function(axis) {
-					var ticks = [],
-						tickSize = axis.tickSize[0], unit = axis.tickSize[1],
-						d = new Date(axis.min*1000);
-
-					var step = tickSize * timeUnitSize[unit];
-
-					if (unit == "second")
-						d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
-					if (unit == "minute")
-						d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
-					if (unit == "hour")
-						d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
-					if (unit == "month")
-						d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
-					if (unit == "year")
-						d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
-
-					// reset smaller components
-					d.setUTCMilliseconds(0);
-					if (step >= timeUnitSize.minute)
-						d.setUTCSeconds(0);
-					if (step >= timeUnitSize.hour)
-						d.setUTCMinutes(0);
-					if (step >= timeUnitSize.day)
-						d.setUTCHours(0);
-					if (step >= timeUnitSize.day * 4)
-						d.setUTCDate(1);
-					if (step >= timeUnitSize.year)
-						d.setUTCMonth(0);
-
-
-					var carry = 0, v = Number.NaN, prev;
-					do {
-						prev = v;
-						v = d.getTime();
-						ticks.push({ v: v/1000, label: axis.tickFormatter(v, axis) });
-						if (unit == "month") {
-							if (tickSize < 1) {
-								// a bit complicated - we'll divide the month
-								// up but we need to take care of fractions
-								// so we don't end up in the middle of a day
-								d.setUTCDate(1);
-								var start = d.getTime();
-								d.setUTCMonth(d.getUTCMonth() + 1);
-								var end = d.getTime();
-								d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
-								carry = d.getUTCHours();
-								d.setUTCHours(0);
-							}
-							else
-								d.setUTCMonth(d.getUTCMonth() + tickSize);
-						}
-						else if (unit == "year") {
-							d.setUTCFullYear(d.getUTCFullYear() + tickSize);
-						}
-						else
-							d.setTime(v + step);
-					} while (v < axis.max*1000 && v != prev);
-
-					return ticks;
-				};
-
-				formatter = function (v, axis) {
-					var d = new Date(v);
-
-					// first check global format
-					if (axisOptions.timeformat != null)
-						return YAHOO.util.Date.format(d, {format: axisOptions.timeformat}, options.locale);
-
-					var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
-					var span = axis.max - axis.min;
-					span*=1000;
-
-					if (t < timeUnitSize.minute)
-						var fmt = "%k:%M:%S";
-					else if (t < timeUnitSize.day) {
-						if (span < 2 * timeUnitSize.day)
-							fmt = "%k:%M";
-						else
-							fmt = "%b %d %k:%M";
-					}
-					else if (t < timeUnitSize.month)
-						fmt = "%b %d";
-					else if (t < timeUnitSize.year) {
-						if (span < timeUnitSize.year/2)
-							fmt = "%b";
-						else
-							fmt = "%b %Y";
-					}
-					else
-						fmt = "%Y";
-
-					return YAHOO.util.Date.format(d, {format: fmt}, axisOptions.timelang);
-				};
-			}
-			else {
-				// pretty rounding of base-10 numbers
-				var maxDec = axisOptions.tickDecimals;
-				var dec = -Math.floor(Math.log(delta) / Math.LN10);
-				if (maxDec != null && dec > maxDec)
-					dec = maxDec;
-
-				magn = Math.pow(10, -dec);
-				norm = delta / magn; // norm is between 1.0 and 10.0
-
-				if (norm < 1.5)
-					size = 1;
-				else if (norm < 3) {
-					size = 2;
-					// special case for 2.5, requires an extra decimal
-					if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
-						size = 2.5;
-						++dec;
-					}
-				}
-				else if (norm < 7.5)
-					size = 5;
-				else
-					size = 10;
-
-				size *= magn;
-
-				if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
-					size = axisOptions.minTickSize;
-
-				if (axisOptions.tickSize != null)
-					size = axisOptions.tickSize;
-
-				axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
-
-				generator = function (axis) {
-					var ticks = [];
-
-					// spew out all possible ticks
-					var start = floorInBase(axis.min, axis.tickSize),
-						i = 0, v = Number.NaN, prev;
-					do {
-						prev = v;
-						v = start + i * axis.tickSize;
-						var t=v;
-						if(axis.scaleType == 'log') {
-							t = Math.exp(t / Math.LOG10E);
-						}
-						ticks.push({ v: v, label: axis.tickFormatter(t, axis) });
-						++i;
-					} while (v < axis.max && v != prev);
-					return ticks;
-				};
-
-				formatter = function (v, axis) {
-					return v.toFixed(axis.tickDecimals);
-				};
-			}
-
-			axis.scaleType = axisOptions.scaleType;
-			axis.tickSize = unit ? [size, unit] : size;
-			axis.tickGenerator = generator;
-			if (L.isFunction(axisOptions.tickFormatter))
-				axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
-			else
-				axis.tickFormatter = formatter;
-			if (axisOptions.labelWidth != null)
-				axis.labelWidth = axisOptions.labelWidth;
-			if (axisOptions.labelHeight != null)
-				axis.labelHeight = axisOptions.labelHeight;
-		}
-
-		function setTicks(axis, axisOptions) {
-			axis.ticks = [];
-
-			if (!axis.used)
-				return;
-
-			if (axisOptions.ticks == null)
-				axis.ticks = axis.tickGenerator(axis);
-			else if (typeof axisOptions.ticks == "number") {
-				if (axisOptions.ticks > 0)
-					axis.ticks = axis.tickGenerator(axis);
-			}
-			else if (axisOptions.ticks) {
-				var ticks = axisOptions.ticks;
-
-				if (L.isFunction(ticks))
-					// generate the ticks
-					ticks = ticks({ min: axis.min, max: axis.max });
-
-				// clean up the user-supplied ticks, copy them over
-				var v;
-				for (var i = 0; i < ticks.length; ++i) {
-					var label = null;
-					var t = ticks[i];
-					if (typeof t == "object") {
-						v = t[0];
-						if (t.length > 1)
-							label = t[1];
-					}
-					else
-						v = t;
-					if (axisOptions.scaleType == 'log') {
-						if (label == null)
-							label = v;
-						v = Math.log(v) * Math.LOG10E;
-					}
-
-					if (label == null)
-						label = axis.tickFormatter(v, axis);
-					axis.ticks[i] = { v: v, label: label };
-				}
-			}
-
-			if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
-				// snap to ticks
-				if (axisOptions.min == null)
-					axis.min = Math.min(axis.min, axis.ticks[0].v);
-				if (axisOptions.max == null && axis.ticks.length > 1)
-					axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v);
-			}
-		}
-
-		function setSpacing() {
-			function measureXLabels(axis) {
-  			if(options.grid.show){
-  				// to avoid measuring the widths of the labels, we
-  				// construct fixed-size boxes and put the labels inside
-  				// them, we don't need the exact figures and the
-  				// fixed-size box content is easy to center
-  				if (axis.labelWidth == null)
-  					axis.labelWidth = canvasWidth / 6;
-
-  				// measure x label heights
-  				if (axis.labelHeight == null) {
-  					var labels = [];
-  					for (var i = 0; i < axis.ticks.length; ++i) {
-  						var l = axis.ticks[i].label;
-  						if (l)
-  							labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
-  					}
-
-  					axis.labelHeight = 0;
-  					if (labels.length > 0) {
-  						var dummyDiv = target.appendChild(DOM.createElementFromMarkup('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
-  										 + labels.join("") + '<div style="clear:left"></div></div>'));
-  						axis.labelHeight = dummyDiv.offsetHeight;
-  						target.removeChild(dummyDiv);
-  					}
-  				}
-			  }
-			  else{
-				  axis.labelHeight = 0;
-				  axis.labelWidth = 0;
-			  }
-			}
-
-			function measureYLabels(axis) {
-  			if(options.grid.show){
-  				if (axis.labelWidth == null || axis.labelHeight == null) {
-  					var labels = [], l;
-  					// calculate y label dimensions
-  					for (var i = 0; i < axis.ticks.length; ++i) {
-  						l = axis.ticks[i].label;
-  						if (l)
-  							labels.push('<div class="tickLabel">' + l + '</div>');
-  					}
-
-  					if (labels.length > 0) {
-  						var dummyDiv = target.appendChild(DOM.createElementFromMarkup('<div style="position:absolute;top:-10000px;font-size:smaller">'
-  										 + labels.join("") + '</div>'));
-  						if (axis.labelWidth == null)
-  							axis.labelWidth = dummyDiv.offsetWidth;
-  						if (axis.labelHeight == null)
-  							axis.labelHeight = dummyDiv.firstChild.offsetHeight;
-  						target.removeChild(dummyDiv);
-  					}
-
-  					if (axis.labelWidth == null)
-  						axis.labelWidth = 0;
-  					if (axis.labelHeight == null)
-  						axis.labelHeight = 0;
-  				}
-  		  }
-			  else{
-				  axis.labelHeight = 0;
-				  axis.labelWidth = 0;
-			  }
-			}
-
-			measureXLabels(axes.xaxis);
-			measureYLabels(axes.yaxis);
-			measureXLabels(axes.x2axis);
-			measureYLabels(axes.y2axis);
-			// get the most space needed around the grid for things
-			// that may stick out
-			var maxOutset = (options.grid.show) ? options.grid.borderWidth : 0;
-			for (var i = 0; i < series.length; ++i)
-				maxOutset = (Math.max(maxOutset, 2 * (((series[i].points.show) ? series[i].points.radius : 0 ) + series[i].points.lineWidth/2)));
-      
-			plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
-
-			var margin = options.grid.labelMargin + options.grid.borderWidth;
-
-			if (axes.xaxis.labelHeight > 0)
-				plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
-			if (axes.yaxis.labelWidth > 0)
-				plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
-
-			if (axes.x2axis.labelHeight > 0)
-				plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
-
-			if (axes.y2axis.labelWidth > 0)
-				plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
-
-			plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
-			plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
-
-			// precompute how much the axis is scaling a point in canvas space
-			for(var axis in axes) {
-				axes[axis].scale = (axis.charAt(0) == 'x' ? plotWidth : plotHeight) / (axes[axis].max - axes[axis].min);
-			}
-		}
-
-		function draw() {
-			drawGrid();
-			for (var i = 0; i < series.length; i++) {
-				drawSeries(series[i]);
-			}
-		}
-
-		function extractRange(ranges, coord) {
-			var firstAxis = coord + "axis",
-				secondaryAxis = coord + "2axis",
-				axis, from, to, reverse;
-
-			if (ranges[firstAxis]) {
-				axis = firstAxis;
-			}
-			else if (ranges[secondaryAxis]) {
-				axis = secondaryAxis;
-			}
-			else {
-				return { from: null, to: null, axis: axes[firstAxis] };
-			}
-
-			from = ranges[axis].from;
-			to = ranges[axis].to;
-
-			if (options[axis].scaleType == 'log') {
-				if (from != null)
-					from = Math.log(from) * Math.LOG10E;
-				if (to != null)
-					to = Math.log(to) * Math.LOG10E;
-			}
-
-			axis = axes[axis];
-
-			// auto-reverse as an added bonus
-			if (from != null && to != null && from > to)
-				return { from: to, to: from, axis: axis };
-
-			return { from: from, to: to, axis: axis };
-		}
-
-		function drawGrid() {
-			var i;
-
-			ctx.save();
-			ctx.clearRect(0, 0, canvasWidth, canvasHeight);
-			ctx.translate(plotOffset.left, plotOffset.top);
-
-			// draw background, if any
-			if (options.grid.backgroundColor) {
-				ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
-				ctx.fillRect(0, 0, plotWidth, plotHeight);
-			}
-
-			// draw markings
-			var markings = options.grid.markings;
-			if (markings) {
-				if (L.isFunction(markings))
-					markings = markings({ xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
-
-				for (i = 0; i < markings.length; ++i) {
-					var m = markings[i],
-						xrange = extractRange(m, "x"),
-						yrange = extractRange(m, "y");
-
-					// fill in missing
-					if (xrange.from == null)
-						xrange.from = xrange.axis.min;
-					if (xrange.to == null)
-						xrange.to = xrange.axis.max;
-					if (yrange.from == null)
-						yrange.from = yrange.axis.min;
-					if (yrange.to == null)
-						yrange.to = yrange.axis.max;
-
-					// clip
-					if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
-						yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
-						continue;
-
-					xrange.from = Math.max(xrange.from, xrange.axis.min);
-					xrange.to = Math.min(xrange.to, xrange.axis.max);
-					yrange.from = Math.max(yrange.from, yrange.axis.min);
-					yrange.to = Math.min(yrange.to, yrange.axis.max);
-
-					if (xrange.from == xrange.to && yrange.from == yrange.to)
-						continue;
-
-					// then draw
-					xrange.from = xrange.axis.p2c(xrange.from);
-					xrange.to = xrange.axis.p2c(xrange.to);
-					yrange.from = yrange.axis.p2c(yrange.from);
-					yrange.to = yrange.axis.p2c(yrange.to);
-
-					if (xrange.from == xrange.to || yrange.from == yrange.to) {
-						// draw line
-						ctx.strokeStyle = m.color || options.grid.markingsColor;
-						ctx.beginPath();
-						ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
-						ctx.moveTo(xrange.from, yrange.from);
-						ctx.lineTo(xrange.to, yrange.to);
-						ctx.stroke();
-					}
-					else {
-						// fill area
-						ctx.fillStyle = m.color || options.grid.markingsColor;
-						ctx.fillRect(xrange.from, yrange.to,
-									 xrange.to - xrange.from,
-									 yrange.from - yrange.to);
-					}
-				}
-			}
-
-			if(options.grid.show && options.grid.showLines) {
-				// draw the inner grid
-				ctx.lineWidth = 1;
-				ctx.strokeStyle = options.grid.tickColor;
-				ctx.beginPath();
-				var v, axis = axes.xaxis;
-				for (i = 0; i < axis.ticks.length; ++i) {
-					v = axis.ticks[i].v;
-					if (v <= axis.min || v >= axes.xaxis.max)
-						continue;   // skip those lying on the axes
-	
-					ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
-					ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
-				}
-	
-				axis = axes.yaxis;
-				for (i = 0; i < axis.ticks.length; ++i) {
-					v = axis.ticks[i].v;
-					if (v <= axis.min || v >= axis.max)
-						continue;
-	
-					ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
-					ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
-				}
-	
-				axis = axes.x2axis;
-				for (i = 0; i < axis.ticks.length; ++i) {
-					v = axis.ticks[i].v;
-					if (v <= axis.min || v >= axis.max)
-						continue;
-	
-					ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
-					ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
-				}
-	
-				axis = axes.y2axis;
-				for (i = 0; i < axis.ticks.length; ++i) {
-					v = axis.ticks[i].v;
-					if (v <= axis.min || v >= axis.max)
-						continue;
-	
-					ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
-					ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
-				}
-	
-				ctx.stroke();
-			}
-
-			if (options.grid.show && options.grid.borderWidth) {
-				// draw border
-				var bw = options.grid.borderWidth;
-				ctx.lineWidth = bw;
-				ctx.strokeStyle = options.grid.borderColor;
-				ctx.lineJoin = "round";
-				ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
-			}
-
-			ctx.restore();
-		}
-
-		function insertLabels() {
-			DOM.getElementsByClassName("tickLabels", "div", target, DOM.removeElement);
-
-			var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
-
-			function addLabels(axis, labelGenerator) {
-				for (var i = 0; i < axis.ticks.length; ++i) {
-					var tick = axis.ticks[i];
-					if (!tick.label || tick.v < axis.min || tick.v > axis.max)
-						continue;
-					html.push(labelGenerator(tick, axis));
-				}
-			}
-
-			var margin = options.grid.labelMargin + options.grid.borderWidth;
-
-			addLabels(axes.xaxis, function (tick, axis) {
-				return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
-			});
-
-
-			addLabels(axes.yaxis, function (tick, axis) {
-				return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
-			});
-
-			addLabels(axes.x2axis, function (tick, axis) {
-				return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
-			});
-
-			addLabels(axes.y2axis, function (tick, axis) {
-				return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
-			});
-
-			html.push('</div>');
-
-			target.appendChild(DOM.createElementFromMarkup(html.join("")));
-		}
-
-		function insertAxisLabels() {
-			var xLocation, yLocation;
-			if( options.xaxis.label ) {
-				yLocation = plotOffset.top + plotHeight + ( axes.xaxis.labelHeight * 1.5 );
-				xLocation = plotOffset.left;
-				DOM.getElementsByClassName("xaxislabel", "div", target, DOM.removeElement);
-				target.appendChild(
-					DOM.createElementFromMarkup(
-						"<div class='xaxislabel' style='color:" + options.grid.color + ";width:" + plotWidth + "px;"
-							+ "text-align:center;position:absolute;top:" + yLocation + "px;left:" + xLocation + "px;'>"
-							+ options.xaxis.label + "</div>"
-					)
-				);
-			}
-			if( options.yaxis.label ) {
-				xLocation = plotOffset.left - ( axes.yaxis.labelWidth * 2 ) - options.grid.labelFontSize;
-				yLocation = plotOffset.top + plotHeight/2;
-				DOM.getElementsByClassName("yaxislabel", "div", target, DOM.removeElement);
-
-				target.appendChild(
-					DOM.createElementFromMarkup(
-						"<div class='yaxislabel' style='-moz-transform:rotate(270deg);-webkit-transform:rotate(270deg);writing-mode:tb-rl;filter:flipV flipH;color:" + options.grid.color + ";"
-							+ "text-align:center;position:absolute;top:" + yLocation + "px;left:" + xLocation + "px;'>"
-							+ options.yaxis.label + "</div>")
-				);
-			}
-	        }
-
-		function drawSeries(series) {
-			if (series.lines.show)
-				drawSeriesLines(series);
-			if (series.bars.show)
-				drawSeriesBars(series);
-			if (series.points.show)
-				drawSeriesPoints(series);
-		}
-
-		function drawSeriesLines(series) {
-			function plotLine(data, xoffset, yoffset, axisx, axisy) {
-				var prev = null, cur=null, drawx = null, drawy = null;
-
-				ctx.beginPath();
-				for (var i = 0; i < data.length; i++) {
-					prev = cur;
-					cur = data[i];
-
-					if(prev == null || cur == null)
-						continue;
-
-					var x1 = prev.x, y1 = prev.y,
-						x2 = cur.x, y2 = cur.y;
-
-					// clip with ymin
-					if (y1 <= y2 && y1 < axisy.min) {
-						if (y2 < axisy.min)
-							continue;   // line segment is outside
-						// compute new intersection point
-						x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y1 = axisy.min;
-					}
-					else if (y2 <= y1 && y2 < axisy.min) {
-						if (y1 < axisy.min)
-							continue;
-						x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y2 = axisy.min;
-					}
-
-					// clip with ymax
-					if (y1 >= y2 && y1 > axisy.max) {
-						if (y2 > axisy.max)
-							continue;
-						x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y1 = axisy.max;
-					}
-					else if (y2 >= y1 && y2 > axisy.max) {
-						if (y1 > axisy.max)
-							continue;
-						x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y2 = axisy.max;
-					}
-
-					// clip with xmin
-					if (x1 <= x2 && x1 < axisx.min) {
-						if (x2 < axisx.min)
-							continue;
-						y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x1 = axisx.min;
-					}
-					else if (x2 <= x1 && x2 < axisx.min) {
-						if (x1 < axisx.min)
-							continue;
-						y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x2 = axisx.min;
-					}
-
-					// clip with xmax
-					if (x1 >= x2 && x1 > axisx.max) {
-						if (x2 > axisx.max)
-							continue;
-						y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x1 = axisx.max;
-					}
-					else if (x2 >= x1 && x2 > axisx.max) {
-						if (x1 > axisx.max)
-							continue;
-						y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x2 = axisx.max;
-					}
-
-					if (drawx != axisx.p2c(x1) + xoffset || drawy != axisy.p2c(y1) + yoffset)
-						ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
-
-					drawx = axisx.p2c(x2) + xoffset;
-					drawy = axisy.p2c(y2) + yoffset;
-					ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
-				}
-				ctx.stroke();
-			}
-
-			function plotLineArea(data, axisx, axisy) {
-				var prev, cur = null,
-					bottom = Math.min(Math.max(0, axisy.min), axisy.max),
-					top, lastX = 0, areaOpen = false;
-
-				for (var i = 0; i < data.length; i++) {
-					prev = cur;
-					cur = data[i];
-
-					if (areaOpen && x1 != null && x2 == null) {
-						// close area
-						ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
-						ctx.fill();
-						areaOpen = false;
-						continue;
-					}
-
-					if (prev == null || cur == null) {
-						if(areaOpen) {
-							ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
-							ctx.fill();
-						}
-						areaOpen = false;
-						continue;
-					}
-
-					var x1 = prev.x, y1 = prev.y,
-						x2 = cur.x, y2 = cur.y;
-
-					// clip x values
-
-					// clip with xmin
-					if (x1 <= x2 && x1 < axisx.min) {
-						if (x2 < axisx.min)
-							continue;
-						y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x1 = axisx.min;
-					}
-					else if (x2 <= x1 && x2 < axisx.min) {
-						if (x1 < axisx.min)
-							continue;
-						y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x2 = axisx.min;
-					}
-
-					// clip with xmax
-					if (x1 >= x2 && x1 > axisx.max) {
-						if (x2 > axisx.max)
-							continue;
-						y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x1 = axisx.max;
-					}
-					else if (x2 >= x1 && x2 > axisx.max) {
-						if (x1 > axisx.max)
-							continue;
-						y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
-						x2 = axisx.max;
-					}
-
-					if (!areaOpen) {
-						// open area
-						ctx.beginPath();
-						ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
-						areaOpen = true;
-					}
-
-					// now first check the case where both is outside
-					if (y1 >= axisy.max && y2 >= axisy.max) {
-						ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
-						ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
-						lastX = x2;
-						continue;
-					}
-					else if (y1 <= axisy.min && y2 <= axisy.min) {
-						ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
-						ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
-						lastX = x2;
-						continue;
-					}
-
-					// else it's a bit more complicated, there might
-					// be two rectangles and two triangles we need to fill
-					// in; to find these keep track of the current x values
-					var x1old = x1, x2old = x2;
-
-					// and clip the y values, without shortcutting
-
-					// clip with ymin
-					if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
-						x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y1 = axisy.min;
-					}
-					else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
-						x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y2 = axisy.min;
-					}
-
-					// clip with ymax
-					if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
-						x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y1 = axisy.max;
-					}
-					else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
-						x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
-						y2 = axisy.max;
-					}
-
-
-					// if the x value was changed we got a rectangle
-					// to fill
-					if (x1 != x1old) {
-						if (y1 <= axisy.min)
-							top = axisy.min;
-						else
-							top = axisy.max;
-
-						ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
-						ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
-					}
-
-					// fill the triangles
-					ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
-					ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
-
-					// fill the other rectangle if it's there
-					if (x2 != x2old) {
-						if (y2 <= axisy.min)
-							top = axisy.min;
-						else
-							top = axisy.max;
-
-						ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
-						ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
-					}
-
-					lastX = Math.max(x2, x2old);
-				}
-
-				if (areaOpen) {
-					ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
-					ctx.fill();
-				}
-			}
-
-			ctx.save();
-			ctx.translate(plotOffset.left, plotOffset.top);
-			ctx.lineJoin = "round";
-
-			var lw = series.lines.lineWidth,
-				sw = series.shadowSize;
-			// FIXME: consider another form of shadow when filling is turned on
-			if (lw > 0 && sw > 0) {
-				// draw shadow as a thick and thin line with transparency
-				ctx.lineWidth = sw;
-				ctx.strokeStyle = "rgba(0,0,0,0.1)";
-				var xoffset = 1;
-				plotLine(series.data, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis);
-				ctx.lineWidth = sw/2;
-				plotLine(series.data, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis);
-			}
-
-			ctx.lineWidth = lw;
-			ctx.strokeStyle = series.color;
-			var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
-			if (fillStyle) {
-				ctx.fillStyle = fillStyle;
-				plotLineArea(series.data, series.xaxis, series.yaxis);
-			}
-
-			if (lw > 0)
-				plotLine(series.data, 0, 0, series.xaxis, series.yaxis);
-			ctx.restore();
-		}
-
-		function drawSeriesPoints(series) {
-			function plotPoints(data, radius, fillStyle, offset, circumference, axisx, axisy) {
-				for (var i = 0; i < data.length; i++) {
-					if (data[i] == null || series.dropped[i])
-						continue;
-
-					var x = data[i].x, y = data[i].y;
-					if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
-						continue;
-
-					ctx.beginPath();
-					ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, true);
-					if (fillStyle) {
-						ctx.fillStyle = fillStyle;
-						ctx.fill();
-					}
-					ctx.stroke();
-				}
-			}
-
-			ctx.save();
-			ctx.translate(plotOffset.left, plotOffset.top);
-
-			var lw = series.lines.lineWidth,
-				sw = series.shadowSize,
-				radius = series.points.radius;
-			if (lw > 0 && sw > 0) {
-				// draw shadow in two steps
-				var w = sw / 2;
-				ctx.lineWidth = w;
-				ctx.strokeStyle = "rgba(0,0,0,0.1)";
-				plotPoints(series.data, radius, null, w + w/2, 2 * Math.PI,
-						   series.xaxis, series.yaxis);
-
-				ctx.strokeStyle = "rgba(0,0,0,0.2)";
-				plotPoints(series.data, radius, null, w/2, 2 * Math.PI,
-						   series.xaxis, series.yaxis);
-			}
-
-			ctx.lineWidth = lw;
-			ctx.strokeStyle = series.color;
-			plotPoints(series.data, radius,
-					   getFillStyle(series.points, series.color), 0, 2 * Math.PI,
-					   series.xaxis, series.yaxis);
-			ctx.restore();
-		}
-
-		function drawBar(x, y, barLeft, barRight, offset, fill, axisx, axisy, c) {
-			var drawLeft = true, drawRight = true,
-				drawTop = true, drawBottom = false,
-				left = x + barLeft, right = x + barRight,
-				bottom = 0, top = y;
-
-			// account for negative bars
-			if (top < bottom) {
-				top = 0;
-				bottom = y;
-				drawBottom = true;
-				drawTop = false;
-			}
-		   
-			// clip
-			if (right < axisx.min || left > axisx.max ||
-				top < axisy.min || bottom > axisy.max)
-				return;
-
-			if (left < axisx.min) {
-				left = axisx.min;
-				drawLeft = false;
-			}
-
-			if (right > axisx.max) {
-				right = axisx.max;
-				drawRight = false;
-			}
-
-			if (bottom < axisy.min) {
-				bottom = axisy.min;
-				drawBottom = false;
-			}
-
-			if (top > axisy.max) {
-				top = axisy.max;
-				drawTop = false;
-			}
-
-			left = axisx.p2c(left);
-			bottom = axisy.p2c(bottom);
-			right = axisx.p2c(right);
-			top = axisy.p2c(top);
-
-			// fill the bar
-			if (fill) {
-				c.beginPath();
-				c.moveTo(left, bottom);
-				c.lineTo(left, top);
-				c.lineTo(right, top);
-				c.lineTo(right, bottom);
-				if(typeof fill === 'function') {
-					c.fillStyle = fill(bottom, top);
-				} else if(typeof fill === 'string') {
-					c.fillStyle = fill;
-				}
-				c.fill();
-			}
-
-			// draw outline
-			if (drawLeft || drawRight || drawTop || drawBottom) {
-				c.beginPath();
-
-				// FIXME: inline moveTo is buggy with excanvas
-				c.moveTo(left, bottom + offset);
-				if (drawLeft)
-					c.lineTo(left, top + offset);
-				else
-					c.moveTo(left, top + offset);
-				if (drawTop)
-					c.lineTo(right, top + offset);
-				else
-					c.moveTo(right, top + offset);
-				if (drawRight)
-					c.lineTo(right, bottom + offset);
-				else
-					c.moveTo(right, bottom + offset);
-				if (drawBottom)
-					c.lineTo(left, bottom + offset);
-				else
-					c.moveTo(left, bottom + offset);
-				c.stroke();
-			}
-		}
-
-		function drawSeriesBars(series) {
-			function plotBars(data, barLeft, barRight, offset, fill, axisx, axisy) {
-
-				for (var i = 0; i < data.length; i++) {
-					if (data[i] == null)
-						continue;
-					drawBar(data[i].x, data[i].y, barLeft, barRight, offset, fill, axisx, axisy, ctx);
-				}
-			}
-
-			ctx.save();
-			ctx.translate(plotOffset.left, plotOffset.top);
-
-			// FIXME: figure out a way to add shadows (for instance along the right edge)
-			ctx.lineWidth = series.bars.lineWidth;
-			ctx.strokeStyle = series.color;
-			var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
-			var fill = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
-			plotBars(series.data, barLeft, barLeft + series.bars.barWidth, 0, fill, series.xaxis, series.yaxis);
-			ctx.restore();
-		}
-
-		function getFillStyle(filloptions, seriesColor, bottom, top) {
-			var fill = filloptions.fill;
-			if (!fill)
-				return null;
-
-			if (filloptions.fillColor)
-				return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
-
-			var c = parseColor(seriesColor);
-			c.a = typeof fill == "number" ? fill : 0.4;
-			c.normalize();
-			return c.toString();
-		}
-
-		function insertLegend() {
-			DOM.getElementsByClassName("legend", "div", target, DOM.removeElement);
-
-			if (!options.legend.show)
-				return;
-
-			var fragments = [], rowStarted = false,
-				lf = options.legend.labelFormatter, s, label;
-			for (var i = 0; i < series.length; ++i) {
-				s = series[i];
-				label = s.label;
-				if (!label)
-					continue;
-
-				if (i % options.legend.noColumns == 0) {
-					if (rowStarted)
-						fragments.push('</tr>');
-					fragments.push('<tr>');
-					rowStarted = true;
-				}
-
-				if (lf)
-					label = lf(label, s);
-
-				fragments.push(
-					'<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
-					'<td class="legendLabel">' + label + '</td>');
-			}
-			if (rowStarted)
-				fragments.push('</tr>');
-
-			if (fragments.length == 0)
-				return;
-
-			var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
-			if (options.legend.container != null)
-				DOM.get(options.legend.container).innerHTML = table;
-			else {
-				var pos = "",
-					p = options.legend.position,
-					m = options.legend.margin;
-				if (m[0] == null)
-					m = [m, m];
-				if (p.charAt(0) == "n")
-					pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
-				else if (p.charAt(0) == "s")
-					pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
-				if (p.charAt(1) == "e")
-					pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
-				else if (p.charAt(1) == "w")
-					pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
-				var legend = target.appendChild(DOM.createElementFromMarkup('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>'));
-				if (options.legend.backgroundOpacity != 0.0) {
-					// put in the transparent background
-					// separately to avoid blended labels and
-					// label boxes
-					var c = options.legend.backgroundColor;
-					if (c == null) {
-						var tmp;
-						if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string")
-							tmp = options.grid.backgroundColor;
-						else
-							tmp = extractColor(legend);
-						c = parseColor(tmp).adjust(null, null, null, 1).toString();
-					}
-					var div = legend.firstChild;
-					var _el = DOM.insertBefore(
-								DOM.createElementFromMarkup('<div style="position:absolute;width:' + parseInt(DOM.getStyle(div, 'width'), 10)
-											+ 'px;height:' + parseInt(DOM.getStyle(div, 'height'), 10) + 'px;'
-											+ pos +'background-color:' + c + ';"> </div>'),
-								legend
-							);
-					DOM.setStyle(_el, 'opacity', options.legend.backgroundOpacity);
-				}
-			}
-		}
-
-
-		// interactive features
-
-		var lastMousePos = { pageX: null, pageY: null },
-			selection = {
-				first: { x: -1, y: -1}, second: { x: -1, y: -1},
-				show: false, active: false },
-			crosshair = { pos: { x: -1, y: -1 } },
-			highlights = [],
-			clickIsMouseUp = false,
-			redrawTimeout = null,
-			hoverTimeout = null;
-
-		// Returns the data item the mouse is over, or null if none is found
-		function findNearbyItem(mouseX, mouseY) {
-			var maxDistance = options.grid.mouseActiveRadius,
-				lowestDistance = maxDistance * maxDistance + 1,
-				item = null, foundPoint = false, j, x, y;
-
-			function result(i, j) {
-				return {
-					datapoint: series[i].data[j],
-					dataIndex: j,
-					series: series[i],
-					seriesIndex: i
-				};
-			}
-
-			for (var i = 0; i < series.length; ++i) {
-				var s = series[i],
-					axisx = s.xaxis,
-					axisy = s.yaxis,
-					mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
-					my = axisy.c2p(mouseY),
-					maxx = maxDistance / axisx.scale,
-					maxy = maxDistance / axisy.scale;
-
-				var data = s.data;
-
-				if (s.lines.show || s.points.show) {
-					for (j = 0; j < data.length; j++ ) {
-						if (data[j] == null)
-							continue;
-
-						x = data[j].x;
-						y = data[j].y;
-
-						// For points and lines, the cursor must be within a
-						// certain distance to the data point
-						if (x - mx > maxx || x - mx < -maxx ||
-							y - my > maxy || y - my < -maxy)
-							continue;
-
-						// We have to calculate distances in pixels, not in
-						// data units, because the scales of the axes may be different
-						var dx = Math.abs(axisx.p2c(x) - mouseX),
-							dy = Math.abs(axisy.p2c(y) - mouseY),
-							dist = dx * dx + dy * dy; // no idea in taking sqrt
-						if (dist < lowestDistance) {
-							lowestDistance = dist;
-							item = result(i, j);
-						}
-					}
-				}
-
-				if (s.bars.show && !item) { // no other point can be nearby
-					var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
-						barRight = barLeft + s.bars.barWidth;
-
-					for (j = 0; j < data.length; j++) {
-						x = data[j].x;
-						y = data[j].y;
-						if (x == null)
-							continue;
-  
-						// for a bar graph, the cursor must be inside the bar
-						if ((mx >= x + barLeft && mx <= x + barRight &&
-							 my >= Math.min(0, y) && my <= Math.max(0, y)))
-								item = result(i, j);
-					}
-				}
-			}
-
-			return item;
-		}
-
-		function onMouseMove(e) {
-			lastMousePos.pageX = E.getPageX(e);
-			lastMousePos.pageY = E.getPageY(e);
-
-			if (options.grid.hoverable)
-				triggerClickHoverEvent("plothover", lastMousePos);
-
-			if (options.crosshair.mode != null) {
-				if (!selection.active) {
-					setPositionFromEvent(crosshair.pos, lastMousePos);
-					triggerRedrawOverlay();
-				}
-				else
-					crosshair.pos.x = -1; // hide the crosshair while selecting
-			}
-
-			if (selection.active) {
-				updateSelection(lastMousePos);
-			}
-		}
-
-		function onMouseDown(e) {
-			var button = e.which || e.button;
-			if (button != 1)  // only accept left-click
-				return;
-
-			// cancel out any text selections
-			document.body.focus();
-
-			// prevent text selection and drag in old-school browsers
-			if (document.onselectstart !== undefined && workarounds.onselectstart == null) {
-				workarounds.onselectstart = document.onselectstart;
-				document.onselectstart = function () { return false; };
-			}
-			if (document.ondrag !== undefined && workarounds.ondrag == null) {
-				workarounds.ondrag = document.ondrag;
-				document.ondrag = function () { return false; };
-			}
-
-			var mousePos = {pageX: E.getPageX(e), pageY: E.getPageY(e)};
-			setSelectionPos(selection.first, mousePos);
-
-			lastMousePos.pageX = null;
-			selection.active = true;
-			E.on(document, "mouseup", onSelectionMouseUp);
-		}
-
-		function onMouseOut(e) {
-			if (options.crosshair.mode != null && crosshair.pos.x != -1) {
-				crosshair.pos.x = -1;
-				triggerRedrawOverlay();
-			}
-		}
-
-		function onClick(e) {
-			if (clickIsMouseUp) {
-				clickIsMouseUp = false;
-				return;
-			}
-
-			var mousePos = {pageX: E.getPageX(e), pageY: E.getPageY(e)};
-			triggerClickHoverEvent("plotclick", mousePos);
-		}
-
-		// trigger click or hover event (they send the same parameters
-		// so we share their code)
-		function triggerClickHoverEvent(eventname, event) {
-			var offset = DOM.getXY(eventHolder[0]),
-				pos = { pageX: event.pageX, pageY: event.pageY },
-				canvasX = event.pageX - offset[0] - plotOffset.left,
-				canvasY = event.pageY - offset[1] - plotOffset.top;
-
-			for(var axis in axes)
-				if(axes[axis].used)
-					pos[axis.replace(/axis$/, '')] = axes[axis].c2p(axis.charAt(0) == 'x' ? canvasX :  canvasY);
-
-			var item = findNearbyItem(canvasX, canvasY);
-
-			if (item) {
-				// fill in mouse pos for any listeners out there
-				item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint.x) + offset[0] + plotOffset.left, 10);
-				item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint.y) + offset[1] + plotOffset.top, 10);
-			}
-
-			if (options.grid.autoHighlight) {
-				// clear auto-highlights
-				for (var i = 0; i < highlights.length; ++i) {
-					var h = highlights[i];
-					if (h.auto == eventname &&
-						!(item && h.series == item.series && h.point == item.datapoint))
-						unhighlight(h.series, h.point);
-				}
-
-				if (item)
-					highlight(item.series, item.datapoint, eventname);
-			}
-
-			plot.fireEvent(eventname, {pos: pos, item: item });
-		}
-
-		function triggerRedrawOverlay() {
-			if (!redrawTimeout)
-				redrawTimeout = setTimeout(redrawOverlay, 30);
-		}
-
-		function redrawOverlay() {
-			redrawTimeout = null;
-
-			// redraw highlights
-			octx.save();
-			octx.clearRect(0, 0, canvasWidth, canvasHeight);
-			octx.translate(plotOffset.left, plotOffset.top);
-
-			var hi;
-			for (var i = 0; i < highlights.length; ++i) {
-				hi = highlights[i];
-
-				if (hi.series.bars.show)
-					drawBarHighlight(hi.series, hi.point);
-				else
-					drawPointHighlight(hi.series, hi.point);
-			}
-
-			// redraw selection
-			if (selection.show && selectionIsSane()) {
-				octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
-				octx.lineWidth = 1;
-				ctx.lineJoin = "round";
-				octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
-
-				var x = Math.min(selection.first.x, selection.second.x),
-					y = Math.min(selection.first.y, selection.second.y),
-					w = Math.abs(selection.second.x - selection.first.x),
-					h = Math.abs(selection.second.y - selection.first.y);
-
-				octx.fillRect(x, y, w, h);
-				octx.strokeRect(x, y, w, h);
-			}
-
-			// redraw crosshair
-			var pos = crosshair.pos, mode = options.crosshair.mode;
-			if (mode != null && pos.x != -1) {
-				octx.strokeStyle = parseColor(options.crosshair.color).scale(null, null, null, 0.8).toString();
-				octx.lineWidth = 1;
-				ctx.lineJoin = "round";
-
-				octx.beginPath();
-				if (mode.indexOf("x") != -1) {
-					octx.moveTo(pos.x, 0);
-					octx.lineTo(pos.x, plotHeight);
-				}
-				if (mode.indexOf("y") != -1) {
-					octx.moveTo(0, pos.y);
-					octx.lineTo(plotWidth, pos.y);
-				}
-				octx.stroke();
-
-			}
-			octx.restore();
-		}
-
-		function highlight(s, point, auto) {
-			if (typeof s == "number")
-				s = series[s];
-
-			if (typeof point == "number")
-				point = s.data[point];
-
-			var i = indexOfHighlight(s, point);
-			if (i == -1) {
-				highlights.push({ series: s, point: point, auto: auto });
-
-				triggerRedrawOverlay();
-			}
-			else if (!auto)
-				highlights[i].auto = false;
-		}
-
-		function unhighlight(s, point) {
-			if (typeof s == "number")
-				s = series[s];
-
-			if (typeof point == "number")
-				point = s.data[point];
-
-			var i = indexOfHighlight(s, point);
-			if (i != -1) {
-				highlights.splice(i, 1);
-
-				triggerRedrawOverlay();
-			}
-		}
-
-		function indexOfHighlight(s, p) {
-			for (var i = 0; i < highlights.length; ++i) {
-				var h = highlights[i];
-				if (h.series == s && h.point[0] == p[0]
-					&& h.point[1] == p[1])
-					return i;
-			}
-			return -1;
-		}
-
-		function drawPointHighlight(series, point) {
-			var x = point.x, y = point.y,
-				axisx = series.xaxis, axisy = series.yaxis;
-
-			if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
-				return;
-
-			var pointRadius = series.points.radius + series.points.lineWidth / 2;
-			octx.lineWidth = pointRadius;
-			octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
-			var radius = 1.5 * pointRadius;
-			octx.beginPath();
-			octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true);
-			octx.stroke();
-		}
-
-		function drawBarHighlight(series, point) {
-			octx.lineJoin = "round";
-			octx.lineWidth = series.bars.lineWidth;
-			octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
-			var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
-			var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
-			drawBar(point.x, point.y, barLeft, barLeft + series.bars.barWidth,
-					0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx);
-		}
-
-		function setPositionFromEvent(pos, e) {
-			var offset = DOM.getXY(eventHolder[0]);
-			pos.x = clamp(0, e.pageX - offset[0] - plotOffset.left, plotWidth);
-			pos.y = clamp(0, e.pageY - offset[1] - plotOffset.top, plotHeight);
-		}
-
-		function setCrosshair(pos) {
-			if (pos == null)
-				crosshair.pos.x = -1;
-			else {
-				crosshair.pos.x = clamp(0, pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plotWidth);
-				crosshair.pos.y = clamp(0, pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plotHeight);
-			}
-			triggerRedrawOverlay();
-		}
-
-		function getSelectionForEvent() {
-			var x1 = Math.min(selection.first.x, selection.second.x),
-				x2 = Math.max(selection.first.x, selection.second.x),
-				y1 = Math.max(selection.first.y, selection.second.y),
-				y2 = Math.min(selection.first.y, selection.second.y);
-
-			var r = {};
-			if (axes.xaxis.used)
-				r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) };
-			if (axes.x2axis.used)
-				r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) };
-			if (axes.yaxis.used)
-				r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) };
-			if (axes.y2axis.used)
-				r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
-			return r;
-		}
-
-		function triggerSelectedEvent() {
-			var r = getSelectionForEvent();
-
-			plot.fireEvent("plotselected", r);
-		}
-
-		function onSelectionMouseUp(e) {
-			// revert drag stuff for old-school browsers
-			if (document.onselectstart !== undefined)
-				document.onselectstart = workarounds.onselectstart;
-			if (document.ondrag !== undefined)
-				document.ondrag = workarounds.ondrag;
-
-			// no more draggy-dee-drag
-			selection.active = false;
-			var mousePos = {pageX: E.getPageX(e), pageY: E.getPageY(e)};
-			updateSelection(mousePos);
-
-			if (selectionIsSane()) {
-				triggerSelectedEvent();
-				clickIsMouseUp = true;
-			}
-			else {
-				// this counts as a clear
-				plot.fireEvent("plotunselected", {});
-			}
-
-			E.removeListener(document, "mouseup", onSelectionMouseUp);
-			return false;
-		}
-
-		function setSelectionPos(pos, e) {
-			setPositionFromEvent(pos, e);
-
-			if (options.selection.mode == "y") {
-				if (pos == selection.first)
-					pos.x = 0;
-				else
-					pos.x = plotWidth;
-			}
-
-			if (options.selection.mode == "x") {
-				if (pos == selection.first)
-					pos.y = 0;
-				else
-					pos.y = plotHeight;
-			}
-		}
-
-		function updateSelection(pos) {
-			if (pos.pageX == null)
-				return;
-
-			setSelectionPos(selection.second, pos);
-			if (selectionIsSane()) {
-				selection.show = true;
-				triggerRedrawOverlay();
-			}
-			else
-				clearSelection(true);
-		}
-
-		function clearSelection(preventEvent) {
-			if (selection.show) {
-				selection.show = false;
-				triggerRedrawOverlay();
-				if (!preventEvent)
-					plot.fireEvent("plotunselected", {});
-			}
-		}
-
-		function setSelection(ranges, preventEvent) {
-			var range;
-
-			if (options.selection.mode == "y") {
-				selection.first.x = 0;
-				selection.second.x = plotWidth;
-			}
-			else {
-				range = extractRange(ranges, "x");
-
-				selection.first.x = range.axis.p2c(range.from);
-				selection.second.x = range.axis.p2c(range.to);
-			}
-
-			if (options.selection.mode == "x") {
-				selection.first.y = 0;
-				selection.second.y = plotHeight;
-			}
-			else {
-				range = extractRange(ranges, "y");
-
-				selection.first.y = range.axis.p2c(range.from);
-				selection.second.y = range.axis.p2c(range.to);
-			}
-
-			selection.show = true;
-			triggerRedrawOverlay();
-			if (!preventEvent)
-				triggerSelectedEvent();
-		}
-
-		function selectionIsSane() {
-			var minSize = 5;
-			return Math.abs(selection.second.x - selection.first.x) >= minSize &&
-				Math.abs(selection.second.y - selection.first.y) >= minSize;
-		}
-
-		function getColorOrGradient(spec, bottom, top, defaultColor) {
-			if (typeof spec == "string")
-				return spec;
-			else {
-				// assume this is a gradient spec; IE currently only
-				// supports a simple vertical gradient properly, so that's
-				// what we support too
-				var gradient = ctx.createLinearGradient(0, top, 0, bottom);
-
-				for (var i = 0, l = spec.colors.length; i < l; ++i) {
-					var c = spec.colors[i];
-					gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity));
-				}
-
-				return gradient;
-			}
-		}
-	}
-
-	L.augment(Plot, YAHOO.util.EventProvider);
-
-	YAHOO.widget.Flot = function(target, data, options) {
-		return new Plot(target, data, options);
-	};
-
-	// round to nearby lower multiple of base
-	function floorInBase(n, base) {
-		return base * Math.floor(n / base);
-	}
-
-	function clamp(min, value, max) {
-		if (value < min)
-			return min;
-		else if (value > max)
-			return max;
-		else
-			return value;
-	}
-
-	// color helpers, inspiration from the jquery color animation
-	// plugin by John Resig
-	function Color (r, g, b, a) {
-	   
-		var rgba = ['r','g','b','a'];
-		var x = 4; //rgba.length
-	   
-		while (-1<--x) {
-			this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
-		}
-	   
-		this.toString = function() {
-			if (this.a >= 1.0) {
-				return "rgb("+[this.r,this.g,this.b].join(",")+")";
-			} else {
-				return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
-			}
-		};
-
-		this.scale = function(rf, gf, bf, af) {
-			x = 4; //rgba.length
-			while (-1<--x) {
-				if (arguments[x] != null)
-					this[rgba[x]] *= arguments[x];
-			}
-			return this.normalize();
-		};
-
-		this.adjust = function(rd, gd, bd, ad) {
-			x = 4; //rgba.length
-			while (-1<--x) {
-				if (arguments[x] != null)
-					this[rgba[x]] += arguments[x];
-			}
-			return this.normalize();
-		};
-
-		this.clone = function() {
-			return new Color(this.r, this.b, this.g, this.a);
-		};
-
-		var limit = function(val,minVal,maxVal) {
-			return Math.max(Math.min(val, maxVal), minVal);
-		};
-
-		this.normalize = function() {
-			this.r = clamp(0, parseInt(this.r, 10), 255);
-			this.g = clamp(0, parseInt(this.g, 10), 255);
-			this.b = clamp(0, parseInt(this.b, 10), 255);
-			this.a = clamp(0, this.a, 1);
-			return this;
-		};
-
-		this.normalize();
-	}
-
-	var lookupColors = {
-		aqua:[0,255,255],
-		azure:[240,255,255],
-		beige:[245,245,220],
-		black:[0,0,0],
-		blue:[0,0,255],
-		brown:[165,42,42],
-		cyan:[0,255,255],
-		darkblue:[0,0,139],
-		darkcyan:[0,139,139],
-		darkgrey:[169,169,169],
-		darkgreen:[0,100,0],
-		darkkhaki:[189,183,107],
-		darkmagenta:[139,0,139],
-		darkolivegreen:[85,107,47],
-		darkorange:[255,140,0],
-		darkorchid:[153,50,204],
-		darkred:[139,0,0],
-		darksalmon:[233,150,122],
-		darkviolet:[148,0,211],
-		fuchsia:[255,0,255],
-		gold:[255,215,0],
-		green:[0,128,0],
-		indigo:[75,0,130],
-		khaki:[240,230,140],
-		lightblue:[173,216,230],
-		lightcyan:[224,255,255],
-		lightgreen:[144,238,144],
-		lightgrey:[211,211,211],
-		lightpink:[255,182,193],
-		lightyellow:[255,255,224],
-		lime:[0,255,0],
-		magenta:[255,0,255],
-		maroon:[128,0,0],
-		navy:[0,0,128],
-		olive:[128,128,0],
-		orange:[255,165,0],
-		pink:[255,192,203],
-		purple:[128,0,128],
-		violet:[128,0,128],
-		red:[255,0,0],
-		silver:[192,192,192],
-		white:[255,255,255],
-		yellow:[255,255,0]
-	};
-
-	function extractColor(element) {
-		var color, elem = element;
-		do {
-			color = DOM.getStyle(elem, 'backgroundColor').toLowerCase();
-			// keep going until we find an element that has color, or
-			// we hit the body
-			if (color != '' && color != 'transparent')
-				break;
-			elem = elem.parentNode;
-		} while (!elem.nodeName == "body");
-
-		// catch Safari's way of signalling transparent
-		if (color == "rgba(0, 0, 0, 0)")
-			return "transparent";
-
-		return color;
-	}
-
-	// parse string, returns Color
-	function parseColor(str) {
-		var result;
-
-		// Look for rgb(num,num,num)
-		if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
-			return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
-
-		// Look for rgba(num,num,num,num)
-		if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
-			return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
-
-		// Look for rgb(num%,num%,num%)
-		if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
-			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
-
-		// Look for rgba(num%,num%,num%,num)
-		if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
-			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
-
-		// Look for #a0b1c2
-		if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
-			return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
-
-		// Look for #fff
-		if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
-			return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
-
-		// Otherwise, we're most likely dealing with a named color
-		var name = L.trim(str).toLowerCase();
-		if (name == "transparent")
-			return new Color(255, 255, 255, 0);
-		else {
-			result = lookupColors[name];
-			return new Color(result[0], result[1], result[2]);
-		}
-	}
-
-})();
--- a/kallithea/templates/about.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/about.html	Sun Mar 31 21:28:56 2019 +0200
@@ -3,20 +3,17 @@
 <%block name="title">
     ${_('About')}
 </%block>
-<%def name="breadcrumbs()">
-    ${c.site_name}
-</%def>
 <%block name="header_menu">
     ${self.menu('about')}
 </%block>
 <%def name="main()">
 
-<div class="box">
-  <!-- box / title -->
-  <div class="title">
-    <h5>${_('About')} Kallithea</h5>
+<div class="panel panel-primary">
+  <div class="panel-heading">
+    <h5 class="panel-title">${_('About')} Kallithea</h5>
   </div>
 
+  <div class="panel-body panel-about">
   <p><a href="https://kallithea-scm.org/">Kallithea</a> is a project of the
   <a href="http://sfconservancy.org/">Software Freedom Conservancy, Inc.</a>
   and is released under the terms of the
@@ -24,31 +21,54 @@
   v 3.0 (GPLv3)</a>.</p>
 
   <p>Kallithea is copyrighted by various authors, including but not
-  necessarily limited to the following:
-  <ul style="margin: 0 0 0 50px;">
+  necessarily limited to the following:</p>
+  <ul>
 
   <li>Copyright &copy; 2012&ndash;2019, Mads Kiilerich</li>
+  <li>Copyright &copy; 2012, 2014&ndash;2017, 2019, Andrej Shadura</li>
   <li>Copyright &copy; 2014&ndash;2019, Thomas De Schampheleire</li>
+  <li>Copyright &copy; 2015&ndash;2017, 2019, Étienne Gilli</li>
+  <li>Copyright &copy; 2017&ndash;2019, Allan Nordhøy</li>
+  <li>Copyright &copy; 2019, Danni Randeris</li>
+  <li>Copyright &copy; 2019, Edmund Wong</li>
+  <li>Copyright &copy; 2019, Manuel Jacob</li>
+  <li>Copyright &copy; 2012, 2014&ndash;2018, Dominik Ruf</li>
   <li>Copyright &copy; 2014&ndash;2015, 2018, Michal Čihař</li>
   <li>Copyright &copy; 2015, 2018, Branko Majic</li>
+  <li>Copyright &copy; 2018, Chris Rule</li>
+  <li>Copyright &copy; 2018, Jesús Sánchez</li>
+  <li>Copyright &copy; 2018, Patrick Vane</li>
+  <li>Copyright &copy; 2018, Pheng Heong Tan</li>
   <li>Copyright &copy; 2018, ssantos</li>
   <li>Copyright &copy; 2018, Максим Якимчук</li>
   <li>Copyright &copy; 2018, Марс Ямбар</li>
   <li>Copyright &copy; 2012&ndash;2017, Unity Technologies</li>
-  <li>Copyright &copy; 2012, 2014&ndash;2017, Andrew Shadura</li>
-  <li>Copyright &copy; 2012, 2014, 2016&ndash;2017, Dominik Ruf</li>
-  <li>Copyright &copy; 2015, 2017, Étienne Gilli</li>
+  <li>Copyright &copy; 2015&ndash;2017, Søren Løvborg</li>
   <li>Copyright &copy; 2015, 2017, Sam Jaques</li>
-  <li>Copyright &copy; 2017, Allan Nordhøy</li>
+  <li>Copyright &copy; 2016&ndash;2017, Asterios Dimitriou</li>
+  <li>Copyright &copy; 2017, Alessandro Molina</li>
+  <li>Copyright &copy; 2017, Anton Schur</li>
   <li>Copyright &copy; 2017, Ching-Chen Mao</li>
+  <li>Copyright &copy; 2017, Eivind Tagseth</li>
   <li>Copyright &copy; 2017, FUJIWARA Katsunori</li>
+  <li>Copyright &copy; 2017, Holger Schramm</li>
+  <li>Copyright &copy; 2017, Karl Goetz</li>
+  <li>Copyright &copy; 2017, Lars Kruse</li>
+  <li>Copyright &copy; 2017, Marko Semet</li>
   <li>Copyright &copy; 2017, Viktar Vauchkevich</li>
   <li>Copyright &copy; 2012&ndash;2016, Takumi IINO</li>
-  <li>Copyright &copy; 2015&ndash;2016, Søren Løvborg</li>
+  <li>Copyright &copy; 2015&ndash;2016, Jan Heylen</li>
+  <li>Copyright &copy; 2015&ndash;2016, Robert Martinez</li>
+  <li>Copyright &copy; 2015&ndash;2016, Robert Rauch</li>
+  <li>Copyright &copy; 2016, Angel Ezquerra</li>
   <li>Copyright &copy; 2016, Anton Shestakov</li>
   <li>Copyright &copy; 2016, Brandon Jones</li>
+  <li>Copyright &copy; 2016, Kateryna Musina</li>
   <li>Copyright &copy; 2016, Konstantin Veretennicov</li>
+  <li>Copyright &copy; 2016, Oscar Curero</li>
   <li>Copyright &copy; 2016, Robert James Dennington</li>
+  <li>Copyright &copy; 2016, timeless@gmail.com</li>
+  <li>Copyright &copy; 2016, YFdyh000</li>
   <li>Copyright &copy; 2012&ndash;2013, 2015, Aras Pranckevičius</li>
   <li>Copyright &copy; 2014&ndash;2015, Christian Oyarzun</li>
   <li>Copyright &copy; 2014&ndash;2015, Joseph Rivera</li>
@@ -63,7 +83,7 @@
   <li>Copyright &copy; 2015, duanhongyi</li>
   <li>Copyright &copy; 2015, EriCSN Chang</li>
   <li>Copyright &copy; 2015, Grzegorz Krason</li>
-  <li>Copyright &copy; 2015, Jan Heylen</li>
+  <li>Copyright &copy; 2015, Jiří Suchan</li>
   <li>Copyright &copy; 2015, Kazunari Kobayashi</li>
   <li>Copyright &copy; 2015, Kevin Bullock</li>
   <li>Copyright &copy; 2015, kobanari</li>
@@ -76,11 +96,10 @@
   <li>Copyright &copy; 2015, Nick High</li>
   <li>Copyright &copy; 2015, Niemand Jedermann</li>
   <li>Copyright &copy; 2015, Peter Vitt</li>
-  <li>Copyright &copy; 2015, Robert Martinez</li>
-  <li>Copyright &copy; 2015, Robert Rauch</li>
   <li>Copyright &copy; 2015, Ronny Pfannschmidt</li>
   <li>Copyright &copy; 2015, Tuux</li>
   <li>Copyright &copy; 2015, Viktar Palstsiuk</li>
+  <li>Copyright &copy; 2014, Ante Ilic</li>
   <li>Copyright &copy; 2014, Bradley M. Kuhn</li>
   <li>Copyright &copy; 2014, Calinou</li>
   <li>Copyright &copy; 2014, Daniel Anderson</li>
@@ -145,15 +164,21 @@
 ## Stefan Engel <mail@engel-stefan.de> in 2012
 ## Ton Plomp <tcplomp@gmail.com> in 2013
 ##
-</ul></p>
+  </ul>
 
-<p>The above are the copyright holders who have submitted direct
-  contributions to the Kallithea repository.  In
-  the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea source
-  code</a>, there is
-  a <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
+  <p>The above are the copyright holders who have submitted direct
+  contributions to the Kallithea repository.</p>
+
+  <p>In the <a href="https://kallithea-scm.org/repos/kallithea">Kallithea
+  source code</a>, there is a
+  <a href="https://kallithea-scm.org/repos/kallithea/files/tip/LICENSE.md">list
   of third-party libraries and code that Kallithea incorporates</a>.</p>
 
+  <p>The front-end contains a <a href="${h.url('/LICENSES.txt')}">list of
+  software that is used to build the front-end</a> but isn't distributed as a
+  part of Kallithea.</p>
+
+  </div>
 </div>
 
 </%def>
--- a/kallithea/templates/admin/admin.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/admin.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,29 +6,24 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    <form id="filter_form">
-    <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
-    <span class="tooltip" title="${h.journal_filter_help()}">?</span>
-    <input type='submit' value="${_('Filter')}" class="btn btn-mini" style="padding:0px 2px 0px 2px;margin:0px"/>
-    ${_('Admin Journal')} - ${ungettext('%s Entry', '%s Entries', c.users_log.item_count) % (c.users_log.item_count)}
+    <form id="filter_form" class="pull-left form-inline input-group-sm">
+        <input class="form-control q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
+        <span data-toggle="popover" data-content="${h.journal_filter_help()}">?</span>
+        <input type='submit' value="${_('Filter')}" class="btn btn-default btn-xs"/>
+        ${_('Admin Journal')} - ${ungettext('%s Entry', '%s Entries', c.users_log.item_count) % (c.users_log.item_count)}
     </form>
-    ${h.end_form()}
 </%def>
 
 <%block name="header_menu">
     ${self.menu('admin')}
 </%block>
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    <div class="table">
-        <div id="user_log">
+    <div id="user_log" class="panel-body">
             <%include file='admin_log.html'/>
-        </div>
     </div>
 </div>
 
@@ -49,7 +44,7 @@
   $('#filter_form').submit(function (e) {
       e.preventDefault();
       var val = $('#j_filter').val();
-      window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
+      window.location = ${h.js(url.current(filter='__FILTER__'))}.replace('__FILTER__',val);
   });
   fix_j_filter_width($('#j_filter').val().length);
 });
--- a/kallithea/templates/admin/admin_log.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/admin_log.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,6 @@
 ## -*- coding: utf-8 -*-
 %if c.users_log:
-<table>
+<table class="table">
     <tr>
         <th class="left">${_('Username')}</th>
         <th class="left">${_('Action')}</th>
@@ -44,7 +44,6 @@
       asynchtml(e.target.href, $user_log, function(){
         show_more_event();
         tooltip_activate();
-        show_changeset_tooltip();
       });
       e.preventDefault();
     });
@@ -56,9 +55,8 @@
   });
 </script>
 
-<div class="pagination-wh pagination-left">
-${c.users_log.pager('$link_previous ~2~ $link_next')}
-</div>
+${c.users_log.pager()}
+
 %else:
     ${_('No actions yet')}
 %endif
--- a/kallithea/templates/admin/auth/auth_settings.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/auth/auth_settings.html	Sun Mar 31 21:28:56 2019 +0200
@@ -16,93 +16,93 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
+    <div class="form panel-body settings">
     ${h.form(url('auth_settings'))}
-    <div class="form">
 
     ## enabled auth plugins
     <h1>${_('Authentication Plugins')}</h1>
-    <div class="fields">
-       <div class="field">
-           <div class="label"><label for="auth_plugins">${_("Enabled Plugins")}</label></div>
-           <div class="input">${h.text("auth_plugins", class_='large')}
-           <span class="help-block">${_('Comma-separated list of plugins; Kallithea will try user authentication in plugin order')}</span>
-               <div style="padding:10px 0px 10px 0px;font-weight: bold">${_('Available built-in plugins')}</div>
-               <ul>
-               %for plugin_path in c.available_plugins:
-                    <li>
-                      <div style="padding:3px 0px 3px 0px">
-                          <span style="margin: 0px 10px 0px 0px" plugin_id="${plugin_path}" class="toggle-plugin btn btn-mini ${'btn-success' if plugin_path in c.enabled_plugins else ''}">
-                          ${_('Enabled') if plugin_path in c.enabled_plugins else _('Disabled')}</span>${plugin_path}
-                      </div>
+        <div class="form-group">
+            <label class="control-label" for="auth_plugins">${_("Enabled Plugins")}</label>
+            <div>
+                ${h.text("auth_plugins", class_='form-control')}
+                <span class="help-block">${_('Comma-separated list of plugins; Kallithea will try user authentication in plugin order')}</span>
+            </div>
+        </div>
+        <div class="form-group">
+            <label class="control-label">${_('Available built-in plugins')}</label>
+            <div>
+                <ul class="list-group">
+                %for plugin_path in c.available_plugins:
+                    <li class="list-group-item">
+                        <button type="button" data-plugin_id="${plugin_path}" class="toggle-plugin btn btn-default btn-xs ${'active' if plugin_path in c.enabled_plugin_names else ''}">
+                            ${_('Enabled') if plugin_path in c.enabled_plugin_names else _('Disabled')}
+                        </button>
+                        ${plugin_path}
                     </li>
-               %endfor
-               </ul>
-           </div>
-       </div>
-    </div>
+                %endfor
+                </ul>
+            </div>
+        </div>
 
-    %for cnt, module in enumerate(c.enabled_plugins):
+    %for cnt, module in enumerate(c.enabled_plugin_names):
         <% pluginName = c.plugin_shortnames[module] %>
         <h1>${_('Plugin')}: ${pluginName}</h1>
-        <div class="fields">
         ## autoform generation, based on plugin definition from it's settings
         %for setting in c.plugin_settings[module]:
             <% fullsetting = "auth_%s_%s" % (pluginName, setting["name"]) %>
             <% displayname = (setting["formname"] if ("formname" in setting) else setting["name"]) %>
             %if setting["type"] == "password":
-            <div class="field">
-                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
-                <div class="input">
-                    ${h.password(fullsetting,class_='small')}
+            <div class="form-group">
+                <label class="control-label" for="${fullsetting}">${_(displayname)}</label>
+                <div>
+                    ${h.password(fullsetting,class_='form-control')}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
             %elif setting["type"] in ["string", "int"]:
-            <div class="field">
-                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
-                <div class="input">
-                    ${h.text(fullsetting,class_='small')}
+            <div class="form-group">
+                <label class="control-label" for="${fullsetting}">${_(displayname)}</label>
+                <div>
+                    ${h.text(fullsetting,class_='form-control')}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
             %elif setting["type"] == "bool":
-            <div class="field">
-                <div class="label label-checkbox"><label for="${fullsetting}">${_(displayname)}</label></div>
-                <div class="checkboxes">
-                    <div class="checkbox">${h.checkbox(fullsetting,True,class_='small')}</div>
+            <div class="form-group">
+                <label class="control-label" for="${fullsetting}">${_(displayname)}</label>
+                <div>
+                    ${h.checkbox(fullsetting,True)}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
             %elif setting["type"] == "select":
-            <div class="field">
-                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
-                <div class="select">
-                    ${h.select(fullsetting,setting['values'][0],setting['values'],class_='small')}
+            <div class="form-group">
+                <label class="control-label" for="${fullsetting}">${_(displayname)}</label>
+                <div>
+                    ${h.select(fullsetting,setting['values'][0],setting['values'],class_='form-control')}
                     <span class="help-block">${setting["description"]}</span>
                 </div>
             </div>
             %else:
-            <div class="field">
-                <div class="label"><label for="${fullsetting}">${_(displayname)}</label></div>
-                <div class="input">This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].</div>
+            <div class="form-group">
+                <label class="control-label" for="${fullsetting}">${_(displayname)}</label>
+                <div>This field is of type ${setting['type']}, which cannot be displayed. Must be one of [string|int|bool|select].</div>
                 <span class="help-block">${setting["description"]}</span>
             </div>
             %endif
         %endfor
-        </div>
     %endfor
-        <div class="fields">
-            <div class="buttons">
-                ${h.submit('save',_('Save'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
+    ${h.end_form()}
     </div>
-    ${h.end_form()}
 </div>
 
 <script>
@@ -113,21 +113,21 @@
         }
         var elems = $auth_plugins_input.val().split(',').filter(notEmpty);
         var $cur_button = $(e.currentTarget);
-        var plugin_id = $cur_button.attr('plugin_id');
+        var plugin_id = $cur_button.data('plugin_id');
 
-        if($cur_button.hasClass('btn-success')){
+        if($cur_button.hasClass('active')){
             elems.splice(elems.indexOf(plugin_id), 1);
             $auth_plugins_input.val(elems.join(','));
-            $cur_button.removeClass('btn-success');
-            $cur_button.html(_TM['disabled']);
+            $cur_button.removeClass('active');
+            $cur_button.html(_TM['Disabled']);
         }
         else{
             if(elems.indexOf(plugin_id) == -1){
                 elems.push(plugin_id);
             }
             $auth_plugins_input.val(elems.join(','));
-            $cur_button.addClass('btn-success');
-            $cur_button.html(_TM['enabled']);
+            $cur_button.addClass('active');
+            $cur_button.html(_TM['Enabled']);
         }
     });
 </script>
--- a/kallithea/templates/admin/defaults/defaults.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/defaults/defaults.html	Sun Mar 31 21:28:56 2019 +0200
@@ -16,72 +16,57 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    ${h.form(url('default', id='defaults'),method='put')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields">
-
-            <div class="field">
-                <div class="label">
-                    <label for="default_repo_type">${_('Type')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('default_repo_type','hg',c.backends,class_="medium")}
+    ${h.form(url('defaults_update', id='defaults'))}
+    <div class="form panel-body settings">
+            <div class="form-group">
+                <label class="control-label" for="default_repo_type">${_('Type')}:</label>
+                <div>
+                    ${h.select('default_repo_type','hg',c.backends,class_='form-control')}
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="default_repo_private">${_('Private repository')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="default_repo_private">${_('Private repository')}:</label>
+                <div>
                     ${h.checkbox('default_repo_private',value="True")}
                     <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
                 </div>
             </div>
 
-
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="default_repo_enable_statistics">${_('Enable statistics')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="default_repo_enable_statistics">${_('Enable statistics')}:</label>
+                <div>
                     ${h.checkbox('default_repo_enable_statistics',value="True")}
                     <span class="help-block">${_('Enable statistics window on summary page.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="default_repo_enable_downloads">${_('Enable downloads')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="default_repo_enable_downloads">${_('Enable downloads')}:</label>
+                <div>
                     ${h.checkbox('default_repo_enable_downloads',value="True")}
                     <span class="help-block">${_('Enable download menu on summary page.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="default_repo_enable_locking">${_('Enable locking')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="default_repo_enable_locking">${_('Enable locking')}:</label>
+                <div>
                     ${h.checkbox('default_repo_enable_locking',value="True")}
                     <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
                 </div>
             </div>
 
-            <div class="buttons">
-            ${h.submit('save',_('Save'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 
--- a/kallithea/templates/admin/gists/edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/gists/edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -23,13 +23,12 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div class="table">
+    <div class="panel-body">
         <div id="edit_error" style="display: none" class="flash_msg">
             <div class="alert alert-dismissable alert-warning">
               <button type="button" class="close" data-dismiss="alert" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
@@ -46,17 +45,16 @@
         <div id="files_data">
           ${h.form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')}
             <div>
-                <div class="gravatar">
-                  ${h.gravatar(c.authuser.email, size=32)}
-                </div>
                 <input type="hidden" value="${c.file_changeset.raw_id}" name="parent_hash">
-                <textarea style="resize:vertical; width:400px;border: 1px solid #ccc;border-radius: 3px;"
+                <textarea class="form-control"
                           id="description" name="description"
                           placeholder="${_('Gist description ...')}">${c.gist.gist_description}</textarea>
-                <div style="padding:0px 0px 0px 42px">
-                    <label for='lifetime'>${_('Gist lifetime')}</label>
-                    ${h.select('lifetime', '0', c.lifetime_options)}
-                    <span class="" style="color: #AAA">
+                <div>
+                    <label>
+                        ${_('Gist lifetime')}
+                        ${h.select('lifetime', '0', c.lifetime_options)}
+                    </label>
+                    <span class="text-muted">
                      %if c.gist.gist_expires == -1:
                       ${_('Expires')}: ${_('Never')}
                      %else:
@@ -67,83 +65,85 @@
             </div>
 
             % for cnt, file in enumerate(c.files):
-                <div id="body" class="codeblock" style="margin-bottom: 4px">
-                    <div style="padding: 10px 10px 10px 26px;color:#666666">
+                <div id="body" class="panel panel-default form-inline">
+                    <div class="panel-heading">
                         <input type="hidden" value="${h.safe_unicode(file.path)}" name="org_files">
-                        <input id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${h.safe_unicode(file.path)}">
-                        <select id="mimetype_${h.FID('f',file.path)}" name="mimetypes"/>
+                        <input class="form-control" id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${h.safe_unicode(file.path)}">
+                        <select class="form-control" id="mimetype_${h.FID('f',file.path)}" name="mimetypes"></select>
                     </div>
-                    <div class="editor_container">
-                        <pre id="editor_pre"></pre>
-                        <textarea id="editor_${h.FID('f',file.path)}" name="contents" style="display:none">${file.content}</textarea>
+                    <div class="panel-body no-padding">
+                        <div id="editor_container">
+                            <textarea id="editor_${h.FID('f',file.path)}" name="contents" style="display:none">${file.content}</textarea>
+                        </div>
                     </div>
                 </div>
 
                 ## dynamic edit box.
                 <script type="text/javascript">
-                var myCodeMirror = initCodeMirror("editor_${h.FID('f',file.path)}", "${request.script_name}", '');
+                    $(document).ready(function(){
+                        var myCodeMirror = initCodeMirror(${h.js('editor_' + h.FID('f',file.path))}, ${h.jshtml(request.script_name)}, '');
+
+                        //inject new modes
+                        var $mimetype_select = $(${h.js('#mimetype_' + h.FID('f',file.path))});
+                        $mimetype_select.each(function(){
+                            var modes_select = this;
+                            var index = 1;
+                            for(var i=0;i<CodeMirror.modeInfo.length;i++) {
+                                var m = CodeMirror.modeInfo[i];
+                                var opt = new Option(m.name, m.mime);
+                                $(opt).attr('mode', m.mode);
+                                if (m.mime == 'text/plain') {
+                                    // default plain text
+                                    $(opt).prop('selected', true);
+                                    modes_select.options[0] = opt;
+                                } else {
+                                    modes_select.options[index++] = opt;
+                                }
+                            }
+                        });
 
-                //inject new modes
-                var $modes_select = $('#mimetype_${h.FID('f',file.path)}');
-                $modes_select.each(function(){
-                    var modes_select = this;
-                    var index = 1;
-                    for(var i=0;i<CodeMirror.modeInfo.length;i++) {
-                        var m = CodeMirror.modeInfo[i];
-                        var opt = new Option(m.name, m.mime);
-                        $(opt).attr('mode', m.mode);
-                        if (m.mime == 'text/plain') {
-                            // default plain text
-                            $(opt).prop('selected', true);
-                            modes_select.options[0] = opt;
-                        } else {
-                            modes_select.options[index++] = opt;
-                        }
-                    }
-                });
+                        var $filename_input = $(${h.js('#filename_' + h.FID('f',file.path))});
+                        // on select change set new mode
+                        $mimetype_select.change(function(e){
+                            var selected = e.currentTarget;
+                            var node = selected.options[selected.selectedIndex];
+                            var detected_mode = CodeMirror.findModeByMIME(node.value);
+                            setCodeMirrorMode(myCodeMirror, detected_mode);
+
+                            var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
+                            var file_data = CodeMirror.getFilenameAndExt($filename_input.val());
+                            var filename = file_data['filename'] || 'filename1';
+                            $filename_input.val(filename + '.' + proposed_ext);
+                        });
 
-                var $filename = $('#filename_${h.FID('f',file.path)}');
-                // on select change set new mode
-                $modes_select.change(function(e){
-                    var selected = e.currentTarget;
-                    var node = selected.options[selected.selectedIndex];
-                    var detected_mode = CodeMirror.findModeByMIME(node.value);
-                    setCodeMirrorMode(myCodeMirror, detected_mode);
+                        // on type the new filename set mode
+                        $filename_input.keyup(function(e){
+                            var file_data = CodeMirror.getFilenameAndExt(this.value);
+                            if(file_data['ext'] != null){
+                                var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
 
-                    var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
-                    var file_data = CodeMirror.getFilenameAndExt($filename.val());
-                    var filename = file_data['filename'] || 'filename1';
-                    $filename.val(filename + '.' + proposed_ext);
-                });
+                                if (detected_mode){
+                                    setCodeMirrorMode(myCodeMirror, detected_mode);
+                                    $mimetype_select.val(detected_mode.mime);
+                                }
+                            }
+                        });
 
-                // on type the new filename set mode
-                $filename.keyup(function(e){
-                    var file_data = CodeMirror.getFilenameAndExt(this.value);
-                    if(file_data['ext'] != null){
-                        var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
+                        // set mode on page load
+                        var detected_mode = CodeMirror.findModeByExtension(${h.js(file.extension)});
 
                         if (detected_mode){
                             setCodeMirrorMode(myCodeMirror, detected_mode);
-                            $modes_select.val(detected_mode.mime);
+                            $mimetype_select.val(detected_mode.mime);
                         }
-                    }
-                });
-
-                // set mode on page load
-                var detected_mode = CodeMirror.findModeByExtension("${file.extension}");
-
-                if (detected_mode){
-                    setCodeMirrorMode(myCodeMirror, detected_mode);
-                    $modes_select.val(detected_mode.mime);
-                }
-
+                    });
                 </script>
 
             %endfor
 
-            <div style="padding-top: 5px">
-            ${h.submit('update',_('Update Gist'),class_="btn btn-mini btn-success")}
-            <a class="btn btn-mini" href="${h.url('gist', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
+            <div>
+            ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
+            <a class="btn btn-default" href="${h.url('gist', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
             </div>
           ${h.end_form()}
           <script>
@@ -152,8 +152,8 @@
 
                   // check for newer version.
                   $.ajax({
-                    url: "${h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id)}",
-                    data: {'revision': '${c.file_changeset.raw_id}', '_authentication_token': _authentication_token},
+                    url: ${h.js(h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id))},
+                    data: {'revision': ${h.js(c.file_changeset.raw_id)}, '_authentication_token': _authentication_token},
                     dataType: 'json',
                     type: 'POST',
                     success: function(data) {
--- a/kallithea/templates/admin/gists/index.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/gists/index.html	Sun Mar 31 21:28:56 2019 +0200
@@ -3,9 +3,9 @@
 
 <%block name="title">
     %if c.show_private:
-        ${_('Private Gists for User %s') % c.authuser.username}
+        ${_('Private Gists for User %s') % request.authuser.username}
     %elif c.show_public:
-        ${_('Public Gists for User %s') % c.authuser.username}
+        ${_('Public Gists for User %s') % request.authuser.username}
     %else:
         ${_('Public Gists')}
     %endif
@@ -13,9 +13,9 @@
 
 <%def name="breadcrumbs_links()">
     %if c.show_private:
-        ${_('Private Gists for User %s') % c.authuser.username}
+        ${_('Private Gists for User %s') % request.authuser.username}
     %elif c.show_public:
-        ${_('Public Gists for User %s') % c.authuser.username}
+        ${_('Public Gists for User %s') % request.authuser.username}
     %else:
         ${_('Public Gists')}
     %endif
@@ -27,32 +27,29 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        %if c.authuser.username != 'default':
-        <ul class="links">
-          <li>
-             <a href="${h.url('new_gist')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Create New Gist')}</a>
-          </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        %if request.authuser.username != 'default':
+        <div class="pull-right">
+             <a href="${h.url('new_gist')}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Create New Gist')}</a>
+        </div>
         %endif
     </div>
-    %if c.gists_pager.item_count>0:
+    <div class="panel-body">
+      %if c.gists_pager.item_count>0:
         % for gist in c.gists_pager:
-          <div class="gist-item" style="padding:10px 20px 10px 15px">
-
-            <div class="gravatar">
-              ${h.gravatar(gist.owner.email, size=28)}
-            </div>
-            <div title="${gist.owner.full_contact}" class="user" style="font-size: 16px">
+          <div class="gist-item clearfix">
+            ${h.gravatar_div(gist.owner.email, size=28)}
+            <span title="${gist.owner.full_contact}" class="user">
                 <b>${h.person(gist.owner.full_contact)}</b> /
                 <b><a href="${h.url('gist',gist_id=gist.gist_access_id)}">gist: ${gist.gist_access_id}</a></b>
-            </div>
-            <div style="padding: 4px 0px 0px 0px">
+            </span>
+            <div>
                 ${_('Created')} ${h.age(gist.created_on)} /
-                <span style="color: #AAA">
+                <span class="text-muted">
                   %if gist.gist_expires == -1:
                    ${_('Expires')}: ${_('Never')}
                   %else:
@@ -61,17 +58,13 @@
                 </span>
             </div>
 
-            <div style="border:0px;padding:10px 0px 0px 40px;color:#AAA">${gist.gist_description}</div>
+            <div class="text-muted">${gist.gist_description}</div>
           </div>
         % endfor
-
-        <div class="notification-paginator">
-          <div class="pagination-wh pagination-left">
-          ${c.gists_pager.pager('$link_previous ~2~ $link_next', **request.GET.mixed())}
-          </div>
-        </div>
-    %else:
-        <div class="table">${_('There are no gists yet')}</div>
-    %endif
+        ${c.gists_pager.pager(**request.GET.mixed())}
+      %else:
+        <div>${_('There are no gists yet')}</div>
+      %endif
+    </div>
 </div>
 </%def>
--- a/kallithea/templates/admin/gists/new.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/gists/new.html	Sun Mar 31 21:28:56 2019 +0200
@@ -23,87 +23,86 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div class="table">
+    <div class="panel-body">
         <div id="files_data">
           ${h.form(h.url('gists'), method='post',id='eform')}
             <div>
-                <div class="gravatar">
-                  ${h.gravatar(c.authuser.email, size=32)}
-                </div>
-                <textarea style="resize:vertical; width:400px;border: 1px solid #ccc;border-radius: 3px;" id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
-                <div style="padding:0px 0px 0px 42px">
-                <label for='lifetime'>${_('Gist lifetime')}</label>
-                ${h.select('lifetime', '', c.lifetime_options)}
+                <textarea class="form-control" id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
+                <div>
+                    <label>
+                        ${_('Gist lifetime')}
+                        ${h.select('lifetime', '', c.lifetime_options)}
+                    </label>
                 </div>
             </div>
-            <div id="body" class="codeblock">
-                <div style="padding: 10px 10px 10px 26px;color:#666666">
-                    ${h.text('filename', size=30, placeholder=_('name this file...'))}
-                    <select id="mimetype" name="mimetype"/>
+            <div id="body" class="panel panel-default form-inline">
+                <div class="panel-heading">
+                    ${h.text('filename', size=30, placeholder=_('Name this gist ...'), class_='form-control')}
+                    <select class="form-control" id="mimetype" name="mimetype"></select>
                 </div>
-                <div id="editor_container">
-                    <pre id="editor_pre"></pre>
-                    <textarea id="editor" name="content" style="display:none"></textarea>
+                <div class="panel-body no-padding">
+                        <textarea id="editor" name="content"></textarea>
                 </div>
             </div>
-            <div style="padding-top: 5px">
-            ${h.submit('private',_('Create Private Gist'),class_="btn btn-mini btn-success")}
-            ${h.submit('public',_('Create Public Gist'),class_="btn btn-mini")}
-            ${h.reset('reset',_('Reset'),class_="btn btn-mini")}
+            <div>
+            ${h.submit('private',_('Create Private Gist'),class_="btn btn-success btn-xs")}
+            ${h.submit('public',_('Create Public Gist'),class_="btn btn-default btn-xs")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-default btn-xs")}
             </div>
           ${h.end_form()}
           <script type="text/javascript">
-            var myCodeMirror = initCodeMirror('editor', "${request.script_name}", '');
+            $(document).ready(function(){
+                var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, '');
 
-            //inject new modes
-            var $modes_select = $('#mimetype');
-            $modes_select.each(function(){
-                var modes_select = this;
-                var index = 1;
-                for(var i=0;i<CodeMirror.modeInfo.length;i++) {
-                    var m = CodeMirror.modeInfo[i];
-                    var opt = new Option(m.name, m.mime);
-                    $(opt).attr('mode', m.mode);
-                    if (m.mime == 'text/plain') {
-                        // default plain text
-                        $(opt).prop('selected', true);
-                        modes_select.options[0] = opt;
-                    } else {
-                        modes_select.options[index++] = opt;
+                //inject new modes
+                var $mimetype_select = $('#mimetype');
+                $mimetype_select.each(function(){
+                    var modes_select = this;
+                    var index = 1;
+                    for(var i=0;i<CodeMirror.modeInfo.length;i++) {
+                        var m = CodeMirror.modeInfo[i];
+                        var opt = new Option(m.name, m.mime);
+                        $(opt).attr('mode', m.mode);
+                        if (m.mime == 'text/plain') {
+                            // default plain text
+                            $(opt).prop('selected', true);
+                            modes_select.options[0] = opt;
+                        } else {
+                            modes_select.options[index++] = opt;
+                        }
                     }
-                }
-            });
+                });
 
-            var $filename = $('#filename');
-            // on select change set new mode
-            $modes_select.change(function(e){
-                var selected = e.currentTarget;
-                var node = selected.options[selected.selectedIndex];
-                var detected_mode = CodeMirror.findModeByMIME(node.value);
-                setCodeMirrorMode(myCodeMirror, detected_mode);
+                var $filename_input = $('#filename');
+                // on select change set new mode
+                $mimetype_select.change(function(e){
+                    var selected = e.currentTarget;
+                    var node = selected.options[selected.selectedIndex];
+                    var detected_mode = CodeMirror.findModeByMIME(node.value);
+                    setCodeMirrorMode(myCodeMirror, detected_mode);
 
-                var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
-                var file_data = CodeMirror.getFilenameAndExt($filename.val());
-                var filename = file_data['filename'] || 'filename1';
-                $filename.val(filename + '.' + proposed_ext);
-            });
+                    var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
+                    var file_data = CodeMirror.getFilenameAndExt($filename_input.val());
+                    var filename = file_data['filename'] || 'filename1';
+                    $filename_input.val(filename + '.' + proposed_ext);
+                });
 
-            // on type the new filename set mode
-            $filename.keyup(function(e){
-                var file_data = CodeMirror.getFilenameAndExt(this.value);
-                if(file_data['ext'] != null){
-                    var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
-                    if (detected_mode){
-                        setCodeMirrorMode(myCodeMirror, detected_mode);
-                        $modes_select.val(detected_mode.mime);
+                // on type the new filename set mode
+                $filename_input.keyup(function(e){
+                    var file_data = CodeMirror.getFilenameAndExt(this.value);
+                    if(file_data['ext'] != null){
+                        var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
+                        if (detected_mode){
+                            setCodeMirrorMode(myCodeMirror, detected_mode);
+                            $mimetype_select.val(detected_mode.mime);
+                        }
                     }
-                }
+                });
             });
           </script>
         </div>
--- a/kallithea/templates/admin/gists/show.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/gists/show.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,85 +15,80 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        %if c.authuser.username != 'default':
-        <ul class="links">
-          <li>
-              <a href="${h.url('new_gist')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Create New Gist')}</a>
-          </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        %if request.authuser.username != 'default':
+        <div class="pull-right">
+            <a href="${h.url('new_gist')}" class="btn btn-success btn-sm"><i class="icon-plus"></i>${_('Create New Gist')}</a>
+        </div>
         %endif
     </div>
-    <div class="table">
+    <div class="panel-body">
         <div id="files_data">
-            <div id="body" class="codeblock">
-                <div class="code-header">
-                    <div class="stats">
-                        <div class="left" style="margin: -4px 0px 0px 0px">
+            <div id="body" class="panel panel-default">
+                <div class="panel-heading clearfix">
+                        <div class="pull-left">
                           %if c.gist.gist_type == 'public':
-                            <div class="btn btn-mini btn-success disabled">${_('Public Gist')}</div>
+                            <div class="label label-success">${_('Public Gist')}</div>
                           %else:
-                            <div class="btn btn-mini btn-warning disabled">${_('Private Gist')}</div>
+                            <div class="label label-warning">${_('Private Gist')}</div>
                           %endif
                         </div>
-                        <div class="left item">
+                        <div class="pull-left">
                             ${c.gist.gist_description}
                         </div>
-                        <div class="left item last" style="color: #AAA">
+                        <div class="pull-left text-muted">
                          %if c.gist.gist_expires == -1:
                           ${_('Expires')}: ${_('Never')}
                          %else:
                           ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))}
                          %endif
-                       </div>
+                        </div>
 
-                       %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.authuser.user_id:
-                        <div style="float:right">
-                            ${h.form(url('gist', gist_id=c.gist.gist_id),method='delete')}
-                                ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
+                        %if h.HasPermissionAny('hg.admin')() or c.gist.owner_id == request.authuser.user_id:
+                        <div class="pull-right">
+                            ${h.form(url('gist_delete', gist_id=c.gist.gist_id))}
+                                ${h.submit('remove_gist', _('Delete'),class_="btn btn-danger btn-xs",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
                             ${h.end_form()}
                         </div>
-                       %endif
-                        <div class="buttons">
+                        %endif
+                        <div class="pull-right">
                           ## only owner should see that
-                          %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.authuser.user_id:
-                            ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-mini")}
+                          %if h.HasPermissionAny('hg.admin')() or c.gist.owner_id == request.authuser.user_id:
+                            ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-default btn-xs")}
                           %endif
-                          ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-mini")}
+                          ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-default btn-xs")}
                         </div>
-                    </div>
-
+                </div>
+                <div class="panel-body">
                     <div class="author">
-                        <div class="gravatar">
-                          ${h.gravatar(h.email_or_none(c.file_changeset.author), size=16)}
-                        </div>
+                        ${h.gravatar_div(h.email_or_none(c.file_changeset.author), size=16)}
                         <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div>
                     </div>
-                    <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
+                    <div>${h.urlify_text(c.file_changeset.message,c.repo_name)}</div>
                 </div>
             </div>
 
-               ## iterate over the files
-               % for file in c.files:
-               <div style="border: 1px solid #EEE;margin-top:20px">
-                <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;">
-                    <a href="${c.gist.gist_url()}">¶</a>
-                    <b style="margin:0px 0px 0px 4px">${h.safe_unicode(file.path)}</b>
-                    <div style="float:right; margin: -5px">
-                       ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=h.safe_unicode(file.path)),class_="btn btn-mini")}
+            %for file in c.files:
+              <div class="panel panel-default">
+                <div id="${h.FID('G', file.path)}" class="panel-heading clearfix">
+                    <div class="pull-left">
+                      <b>${h.safe_unicode(file.path)}</b>
+                    </div>
+                    <div class="pull-right">
+                      ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.changeset.raw_id, f_path=h.safe_unicode(file.path)),class_="btn btn-default btn-xs")}
                     </div>
                 </div>
-                <div class="code-body">
+                <div class="panel-body no-padding">
                     ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
                 </div>
               </div>
-               %endfor
+            %endfor
         </div>
     </div>
 
-
 </div>
 </%def>
--- a/kallithea/templates/admin/my_account/my_account.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,7 +2,7 @@
 <%inherit file="/base/base.html"/>
 
 <%block name="title">
-    ${_('My Account')} ${c.authuser.username}
+    ${_('My Account')} ${request.authuser.username}
 </%block>
 
 <%def name="breadcrumbs_links()">
@@ -14,36 +14,26 @@
 </%block>
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading">
         ${self.breadcrumbs()}
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="panel-body settings">
         <ul class="nav nav-pills nav-stacked">
-          <li>
-           <div class="gravatar_box" style="height: 26px">
-               <div class="gravatar" style="float: left">
-                 ${h.gravatar(c.user.email)}
-               </div>
-               <div class="truncate" style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left; width: 100px">
-                <strong>${c.user.username}</strong>
-               </div>
-           </div>
-          </li>
-          <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
-          <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Email Addresses')}</a></li>
-          <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
-          <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('my_account_api_keys')}">${_('API Keys')}</a></li>
-          <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Owned Repositories')}</a></li>
-          <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched Repositories')}</a></li>
-          <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Show Permissions')}</a></li>
+            <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('my_account')}">${_('Profile')}</a></li>
+            <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('my_account_emails')}">${_('Email Addresses')}</a></li>
+            <li class="${'active' if c.active=='password' else ''}"><a href="${h.url('my_account_password')}">${_('Password')}</a></li>
+            <li class="${'active' if c.active=='api_keys' else ''}"><a href="${h.url('my_account_api_keys')}">${_('API Keys')}</a></li>
+            <li class="${'active' if c.active=='repos' else ''}"><a href="${h.url('my_account_repos')}">${_('Owned Repositories')}</a></li>
+            <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched Repositories')}</a></li>
+            <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('Show Permissions')}</a></li>
         </ul>
-    </div>
 
-    <div style="min-width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
-        <%include file="/admin/my_account/my_account_${c.active}.html"/>
+        <div>
+            <%include file="/admin/my_account/my_account_${c.active}.html"/>
+        </div>
     </div>
 </div>
 
--- a/kallithea/templates/admin/my_account/my_account_api_keys.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_api_keys.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,16 +1,15 @@
-<div class="apikeys_wrap">
-  <table class="noborder">
+<table class="table">
     <tr>
-        <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${c.user.api_key}</div></td>
+        <td><div class="truncate autoexpand">${c.user.api_key}</div></td>
         <td>
-            <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
+            <span class="label label-success">${_('Built-in')}</span>
         </td>
         <td>${_('Expires')}: ${_('Never')}</td>
         <td>
-            ${h.form(url('my_account_api_keys'),method='delete')}
+            ${h.form(url('my_account_api_keys_delete'))}
                 ${h.hidden('del_api_key',c.user.api_key)}
                 ${h.hidden('del_api_key_builtin',1)}
-                <button class="btn btn-mini btn-danger" type="submit"
+                <button class="btn btn-danger btn-xs" type="submit"
                         onclick="return confirm('${_('Confirm to reset this API key: %s') % c.user.api_key}');">
                     ${_('Reset')}
                 </button>
@@ -19,14 +18,14 @@
     </tr>
     %if c.user_api_keys:
         %for api_key in c.user_api_keys:
-          <tr class="${'expired' if api_key.expired else ''}">
-            <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${api_key.api_key}</div></td>
+          <tr class="${'expired' if api_key.is_expired else ''}">
+            <td><div class="truncate autoexpand">${api_key.api_key}</div></td>
             <td>${api_key.description}</td>
-            <td style="min-width: 80px">
+            <td>
                  %if api_key.expires == -1:
                   ${_('Expires')}: ${_('Never')}
                  %else:
-                    %if api_key.expired:
+                    %if api_key.is_expired:
                         ${_('Expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %else:
                         ${_('Expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
@@ -34,11 +33,11 @@
                  %endif
             </td>
             <td>
-                ${h.form(url('my_account_api_keys'),method='delete')}
+                ${h.form(url('my_account_api_keys_delete'))}
                     ${h.hidden('del_api_key',api_key.api_key)}
-                    <button class="btn btn-mini btn-danger" type="submit"
+                    <button class="btn btn-danger btn-xs" type="submit"
                             onclick="return confirm('${_('Confirm to remove this API key: %s') % api_key.api_key}');">
-                        <i class="icon-minus-circled"></i>
+                        <i class="icon-trashcan"></i>
                         ${_('Remove')}
                     </button>
                 ${h.end_form()}
@@ -48,32 +47,49 @@
     %else:
     <tr><td><div class="ip">${_('No additional API keys specified')}</div></td></tr>
     %endif
-  </table>
-</div>
+</table>
 
 <div>
     ${h.form(url('my_account_api_keys'), method='post')}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="description">${_('New API key')}:</label>
+            <div class="form-group">
+                <label class="control-label">${_('New API key')}</label>
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="description">${_('Description')}:</label>
+                <div>
+                    ${h.text('description', class_='form-control', placeholder=_('Description'))}
                 </div>
-                <div class="input">
-                    ${h.text('description', class_='medium', placeholder=_('Description'))}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="lifetime">${_('Lifetime')}:</label>
+                <div>
                     ${h.select('lifetime', '', c.lifetime_options)}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
+            </div>
     </div>
     ${h.end_form()}
 </div>
 
+<div class="alert alert-warning">
+<p>${_('''
+API keys are used to let scripts or services access %s using your
+account, as if you had provided the script or service with your actual
+password.
+''') % (c.site_name or 'Kallithea')}</p>
+<p>${_('''
+Like passwords, API keys should therefore never be shared with others,
+nor passed to untrusted scripts or services. If such sharing should
+happen anyway, reset the API key on this page to prevent further use.
+''')}</p>
+</div>
+
 <script>
     $(document).ready(function(){
         $("#lifetime").select2({
--- a/kallithea/templates/admin/my_account/my_account_emails.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_emails.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,29 +1,33 @@
 <div class="emails_wrap">
-  <table class="noborder">
+  <table class="table">
     <tr>
-    <td><div class="gravatar">${h.gravatar(c.user.email, size=16)}</div></td>
+    %if c.visual.use_gravatar:
+    <td>${h.gravatar_div(c.user.email, size=16)}</td>
+    %endif
     <td><div class="email">${c.user.email}</div></td>
     <td>
-        <span class="btn btn-mini btn-success disabled">${_('Primary')}</span>
+        <span class="label label-success">${_('Primary')}</span>
     </td>
     </tr>
     %if c.user_email_map:
         %for em in c.user_email_map:
           <tr>
-            <td><div class="gravatar">${h.gravatar(em.email, size=16)}</div></td>
+            %if c.visual.use_gravatar:
+            <td>${h.gravatar_div(em.email, size=16)}</td>
+            %endif
             <td><div class="email">${em.email}</div></td>
             <td>
-                ${h.form(url('my_account_emails'),method='delete')}
+                ${h.form(url('my_account_emails_delete'))}
                     ${h.hidden('del_email_id',em.email_id)}
-                    <i class="icon-minus-circled" style="color:#FF4444"></i>
+                    <i class="icon-trashcan"></i>
                     ${h.submit('remove_',_('Delete'),id="remove_email_%s" % em.email_id,
-                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
+                    class_="btn btn-default btn-xs", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
                 ${h.end_form()}
             </td>
           </tr>
         %endfor
     %else:
-    <tr><td><div class="ip">${_('No additional emails specified.')}</div></td></tr>
+    <tr><td colspan="${3 if c.visual.use_gravatar else 2}"><div class="ip">${_('No additional emails specified.')}</div></td></tr>
     %endif
   </table>
 </div>
@@ -31,21 +35,18 @@
 <div>
     ${h.form(url('my_account_emails'), method='post')}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_email">${_('New email address')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_email">${_('New email address')}:</label>
+                <div>
+                    ${h.text('new_email', class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('new_email', class_='medium')}
+            </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/my_account/my_account_password.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_password.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,40 +1,34 @@
-<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Change Your Account Password')}</div>
+<h4>${_('Change Your Account Password')}</h4>
 
 %if c.can_change_password:
 
 ${h.form(url('my_account_password'), method='post')}
 <div class="form">
-    <div class="fields">
-     <div class="field">
-        <div class="label">
-            <label for="current_password">${_('Current password')}:</label>
+    <div class="form-group">
+        <label class="control-label" for="current_password">${_('Current password')}:</label>
+        <div>
+            ${h.password('current_password',class_='form-control')}
         </div>
-        <div class="input">
-            ${h.password('current_password',class_='medium')}
-        </div>
-     </div>
+    </div>
 
-     <div class="field">
-        <div class="label">
-            <label for="new_password">${_('New password')}:</label>
+    <div class="form-group">
+        <label class="control-label" for="new_password">${_('New password')}:</label>
+        <div>
+            ${h.password('new_password',class_='form-control')}
         </div>
-        <div class="input">
-            ${h.password('new_password',class_='medium')}
-        </div>
-     </div>
+    </div>
 
-     <div class="field">
-        <div class="label">
-            <label for="new_password_confirmation">${_('Confirm new password')}:</label>
+    <div class="form-group">
+        <label class="control-label" for="new_password_confirmation">${_('Confirm new password')}:</label>
+        <div>
+            ${h.password('new_password_confirmation',class_='form-control')}
         </div>
-        <div class="input">
-            ${h.password('new_password_confirmation',class_='medium')}
-        </div>
-     </div>
+    </div>
 
+    <div class="form-group">
         <div class="buttons">
-          ${h.submit('save',_('Save'),class_="btn")}
-          ${h.reset('reset',_('Reset'),class_="btn")}
+            ${h.submit('save',_('Save'),class_="btn btn-default")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-default")}
         </div>
     </div>
 </div>
--- a/kallithea/templates/admin/my_account/my_account_perms.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_perms.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,9 @@
 ## permissions overview
 <div id="perms_container">
+<h4>${_('Current IP')}</h4>
+<div>
+  ${request.ip_addr}
+</div>
 <%namespace name="p" file="/base/perms_summary.html"/>
 ${p.perms_summary(c.perm_user.permissions, actions=False)}
 </div>
--- a/kallithea/templates/admin/my_account/my_account_profile.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_profile.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,65 +1,52 @@
 ${h.form(url('my_account'), method='post')}
     <div class="form">
+            <div class="form-group">
+                <label class="control-label">${_('Gravatar')}:</label>
+                <div class="gravatar_box clearfix">
+                    ${h.gravatar_div(c.user.email)}
+                </div>
+                <div>
+                    %if c.visual.use_gravatar:
+                        ${_('Change %s avatar at') % c.user.email} <a href="http://gravatar.com">gravatar.com</a>
+                    %else:
+                        ${_('Avatars are disabled')}
+                    %endif
+                </div>
+            </div>
 
-         <div class="field">
-           <div class="gravatar_box">
-               <div class="gravatar">
-                 ${h.gravatar(c.user.email)}
-               </div>
-                <p>
-                %if c.visual.use_gravatar:
-                <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
-                <br/>${_('Using')} ${c.user.email}
-                %else:
-                <strong>${_('Avatars are disabled')}</strong>
-                <br/>${c.user.email or _('Missing email, please update your user email address.')}
-                    [${_('Current IP')}: ${c.ip_addr}]
-                %endif
-               </p>
-           </div>
-         </div>
-
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="username">${_('Username')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="username">${_('Username')}:</label>
+                <div>
+                    ${h.text('username',class_='form-control', readonly=c.readonly('username'))}
                 </div>
-                <div class="input">
-                  ${h.text('username',class_='medium', readonly=c.readonly('username'))}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="name">${_('First Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('firstname',class_="medium", readonly=c.readonly('firstname'))}
+            <div class="form-group">
+                <label class="control-label" for="firstname">${_('First Name')}:</label>
+                <div>
+                    ${h.text('firstname',class_='form-control', readonly=c.readonly('firstname'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="lastname">${_('Last Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('lastname',class_="medium", readonly=c.readonly('lastname'))}
+            <div class="form-group">
+                <label class="control-label" for="lastname">${_('Last Name')}:</label>
+                <div>
+                    ${h.text('lastname',class_='form-control', readonly=c.readonly('lastname'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="email">${_('Email')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="email">${_('Email')}:</label>
+                <div>
+                    ${h.text('email',class_='form-control', readonly=c.readonly('email'))}
                 </div>
-                <div class="input">
-                    ${h.text('email',class_="medium", readonly=c.readonly('email'))}
-                </div>
-             </div>
+            </div>
 
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/my_account/my_account_repos.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_repos.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,107 +1,22 @@
-<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Repositories You Own')}</div>
-<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter"
-       placeholder="${_('quick filter...')}" value=""/>
+<h4>${_('Repositories You Own')}</h4>
 
-<div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
-<div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+<div>
+    <table class="table" id="datatable_list_wrap" width="100%"></table>
+</div>
 
 <script>
-function table_renderer(data){
-    var myDataSource = new YAHOO.util.DataSource(data);
-    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-    myDataSource.responseSchema = {
-        resultsList: "records",
-        fields: [
-            {key:"menu"},
-            {key:"raw_name"},
-            {key:"name"},
-            {key:"last_changeset"},
-            {key:"last_rev_raw"},
-            {key:"action"}
-            ]
-        };
-    myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-        // This is the filter function
-        var data     = res.results || [],
-            filtered = [],
-            i,l;
-
-        if (req) {
-            req = req.toLowerCase();
-            for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req);
-                if (pos != -1) {
-                    filtered.push(data[i]);
-                }
-            }
-            res.results = filtered;
-        }
-        return res;
-    }
-
-      // main table sorting
-      var myColumnDefs = [
-          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-          {key:"name",label:"${_('Name')}",sortable:true,
-              sortOptions: { sortFunction: nameSort }},
-          {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-              sortOptions: { sortFunction: revisionSort }},
-          {key:"action",label:"${_('Action')}",sortable:false}
-      ];
-
-      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-        sortedBy:{key:"name",dir:"asc"},
-        paginator: YUI_paginator(50, ['user-paginator']),
-
-        MSG_SORTASC:"${_('Click to sort ascending')}",
-        MSG_SORTDESC:"${_('Click to sort descending')}",
-        MSG_EMPTY:"${_('No records found.')}",
-        MSG_ERROR:"${_('Data error.')}",
-        MSG_LOADING:"${_('Loading...')}"
-      }
-      );
-      myDataTable.subscribe('postRenderEvent',function(oArgs) {
-          tooltip_activate();
-          quick_repo_menu();
-      });
-
-      var filterTimeout = null;
-
-      updateFilter = function() {
-          // Reset timeout
-          filterTimeout = null;
-
-          // Reset sort
-          var state = myDataTable.getState();
-          state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-          // Get filtered data
-          myDataSource.sendRequest(YUD.get('q_filter').value,{
-              success : myDataTable.onDataReturnInitializeTable,
-              failure : myDataTable.onDataReturnInitializeTable,
-              scope   : myDataTable,
-              argument: state
-          });
-
-      };
-      $('#q_filter').click(function(){
-          if(!$('#q_filter').hasClass('loaded')){
-              //TODO: load here full list later to do search within groups
-              $('#q_filter').addClass('loaded');
-          }
-      });
-
-      $('#q_filter').keyup(function(){
-          clearTimeout(filterTimeout);
-          filterTimeout = setTimeout(updateFilter,600);
-      });
-
-      if($('#q_filter').val()) {
-          updateFilter();
-      }
-
-    }
-
-table_renderer(${c.data |n});
+  var data = ${h.js(c.data)};
+  var myDataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "raw_name", "visible": false, searchable: false},
+            {data: "name", "orderData": 1, title: ${h.jshtml(_('Name'))}},
+            {data: "last_rev_raw", "visible": false, searchable: false},
+            {data: "last_changeset", "orderData": 3, title: ${h.jshtml(_('Tip'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
+        ],
+        order: [[2, "asc"]],
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
 </script>
--- a/kallithea/templates/admin/my_account/my_account_watched.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/my_account/my_account_watched.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,107 +1,21 @@
-<div style="font-size: 20px; color: #666666; padding: 0px 0px 10px 0px">${_('Repositories You are Watching')}</div>
-<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter"
-       placeholder="${_('quick filter...')}" value=""/>
+<h4>${_('Repositories You are Watching')}</h4>
 
-<div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
-<div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+<div>
+    <table class="table" id="datatable_list_wrap" width="100%"></table>
+</div>
 
 <script>
-function table_renderer(data){
-    var myDataSource = new YAHOO.util.DataSource(data);
-    myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-    myDataSource.responseSchema = {
-        resultsList: "records",
-        fields: [
-            {key:"menu"},
-            {key:"raw_name"},
-            {key:"name"},
-            {key:"last_changeset"},
-            {key:"last_rev_raw"},
-            {key:"action"}
-            ]
-        };
-    myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-        // This is the filter function
-        var data     = res.results || [],
-            filtered = [],
-            i,l;
-
-        if (req) {
-            req = req.toLowerCase();
-            for (i = 0; i<data.length; i++) {
-                var pos = data[i].raw_name.toLowerCase().indexOf(req);
-                if (pos != -1) {
-                    filtered.push(data[i]);
-                }
-            }
-            res.results = filtered;
-        }
-        return res;
-    }
-
-      // main table sorting
-      var myColumnDefs = [
-          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-          {key:"name",label:"${_('Name')}",sortable:true,
-              sortOptions: { sortFunction: nameSort }},
-          {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-              sortOptions: { sortFunction: revisionSort }},
-          {key:"action",label:"${_('Action')}",sortable:false}
-      ];
-
-      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-        sortedBy:{key:"name",dir:"asc"},
-        paginator: YUI_paginator(50, ['user-paginator']),
-
-        MSG_SORTASC:"${_('Click to sort ascending')}",
-        MSG_SORTDESC:"${_('Click to sort descending')}",
-        MSG_EMPTY:"${_('No records found.')}",
-        MSG_ERROR:"${_('Data error.')}",
-        MSG_LOADING:"${_('Loading...')}"
-      }
-      );
-      myDataTable.subscribe('postRenderEvent',function(oArgs) {
-          tooltip_activate();
-          quick_repo_menu();
-      });
-
-      var filterTimeout = null;
-
-      updateFilter = function() {
-          // Reset timeout
-          filterTimeout = null;
-
-          // Reset sort
-          var state = myDataTable.getState();
-          state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-          // Get filtered data
-          myDataSource.sendRequest(YUD.get('q_filter').value,{
-              success : myDataTable.onDataReturnInitializeTable,
-              failure : myDataTable.onDataReturnInitializeTable,
-              scope   : myDataTable,
-              argument: state
-          });
-
-      };
-      $('#q_filter').click(function(){
-          if(!$('#q_filter').hasClass('loaded')){
-              //TODO: load here full list later to do search within groups
-              $('#q_filter').addClass('loaded');
-          }
-      });
-
-      $('#q_filter').keyup(function(){
-          clearTimeout(filterTimeout);
-          filterTimeout = setTimeout(updateFilter,600);
-      });
-
-      if($('#q_filter').val()) {
-          updateFilter();
-      }
-
-    }
-
-table_renderer(${c.data |n});
+  var data = ${h.js(c.data)};
+  var myDataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "raw_name", "visible": false, searchable: false},
+            {data: "name", "orderData": 1, title: ${h.jshtml(_('Name'))}},
+            {data: "last_rev_raw", "visible": false, searchable: false},
+            {data: "last_changeset", "orderData": 3, title: ${h.jshtml(_('Tip'))}, searchable: false},
+        ],
+        order: [[2, "asc"]],
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
 </script>
--- a/kallithea/templates/admin/notifications/notifications.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%block name="title">
-    ${_('My Notifications')} ${c.authuser.username}
-</%block>
-
-<%def name="breadcrumbs_links()">
-    ${_('My Notifications')}
-</%def>
-
-<%block name="header_menu">
-    ${self.menu('admin')}
-</%block>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-
-      <div style="padding:14px 18px;text-align: right;float:left">
-      <span id='all' class="btn btn-small"><a href="${h.url.current()}">${_('All')}</a></span>
-      <span id='comment' class="btn btn-small"><a href="${h.url.current(type=c.comment_type)}">${_('Comments')}</a></span>
-      <span id='pull_request' class="btn btn-small"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull Requests')}</a></span>
-      </div>
-      %if c.notifications:
-      <div style="padding:14px 18px;text-align: right;float:right">
-      <span id='mark_all_read' class="btn btn-small">${_('Mark All Read')}</span>
-      </div>
-      %endif
-  <div id='notification_data'>
-    <%include file='notifications_data.html'/>
-  </div>
-</div>
-<script type="text/javascript">
-var url_action = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
-var run = function(){
-  $('.delete-notification').click(function(e){
-    var notification_id = e.currentTarget.id;
-    deleteNotification(url_action,notification_id);
-  });
-  $('.read-notification').click(function(e){
-    var notification_id = e.currentTarget.id;
-    readNotification(url_action,notification_id);
-  });
-}
-run();
-$('#mark_all_read').click(function(){
-    var url = "${h.url('notifications_mark_all_read', **request.GET.mixed())}";
-    asynchtml(url, $('#notification_data'), function(){run();});
-});
-
-var current_filter = "${c.current_filter}";
-$('#'+current_filter).addClass('active');
-</script>
-</%def>
--- a/kallithea/templates/admin/notifications/notifications_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-
-%if c.notifications:
-<%
-unread = lambda n:{False:'unread'}.get(n)
-%>
-
-
-<div class="notification-list  notification-table">
-%for notification in c.notifications:
-  <div id="notification_${notification.notification.notification_id}" class="container ${unread(notification.read)}">
-    <div class="notification-header">
-      <div class="gravatar">
-        ${h.gravatar(notification.notification.created_by_user.email, size=24)}
-      </div>
-      <div class="desc ${unread(notification.read)}">
-      <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
-
-      </div>
-      <div class="delete-notifications">
-        <span id="${notification.notification.notification_id}" class="delete-notification"><i class="icon-minus-circled" id="yui-gen24" style="color: #b94a48; padding: 2px;"></i></span>
-      </div>
-      %if not notification.read:
-      <div class="read-notifications">
-        <span id="${notification.notification.notification_id}" class="read-notification"><i class="icon-ok" id="yui-gen24" style="color: #4CBB17; padding: 2px;"></i></span>
-      </div>
-      %endif
-    </div>
-        <div class="notification-subject"></div>
-  </div>
-%endfor
-</div>
-
-<div class="notification-paginator">
-  <div class="pagination-wh pagination-left">
-  ${c.notifications.pager('$link_previous ~2~ $link_next',controller='admin/notifications',**request.GET.mixed())}
-  </div>
-</div>
-
-%else:
-    <div class="table">${_('No notifications here yet')}</div>
-%endif
--- a/kallithea/templates/admin/notifications/show_notification.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%block name="title">
-    ${_('Show Notification')} ${c.authuser.username}
-</%block>
-
-<%def name="breadcrumbs_links()">
-    ${h.link_to(_('Notifications'),h.url('notifications'))}
-    &raquo;
-    ${_('Show Notification')}
-</%def>
-
-<%block name="header_menu">
-    ${self.menu('admin')}
-</%block>
-
-<%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <div class="table">
-      <div id="notification_${c.notification.notification_id}">
-        <div class="notification-header">
-          <div class="gravatar">
-            ${h.gravatar(c.notification.created_by_user.email, size=24)}
-          </div>
-          <div class="desc">
-              ${c.notification.description}
-          </div>
-          <div class="delete-notifications">
-            <span id="${c.notification.notification_id}" class="delete-notification action"><i class="icon-minus-circled" id="yui-gen24"></i></span>
-          </div>
-        </div>
-        <div class="notification-body">
-        <div class="notification-subject">${h.literal(c.notification.subject)}</div>
-        %if c.notification.body:
-            ${h.rst_w_mentions(c.notification.body)}
-        %endif
-        </div>
-      </div>
-    </div>
-</div>
-<script type="text/javascript">
-var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
-var main = "${url('notifications')}";
-   $('.delete-notification').click(function(e){
-       var notification_id = e.currentTarget.id;
-       deleteNotification(url,notification_id,[function(){window.location=main}]);
-   });
-</script>
-</%def>
--- a/kallithea/templates/admin/permissions/permissions.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/permissions/permissions.html	Sun Mar 31 21:28:56 2019 +0200
@@ -17,22 +17,22 @@
 
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="panel-body settings">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='globals' else ''}"><a href="${h.url('admin_permissions')}">${_('Global')}</a></li>
         <li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('admin_permissions_ips')}">${_('IP Whitelist')}</a></li>
         <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('admin_permissions_perms')}">${_('Show Permissions')}</a></li>
       </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+      <div>
         <%include file="/admin/permissions/permissions_${c.active}.html"/>
+      </div>
     </div>
 </div>
 
--- a/kallithea/templates/admin/permissions/permissions_globals.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/permissions/permissions_globals.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,117 +1,100 @@
 ${h.form(url('admin_permissions'), method='post')}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="anonymous">${_('Anonymous access')}:</label>
-                </div>
-                <div class="checkboxes">
-                    <div class="checkbox">
+            <div class="form-group">
+                <label class="control-label" for="anonymous">${_('Anonymous access')}:</label>
+                <div class="form-inline">
+                    <label>
                         ${h.checkbox('anonymous',True)}
-                    </div>
-                     <span class="help-block">${h.literal(_('Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions.' % (h.link_to('*default*',h.url('admin_permissions_perms')))))}</span>
+                        <span>${_('Allow anonymous access')}</span>
+                    </label>
+                    <span class="help-block">${h.literal(_('Allow access to Kallithea without needing to log in. Anonymous users use %s user permissions.' % (h.link_to('*default*',h.url('admin_permissions_perms')))))}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_repo_perm">${_('Repository')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_repo_perm','',c.repo_perms_choices)}
-                    ${h.checkbox('overwrite_default_repo','true')}
-                    <label for="overwrite_default_repo">
-                    <span class="tooltip"
-                    title="${_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost')}">
-                    ${_('Apply to all existing repositories')}</span> </label>
+            <div class="form-group">
+                <label class="control-label" for="default_repo_perm">${_('Repository')}:</label>
+                <div class="form-inline">
+                    ${h.select('default_repo_perm','',c.repo_perms_choices,class_='form-control')}
+                    <label>
+                        ${h.checkbox('overwrite_default_repo','true')}
+                        <span data-toggle="tooltip" title="${_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost')}">
+                            ${_('Apply to all existing repositories')}
+                        </span>
+                    </label>
                     <span class="help-block">${_('Permissions for the Default user on new repositories.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_group_perm">${_('Repository group')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_group_perm','',c.group_perms_choices)}
-                    ${h.checkbox('overwrite_default_group','true')}
-                    <label for="overwrite_default_group">
-                    <span class="tooltip"
-                    title="${_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost')}">
-                    ${_('Apply to all existing repository groups')}</span> </label>
+            <div class="form-group">
+                <label class="control-label" for="default_group_perm">${_('Repository group')}:</label>
+                <div class="form-inline">
+                    ${h.select('default_group_perm','',c.group_perms_choices,class_='form-control')}
+                    <label>
+                        ${h.checkbox('overwrite_default_group','true')}
+                        <span data-toggle="tooltip" title="${_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost')}">
+                            ${_('Apply to all existing repository groups')}
+                        </span>
+                    </label>
                     <span class="help-block">${_('Permissions for the Default user on new repository groups.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="default_group_perm">${_('User group')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_user_group_perm','',c.user_group_perms_choices)}
-                    ${h.checkbox('overwrite_default_user_group','true')}
-                    <label for="overwrite_default_user_group">
-                    <span class="tooltip"
-                    title="${_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost')}">
-                    ${_('Apply to all existing user groups')}</span></label>
+            <div class="form-group">
+                <label class="control-label" for="default_user_group_perm">${_('User group')}:</label>
+                <div class="form-inline">
+                    ${h.select('default_user_group_perm','',c.user_group_perms_choices,class_='form-control')}
+                    <label>
+                        ${h.checkbox('overwrite_default_user_group','true')}
+                        <span data-toggle="tooltip" title="${_('All default permissions on each user group will be reset to chosen permission, note that all custom default permission on user groups will be lost')}">
+                            ${_('Apply to all existing user groups')}
+                        </span>
+                    </label>
                     <span class="help-block">${_('Permissions for the Default user on new user groups.')}</span>
                 </div>
             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_repo_create">${_('Top level repository creation')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_repo_create','',c.repo_create_choices)}
+            <div class="form-group">
+                <label class="control-label" for="default_repo_create">${_('Top level repository creation')}:</label>
+                <div>
+                    ${h.select('default_repo_create','',c.repo_create_choices,class_='form-control')}
                     <span class="help-block">${_('Enable this to allow non-admins to create repositories at the top level.')}</span>
                     <span class="help-block">${_('Note: This will also give all users API access to create repositories everywhere. That might change in future versions.')}</span>
                 </div>
-             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="create_on_write">${_('Repository creation with group write access')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('create_on_write','',c.repo_create_on_write_choices)}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="create_on_write">${_('Repository creation with group write access')}:</label>
+                <div>
+                    ${h.select('create_on_write','',c.repo_create_on_write_choices,class_='form-control')}
                     <span class="help-block">${_('With this, write permission to a repository group allows creating repositories inside that group. Without this, group write permissions mean nothing.')}</span>
                 </div>
             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_user_group_create">${_('User group creation')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_user_group_create','',c.user_group_create_choices)}
+            <div class="form-group">
+                <label class="control-label" for="default_user_group_create">${_('User group creation')}:</label>
+                <div>
+                    ${h.select('default_user_group_create','',c.user_group_create_choices,class_='form-control')}
                     <span class="help-block">${_('Enable this to allow non-admins to create user groups.')}</span>
                 </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_fork">${_('Repository forking')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_fork','',c.fork_choices)}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="default_fork">${_('Repository forking')}:</label>
+                <div>
+                    ${h.select('default_fork','',c.fork_choices,class_='form-control')}
                     <span class="help-block">${_('Enable this to allow non-admins to fork repositories.')}</span>
                 </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_register">${_('Registration')}:</label>
-                </div>
-                <div class="select">
-                    ${h.select('default_register','',c.register_choices)}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="default_register">${_('Registration')}:</label>
+                <div>
+                    ${h.select('default_register','',c.register_choices,class_='form-control')}
                 </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="default_extern_activate">${_('External auth account activation')}:</label>
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="default_extern_activate">${_('External auth account activation')}:</label>
+                <div>
+                    ${h.select('default_extern_activate','',c.extern_activate_choices,class_='form-control')}
                 </div>
-                <div class="select">
-                    ${h.select('default_extern_activate','',c.extern_activate_choices)}
+            </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/permissions/permissions_ips.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/permissions/permissions_ips.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,16 +1,15 @@
-<div class="ips_wrap">
-      <table class="noborder">
+<table class="table">
       %if c.user_ip_map:
         %for ip in c.user_ip_map:
           <tr>
               <td><div class="ip">${ip.ip_addr}</div></td>
               <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
               <td>
-                ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
+                ${h.form(url('edit_user_ips_delete', id=c.user.user_id))}
                     ${h.hidden('del_ip_id',ip.ip_id)}
                     ${h.hidden('default_user', 'True')}
-                    <i class="icon-minus-circled" style="color:#FF4444"></i> ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="action_button", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
+                    <i class="icon-trashcan"></i>${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
+                    class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
                 ${h.end_form()}
               </td>
           </tr>
@@ -18,26 +17,22 @@
        %else:
         <tr><td><div class="ip">${_('All IP addresses are allowed.')}</div></td></tr>
        %endif
-      </table>
-</div>
+</table>
 
-${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
+${h.form(url('edit_user_ips_update', id=c.user.user_id))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_ip">${_('New IP address')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_ip">${_('New IP address')}:</label>
+                <div>
+                    ${h.hidden('default_user', 'True')}
+                    ${h.text('new_ip', class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.hidden('default_user', 'True')}
-                    ${h.text('new_ip', class_='medium')}
+            </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/repo_groups/repo_group_add.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_add.html	Sun Mar 31 21:28:56 2019 +0200
@@ -18,57 +18,46 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
     ${h.form(url('repos_groups'))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="group_name">${_('Group name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('group_name',class_='small')}
-                </div>
-             </div>
-
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="group_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea-repo editor">
-                    ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
+    <div class="form panel-body settings">
+            <div class="form-group">
+                <label class="control-label" for="group_name">${_('Group name')}:</label>
+                <div>
+                    ${h.text('group_name',class_='form-control')}
                 </div>
             </div>
 
-            <div class="field">
-                 <div class="label">
-                     <label for="group_parent_id">${_('Group parent')}:</label>
-                 </div>
-                 <div class="input">
-                     ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
-                 </div>
+            <div class="form-group">
+                <label class="control-label" for="group_description">${_('Description')}:</label>
+                <div>
+                    ${h.textarea('group_description',cols=23,rows=5,class_='form-control')}
+                </div>
             </div>
 
-            <div id="copy_perms" class="field">
-                <div class="label label-checkbox">
-                    <label for="group_copy_permissions">${_('Copy parent group permissions')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="parent_group_id">${_('Group parent')}:</label>
+                <div>
+                    ${h.select('parent_group_id',request.GET.get('parent_group'),c.repo_groups,class_='form-control')}
                 </div>
-                <div class="checkboxes">
+            </div>
+
+            <div id="copy_perms" class="form-group">
+                <label class="control-label" for="group_copy_permissions">${_('Copy parent group permissions')}:</label>
+                <div>
                     ${h.checkbox('group_copy_permissions',value="True")}
                     <span class="help-block">${_('Copy permission set from parent repository group.')}</span>
                 </div>
             </div>
 
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
@@ -82,11 +71,11 @@
                 $('#copy_perms').hide();
             }
         }
-        $("#group_parent_id").select2({
+        $("#parent_group_id").select2({
             'dropdownAutoWidth': true
         });
-        setCopyPermsOption($('#group_parent_id').val());
-        $("#group_parent_id").on("change", function(e) {
+        setCopyPermsOption($('#parent_group_id').val());
+        $("#parent_group_id").on("change", function(e) {
             setCopyPermsOption(e.val);
         });
         $('#group_name').focus();
--- a/kallithea/templates/admin/repo_groups/repo_group_edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,36 +15,32 @@
     &raquo; ${h.link_to(c.repo_group.name,h.url('repos_group_home',group_name=c.repo_group.group_name))}
 </%def>
 
-<%def name="breadcrumbs_side_links()">
-    <ul class="links">
-      <li>
-          <a href="${h.url('new_repos_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add Child Group')}</a>
-      </li>
-    </ul>
-</%def>
-
 <%block name="header_menu">
     ${self.menu('admin')}
 </%block>
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
-        ${self.breadcrumbs()}
-        ${self.breadcrumbs_side_links()}
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+          ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="${h.url('new_repos_group', parent_group=c.repo_group.group_id)}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Add Child Group')}</a>
+        </div>
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="panel-body settings">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_repo_group', group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
         <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_repo_group_advanced', group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
         <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
       </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+      <div>
         <%include file="/admin/repo_groups/repo_group_edit_${c.active}.html"/>
+      </div>
     </div>
 </div>
 </%def>
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('Repository Group: %s') % c.repo_group.group_name}</div>
+<h4>${_('Repository Group: %s') % c.repo_group.group_name}</h4>
 
 <dl class="dl-horizontal">
 <%
@@ -7,21 +7,21 @@
     (_('Total repositories'), c.repo_group.repositories_recursive_count, ''),
     (_('Children groups'), c.repo_group.children.count(), ''),
     (_('Created on'), h.fmt_date(c.repo_group.created_on), ''),
-    (_('Owner'), h.person(c.repo_group.user), ''),
+    (_('Owner'), h.person(c.repo_group.owner), ''),
  ]
 %>
 %for dt, dd, tt in elems:
-  <dt style="width:150px; text-align: left">${dt}:</dt>
-  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+  <dt>${dt}:</dt>
+  <dd title="${tt}">${dd}</dd>
 %endfor
 </dl>
 
-${h.form(h.url('repos_group', group_name=c.repo_group.group_name),method='delete')}
-    <button class="btn btn-small btn-danger" type="submit"
+${h.form(h.url('delete_repo_group', group_name=c.repo_group.group_name))}
+    <button class="btn btn-danger btn-sm" type="submit"
             onclick="return confirm('${ungettext('Confirm to delete this group: %s with %s repository',
           'Confirm to delete this group: %s with %s repositories',
  c.repo_group.repositories_recursive_count) % (c.repo_group.group_name, c.repo_group.repositories_recursive_count)}');">
-        <i class="icon-minus-circled"></i>
+        <i class="icon-trashcan"></i>
         ${_('Delete this repository group')}
     </button>
 ${h.end_form()}
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_perms.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,13 @@
-${h.form(url('edit_repo_group_perms', group_name=c.repo_group.group_name),method='put')}
+${h.form(url('edit_repo_group_perms', group_name=c.repo_group.group_name))}
 <div class="form">
-   <div class="fields">
-        <div class="field">
-            <table id="permissions_manage" class="noborder">
+    <div>
+        <div>
+            <table id="permissions_manage" class="table">
                 <tr>
-                    <td>${_('None')}</td>
-                    <td>${_('Read')}</td>
-                    <td>${_('Write')}</td>
-                    <td>${_('Admin')}</td>
+                    <td>${_('None')}<br />(${_('Not visible')})</td>
+                    <td>${_('Read')}<br />(${_('Visible')})</td>
+                    <td>${_('Write')}<br />(${_('Add repos')})</td>
+                    <td>${_('Admin')}<br />(${_('Add/Edit groups')})</td>
                     <td>${_('User/User Group')}</td>
                     <td></td>
                 </tr>
@@ -15,12 +15,12 @@
                 %for r2p in c.repo_group.repo_group_to_perm:
                     ##forbid revoking permission from yourself, except if you're an super admin
                     <tr id="id${id(r2p.user.username)}">
-                      %if c.authuser.user_id != r2p.user.user_id or c.authuser.is_admin:
+                      %if request.authuser.user_id != r2p.user.user_id or request.authuser.is_admin:
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
@@ -30,9 +30,9 @@
                         </td>
                         <td>
                           %if r2p.user.username !='default':
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                             <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                             <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                           %endif
                         </td>
                       %else:
@@ -40,11 +40,11 @@
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
                             ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                         </td>
-                        <td><i class="icon-user"></i> ${_('Admin')}</td>
+                        <td><i class="icon-user"></i>${_('Admin')}</td>
                       %endif
                     </tr>
                 %endfor
@@ -56,7 +56,7 @@
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             <i class="icon-users"></i>
                             %if h.HasPermissionAny('hg.admin')():
                              <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">
@@ -67,35 +67,19 @@
                             %endif
                         </td>
                         <td>
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
+                            <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                         </td>
                     </tr>
                 %endfor
-
-                <%
-                _tmpl = h.literal("""' \
-                    <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="group.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td class="ac"> \
-                        <div class="perm_ac" id="perm_ac_{0}"> \
-                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                            <div id="perm_container_{0}"></div> \
-                        </div> \
-                    </td> \
-                    <td></td>'""")
-                %>
-                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                ## New entries added by addPermAction here.
+                <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
                 <tr>
                     <td colspan="6">
-                        <span id="add_perm" style="cursor: pointer;">
-                            <i class="icon-plus"></i> ${_('Add new')}
-                        </span>
+                        <button type="button" id="add_perm" class="btn btn-link btn-xs">
+                            <i class="icon-plus"></i>${_('Add new')}
+                        </button>
                     </td>
                 </tr>
                 <tr>
@@ -111,16 +95,16 @@
             </table>
         </div>
         <div class="buttons">
-          ${h.submit('save',_('Save'),class_="btn")}
-          ${h.reset('reset',_('Reset'),class_="btn")}
+            ${h.submit('save',_('Save'),class_="btn btn-default")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-default")}
         </div>
-   </div>
+    </div>
 </div>
 ${h.end_form()}
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_repo_group_perms', group_name=c.repo_group.group_name)}";
+        url = ${h.jshtml(h.url('edit_repo_group_perms_delete', group_name=c.repo_group.group_name))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             var recursive = $('input[name=recursive]:checked').val();
@@ -133,7 +117,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction('group');
         });
     });
 </script>
--- a/kallithea/templates/admin/repo_groups/repo_group_edit_settings.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_edit_settings.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,64 +1,57 @@
 ## -*- coding: utf-8 -*-
-${h.form(url('repos_group',group_name=c.repo_group.group_name),method='put')}
+${h.form(url('update_repos_group',group_name=c.repo_group.group_name))}
 <div class="form">
-    <!-- fields -->
-    <div class="fields">
-        <div class="field">
-            <div class="label">
-                <label for="group_name">${_('Group name')}:</label>
-            </div>
-            <div class="input">
-                ${h.text('group_name',class_='medium')}
+        <div class="form-group">
+            <label class="control-label" for="group_name">${_('Group name')}:</label>
+            <div>
+                ${h.text('group_name',class_='form-control')}
             </div>
         </div>
 
-        <div class="field">
-            <div class="label label-textarea">
-                <label for="group_description">${_('Description')}:</label>
-            </div>
-            <div class="textarea text-area editor">
-                ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
+        <div class="form-group">
+            <label class="control-label" for="group_description">${_('Description')}:</label>
+            <div>
+                ${h.textarea('group_description',cols=23,rows=5,class_='form-control')}
             </div>
         </div>
 
-        <div class="field">
-            <div class="label">
-                <label for="group_parent_id">${_('Group parent')}:</label>
-            </div>
-            <div class="input">
-                ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
+        <div class="form-group">
+            <label class="control-label" for="parent_group_id">${_('Group parent')}:</label>
+            <div>
+                ${h.select('parent_group_id','',c.repo_groups,class_='form-control')}
             </div>
         </div>
-        <div class="field">
-            <div class="label label-checkbox">
-                <label for="enable_locking">${_('Enable locking')}:</label>
-            </div>
-            <div class="checkboxes">
+
+        <div class="form-group">
+            <label class="control-label" for="enable_locking">${_('Enable locking')}:</label>
+            <div>
                 ${h.checkbox('enable_locking',value="True")}
                 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
             </div>
         </div>
-        <div class="buttons">
-          ${h.submit('save',_('Save'),class_="btn")}
-          ${h.reset('reset',_('Reset'),class_="btn")}
+
+        <div class="form-group">
+            <div class="buttons">
+                ${h.submit('save',_('Save'),class_="btn btn-default")}
+                ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+            </div>
         </div>
-    </div>
 </div>
 ${h.end_form()}
 
-${h.form(url('delete_repo_group', group_name=c.repo_group.group_name),method='delete')}
+${h.form(url('delete_repo_group', group_name=c.repo_group.group_name))}
 <div class="form">
-    <div class="fields">
-        <div class="field" style="border:none;color:#888">
-            ${h.submit('remove_%s' % c.repo_group.group_name,_('Remove this group'),class_="btn red",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
+        <div class="form-group">
+            <div class="buttons">
+                ${h.submit('remove_%s' % c.repo_group.group_name,_('Remove this group'),class_="btn btn-danger",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
+            </div>
         </div>
-    </div>
 </div>
 ${h.end_form()}
 
 <script>
     $(document).ready(function(){
-        $("#group_parent_id").select2({
+        $("#parent_group_id").select2({
             'dropdownAutoWidth': true
         });
     });
--- a/kallithea/templates/admin/repo_groups/repo_group_show.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_group_show.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,9 @@
 ## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
 <%block name="title">
-    ${_('%s Repository group dashboard') % c.group.group_name}
+    ${_('Repository group %s') % c.group.group_name}
 </%block>
 
-<%def name="breadcrumbs()">
-    <span class="groups_breadcrumbs">
-    ${h.link_to(_('Home'),h.url('/'))}
-    %if c.group.parent_group:
-        &raquo; ${h.link_to(c.group.parent_group.name,h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
-    %endif
-    &raquo; "${c.group.name}" ${_('with')}
-    </span>
-</%def>
-
 <%block name="header_menu">
     ${self.menu('repositories')}
 </%block>
--- a/kallithea/templates/admin/repo_groups/repo_groups.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repo_groups/repo_groups.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,7 +6,6 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
     ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('Repository Groups')}
 </%def>
 
@@ -16,41 +15,37 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
             %if h.HasPermissionAny('hg.admin')():
-             <li>
-               <a href="${h.url('new_repos_group')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add Repository Group')}</a>
-             </li>
+               <a href="${h.url('new_repos_group')}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Add Repository Group')}</a>
             %endif
-        </ul>
+        </div>
     </div>
-    <!-- end box / title -->
-    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
-    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+    <div class="panel-body">
+        <table class="table" id="datatable_list_wrap" width="100%"></table>
+    </div>
 </div>
 <script>
-  var data = ${c.data|n};
-  var fields = [
-    {key: "group_name"},
-    {key: "raw_name"},
-    {key: "desc"},
-    {key: "repos"},
-    {key: "owner"},
-    {key: "action"}
-  ];
-  var column_defs = [
-    {key:"group_name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
-    {key:"desc",label:"${_('Description')}",sortable:true},
-    {key:"repos",label:"${_('Number of Top-level Repositories')}",sortable:true},
-    {key:"owner",label:"${_('Owner')}",sortable:true},
-    {key:"action",label:"${_('Action')}",sortable:false}
-  ];
-  var counter = YUD.get('repo_group_count');
-  var sort_key = "group_name";
-  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+  var data = ${h.js(c.data)};
+  var myDataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "raw_name", visible: false, searchable: false},
+            {data: "group_name", orderData: 0, title: ${h.jshtml(_('Name'))}},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+            {data: "repos", title: ${h.jshtml(_('Number of Top-level Repositories'))}, searchable: false},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
+        ],
+        drawCallback: updateRowCountCallback($("#repo_group_count")),
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
+
 </script>
 </%def>
--- a/kallithea/templates/admin/repos/repo_add.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_add.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,7 +6,7 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    %if c.authuser.is_admin:
+    %if request.authuser.is_admin:
     ${h.link_to(_('Admin'),h.url('admin_home'))}
     &raquo;
     ${h.link_to(_('Repositories'),h.url('repos'))}
@@ -24,11 +24,12 @@
 </%block>
 
 <%def name="main()">
-    <div class="box">
-        <!-- box / title -->
-        <div class="title">
+    <div class="panel panel-primary">
+        <div class="panel-heading clearfix">
             ${self.breadcrumbs()}
         </div>
-        <%include file="repo_add_base.html"/>
+        <div class="panel-body settings">
+            <%include file="repo_add_base.html"/>
+        </div>
     </div>
 </%def>
--- a/kallithea/templates/admin/repos/repo_add_base.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_add_base.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,85 +2,68 @@
 
 ${h.form(url('repos'))}
 <div class="form">
-    <!-- fields -->
-    <div class="fields">
-        <div class="field">
-            <div class="label">
-                <label for="repo_name">${_('Name')}:</label>
-            </div>
-            <div class="input">
-                ${h.text('repo_name',class_="small")}
+        <div class="form-group">
+            <label class="control-label" for="repo_name">${_('Name')}:</label>
+            <div>
+                ${h.text('repo_name',class_='form-control')}
             </div>
          </div>
-        <div id="remote_clone" class="field">
-            <div class="label">
-                <label for="clone_uri">${_('Clone remote repository')}:</label>
-            </div>
-            <div class="input">
-                ${h.text('clone_uri',class_="small")}
+        <div id="remote_clone" class="form-group">
+            <label class="control-label" for="clone_uri">${_('Clone remote repository')}:</label>
+            <div>
+                ${h.text('clone_uri',class_='form-control')}
                 <span class="help-block">
                     ${_('Optional: URL of a remote repository. If set, the repository will be created as a clone from this URL.')}
                 </span>
             </div>
         </div>
-        <div class="field">
-            <div class="label label-textarea">
-                <label for="repo_description">${_('Description')}:</label>
-            </div>
-            <div class="textarea-repo editor">
-                ${h.textarea('repo_description')}
+        <div class="form-group">
+            <label class="control-label" for="repo_description">${_('Description')}:</label>
+            <div>
+                ${h.textarea('repo_description',class_='form-control')}
                 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
             </div>
         </div>
-        <div class="field">
-             <div class="label">
-                 <label for="repo_group">${_('Repository group')}:</label>
-             </div>
-             <div class="input">
-                 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
-                 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
-             </div>
+        <div class="form-group">
+            <label class="control-label" for="repo_group">${_('Repository group')}:</label>
+            <div>
+                ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_='form-control')}
+                <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
+            </div>
         </div>
-        <div id="copy_perms" class="field">
-            <div class="label label-checkbox">
-                <label for="repo_copy_permissions">${_('Copy parent group permissions')}:</label>
-            </div>
-            <div class="checkboxes">
+        <div id="copy_perms" class="form-group">
+            <label class="control-label" for="repo_copy_permissions">${_('Copy parent group permissions')}:</label>
+            <div>
                 ${h.checkbox('repo_copy_permissions',value="True")}
                 <span class="help-block">${_('Copy permission set from parent repository group.')}</span>
             </div>
         </div>
-        <div class="field">
-            <div class="label">
-                <label for="repo_type">${_('Type')}:</label>
-            </div>
-            <div class="input">
-                ${h.select('repo_type','hg',c.backends,class_="small")}
+        <div class="form-group">
+            <label class="control-label" for="repo_type">${_('Type')}:</label>
+            <div>
+                ${h.select('repo_type','hg',c.backends,class_='form-control')}
                 <span class="help-block">${_('Type of repository to create.')}</span>
             </div>
         </div>
-        <div class="field">
-            <div class="label">
-                <label for="repo_landing_rev">${_('Landing revision')}:</label>
-            </div>
-            <div class="input">
-                ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
+        <div class="form-group">
+            <label class="control-label" for="repo_landing_rev">${_('Landing revision')}:</label>
+            <div>
+                ${h.select('repo_landing_rev','',c.landing_revs,class_='form-control')}
                 <span class="help-block">${_('Default revision for files page, downloads, full text search index and readme generation')}</span>
             </div>
         </div>
-        <div class="field">
-            <div class="label label-checkbox">
-                <label for="repo_private">${_('Private repository')}:</label>
-            </div>
-            <div class="checkboxes">
+        <div class="form-group">
+            <label class="control-label" for="repo_private">${_('Private repository')}:</label>
+            <div>
                 ${h.checkbox('repo_private',value="True")}
                 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
             </div>
         </div>
-        <div class="buttons">
-          ${h.submit('add',_('Add'),class_="btn")}
+        <div class="form-group">
+            <div class="buttons">
+                ${h.submit('add',_('Add'),class_="btn btn-default")}
+            </div>
         </div>
-    </div>
 </div>
 <script>
     $(document).ready(function(){
--- a/kallithea/templates/admin/repos/repo_creating.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_creating.html	Sun Mar 31 21:28:56 2019 +0200
@@ -17,26 +17,27 @@
     ${self.menu('repositories')}
 </%block>
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div style="display:table; padding: 10px 0px; font-size: 14px;font-weight: bold;margin-right: auto;margin-left: auto">
-        ${_('Repository "%(repo_name)s" is being created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
-    </div>
+    <div class="panel-body">
+            <h4 class="text-center">
+                ${_('Repository "%(repo_name)s" is being created, you will be redirected when this process is finished.' % {'repo_name':c.repo_name})}
+            </h4>
 
-    <div id="progress" style="width: 500px;margin-left: auto; margin-right: auto">
-        <div class="progress progress-striped active">
-          <div class="progress-bar progress-bar" role="progressbar"
-               aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
-          </div>
+        <div id="progress">
+            <div class="progress progress-striped active">
+                <div class="progress-bar" role="progressbar"
+                    aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
+                </div>
+            </div>
         </div>
-    </div>
-    <div id="progress_error" style="display: none;">
-        <div style="font-weight: bold; color:#aa1111">
-        ${_("We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator.")}
+        <div id="progress_error" style="display: none;">
+            <div class="alert alert-danger">
+                ${_("We're sorry but error occurred during this operation. Please check your Kallithea server logs, or contact administrator.")}
+            </div>
         </div>
     </div>
 </div>
@@ -44,11 +45,11 @@
 <script>
 (function worker() {
   $.ajax({
-    url: '${h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id)}',
+    url: ${h.js(h.url('repo_check_home', repo_name=c.repo_name, repo=c.repo, task_id=c.task_id))},
     success: function(data) {
       if(data.result === true){
           //redirect to created fork if our ajax loop tells us to do so.
-          window.location = "${h.url('summary_home', repo_name = c.repo)}";
+          window.location = ${h.js(h.url('summary_home', repo_name = c.repo))};
       }
     },
     complete: function(resp, status) {
--- a/kallithea/templates/admin/repos/repo_edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -18,24 +18,9 @@
 
 <%def name="main()">
 ${self.repo_context_bar('options')}
-<div class="box" style="overflow:auto">
-    <!--<div class="title">-->
-        <!--${self.breadcrumbs()}-->
-    <!--</div>-->
-
-    ##main
-    <div style="width: 150px; float:left">
+<div class="panel panel-primary">
+    <div class="panel-body settings">
         <ul class="nav nav-pills nav-stacked">
-          <!--<li>-->
-           <!--<div class="gravatar_box" style="height: 26px">-->
-             <!--<div class="gravatar" style="float: left">-->
-                <!--<i class="icon-users" style="font-size: 26px"></i>-->
-             <!--</div>-->
-               <!--<div style="margin:10px 0px 10px 0px; color:#5f5f5f; float:left">-->
-                <!--<strong>${'repo-info'}</strong>-->
-               <!--</div>-->
-           <!--</div>-->
-          <!--</li>-->
           <li class="${'active' if c.active=='settings' else ''}">
               <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
           </li>
@@ -58,10 +43,10 @@
               <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
           </li>
         </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
-        <%include file="/admin/repos/repo_edit_${c.active}.html"/>
+        <div>
+            <%include file="/admin/repos/repo_edit_${c.active}.html"/>
+        </div>
     </div>
 </div>
 
--- a/kallithea/templates/admin/repos/repo_edit_advanced.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_advanced.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,12 +1,10 @@
 <h3>${_('Parent')}</h3>
-${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
+${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name))}
 <div class="form">
-       ${h.select('id_fork_of','',c.repos_list,class_="medium")}
-       ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small")}
-       <div class="field" style="border:none;color:#888">
-       <ul>
-            <li>${_('''Manually set this repository as a fork of another from the list.''')}</li>
-       </ul>
+       ${h.select('id_fork_of','',c.repos_list)}
+       ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-default btn-sm")}
+       <div class="text-muted">
+       ${_('''Manually set this repository as a fork of another from the list.''')}
        </div>
 </div>
 ${h.end_form()}
@@ -20,35 +18,33 @@
 </script>
 
 <h3>${_('Public Journal Visibility')}</h3>
-${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
+${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name))}
 <div class="form">
-  <div class="field">
+  <div>
   %if c.in_public_journal:
-    <button class="btn btn-small" type="submit">
+    <button class="btn btn-default btn-sm" type="submit">
         <i class="icon-minus"></i>
         ${_('Remove from public journal')}
     </button>
   %else:
-    <button class="btn btn-small" type="submit">
+    <button class="btn btn-default btn-sm" type="submit">
         <i class="icon-plus"></i>
         ${_('Add to Public Journal')}
     </button>
   %endif
   </div>
- <div class="field" style="border:none;color:#888">
- <ul>
-      <li>${_('All actions done in this repository will be visible to everyone in the public journal.')}</li>
- </ul>
+ <div class="text-muted">
+ ${_('All actions done in this repository will be visible to everyone in the public journal.')}
  </div>
 </div>
 ${h.end_form()}
 
 <h3>${_('Change Locking')}</h3>
-${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
+${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name))}
 <div class="form">
       %if c.repo_info.locked[0]:
         ${h.hidden('set_unlock', '1')}
-        <button class="btn btn-small" type="submit"
+        <button class="btn btn-default btn-sm" type="submit"
                 onclick="return confirm('${_('Confirm to unlock repository.')}');">
             <i class="icon-lock-open-alt"></i>
             ${_('Unlock Repository')}
@@ -56,39 +52,40 @@
        ${_('Locked by %s on %s') % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
       %else:
         ${h.hidden('set_lock', '1')}
-        <button class="btn btn-small" type="submit"
+        <button class="btn btn-default btn-sm" type="submit"
                 onclick="return confirm('${_('Confirm to lock repository.')}');">
             <i class="icon-lock"></i>
             ${_('Lock Repository')}
         </button>
         ${_('Repository is not locked')}
       %endif
-   <div class="field" style="border:none;color:#888">
-   <ul>
-        <li>${_('Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push.')}
-        </li>
-   </ul>
+   <div class="text-muted">
+   ${_('Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository.  The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push.')}
    </div>
 </div>
 ${h.end_form()}
 
 <h3>${_('Delete')}</h3>
-${h.form(url('delete_repo', repo_name=c.repo_name), method='delete')}
+${h.form(url('delete_repo', repo_name=c.repo_name))}
 <div class="form">
-    <button class="btn btn-small btn-danger" type="submit"
+    <button class="btn btn-danger btn-sm" type="submit"
             onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
-        <i class="icon-minus-circled"></i>
+        <i class="icon-trashcan"></i>
         ${_('Delete this Repository')}
     </button>
     %if c.repo_info.forks.count():
         ${ungettext('This repository has %s fork', 'This repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
-        <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
-        <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
+        <label>
+            <input type="radio" name="forks" value="detach_forks" checked="checked"/>
+            ${_('Detach forks')}
+        </label>
+        <label>
+            <input type="radio" name="forks" value="delete_forks" />
+            ${_('Delete forks')}
+        </label>
     %endif
-    <div class="field" style="border:none;color:#888">
-        <ul>
-        <li>${_('The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it.')}</li>
-        </ul>
+    <div class="text-muted">
+        ${_('The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it.')}
     </div>
 </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/repos/repo_edit_caches.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_caches.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,21 +1,18 @@
-${h.form(url('edit_repo_caches', repo_name=c.repo_name), method='put')}
+${h.form(url('update_repo_caches', repo_name=c.repo_name))}
 <div class="form">
-   <div class="fields">
-       ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache.')+"');")}
-      <div class="field" style="border:none;color:#888">
-      <ul>
-          <li>${_('Manually invalidate cache for this repository. On first access, the repository will be cached again.')}
-          </li>
-      </ul>
+   <div>
+       ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate Repository Cache'),class_="btn btn-default btn-sm")}
+      <div class="text-muted">
+        ${_('Manually invalidate cache for this repository. On first access, the repository will be cached again.')}
       </div>
-      <div class="field" style="border:none;">
-        ${_('List of Cached Values')}
-           <table>
-           <tr>
+      <div>
+        <h5>${_('List of Cached Values')}</h5>
+        <table class="table">
+          <tr>
             <th>${_('Prefix')}</th>
             <th>${_('Key')}</th>
             <th>${_('Active')}</th>
-            </tr>
+          </tr>
           %for cache in c.repo_info.cache_keys:
               <tr>
                 <td>${cache.get_prefix() or '-'}</td>
@@ -23,7 +20,7 @@
                 <td>${h.boolicon(cache.cache_active)}</td>
               </tr>
           %endfor
-          </table>
+        </table>
       </div>
    </div>
 </div>
--- a/kallithea/templates/admin/repos/repo_edit_fields.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_fields.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,22 +1,23 @@
 %if c.visual.repository_fields:
     %if c.repo_fields:
     <div class="emails_wrap">
-      <table class="noborder">
-        <th>${_('Label')}</th>
-        <th>${_('Key')}</th>
-        <th>${_('Type')}</th>
-        <th>${_('Action')}</th>
-
+      <table class="table">
+        <tr>
+          <th>${_('Label')}</th>
+          <th>${_('Key')}</th>
+          <th>${_('Type')}</th>
+          <th>${_('Action')}</th>
+        </tr>
       %for field in c.repo_fields:
         <tr>
             <td>${field.field_label}</td>
             <td>${field.field_key}</td>
             <td>${field.field_type}</td>
             <td>
-              ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
-                  <i class="icon-minus-circled" style="color:#FF4444"></i>
+              ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id))}
+                  <i class="icon-trashcan"></i>
                   ${h.submit('remove_%s' % field.repo_field_id, _('Delete'), id="remove_field_%s" % field.repo_field_id,
-                  class_="action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
+                  class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
               ${h.end_form()}
             </td>
         </tr>
@@ -24,45 +25,39 @@
       </table>
     </div>
     %endif
-    ${h.form(url('create_repo_fields', repo_name=c.repo_name),method='put')}
+    ${h.form(url('create_repo_fields', repo_name=c.repo_name))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_key">${_('New field key')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_field_key">${_('New field key')}:</label>
+                <div>
+                    ${h.text('new_field_key', class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('new_field_key', class_='small')}
-                </div>
-             </div>
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_label">${_('New field label')}:</label>
+            </div>
+
+            <div class="form-group">
+                <label class="control-label" for="new_field_label">${_('New field label')}:</label>
+                <div>
+                    ${h.text('new_field_label', class_='form-control', placeholder=_('Enter short label'))}
                 </div>
-                <div class="input">
-                    ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="new_field_desc">${_('New field description')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_field_desc">${_('New field description')}:</label>
+                <div>
+                    ${h.text('new_field_desc', class_='form-control', placeholder=_('Enter description of a field'))}
                 </div>
-                <div class="input">
-                    ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
-                </div>
-             </div>
+            </div>
 
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 %else:
-  <div style="font-size: 20px">
+  <h4>
     ${_('Extra fields are disabled.')}
-  </div>
+  </h4>
 %endif
--- a/kallithea/templates/admin/repos/repo_edit_fork.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
-<div class="form">
-   <div class="fields">
-       ${h.select('id_fork_of','',c.repos_list,class_="medium")}
-       ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small")}
-   </div>
-       <div class="field" style="border:none;color:#888">
-       <ul>
-            <li>${_('''Manually set this repository as a fork of another from the list.''')}</li>
-       </ul>
-       </div>
-</div>
-${h.end_form()}
-
-<script>
-    $(document).ready(function(){
-        $("#id_fork_of").select2({
-            'dropdownAutoWidth': true
-        });
-    });
-</script>
--- a/kallithea/templates/admin/repos/repo_edit_permissions.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_permissions.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,8 @@
-${h.form(url('edit_repo_perms_update', repo_name=c.repo_name), method='put')}
+${h.form(url('edit_repo_perms_update', repo_name=c.repo_name))}
 <div class="form">
-   <div class="fields">
-        <div class="field">
+        <div class="form-group">
             ${h.hidden('repo_private')}
-            <table id="permissions_manage" class="noborder">
+            <table id="permissions_manage" class="table table-condensed">
                 <tr>
                     <td>${_('None')}</td>
                     <td>${_('Read')}</td>
@@ -21,7 +20,7 @@
                                 ${_('Private Repository')}
                                 </span>
                             </td>
-                            <td class="private_repo_msg"><i class="icon-user"></i> ${_('Default')}</td>
+                            <td class="private_repo_msg"><i class="icon-user"></i>${_('Default')}</td>
                         </tr>
                     %else:
                     <tr id="id${id(r2p.user.username)}">
@@ -29,7 +28,7 @@
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             ${h.gravatar(r2p.user.email, size=14)}
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
@@ -39,9 +38,9 @@
                         </td>
                         <td>
                           %if r2p.user.username !='default':
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                            <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                            <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                           %endif
                         </td>
                     </tr>
@@ -55,7 +54,7 @@
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             <i class="icon-users"></i>
                             %if h.HasPermissionAny('hg.admin')():
                              <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
@@ -64,50 +63,33 @@
                             %endif
                         </td>
                         <td>
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}', '${g2p.users_group.users_group_name}')">
+                            <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                         </td>
                     </tr>
                 %endfor
-
-                <%
-                _tmpl = h.literal("""' \
-                    <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="repository.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td class="ac"> \
-                        <div class="perm_ac" id="perm_ac_{0}"> \
-                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                            <div id="perm_container_{0}"></div> \
-                        </div> \
-                    </td> \
-                    <td></td>'""")
-                %>
-                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                ## New entries added by addPermAction here.
+                <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
                 <tr>
                     <td colspan="6">
-                        <span id="add_perm" style="cursor: pointer;">
-                            <i class="icon-plus"></i> ${_('Add new')}
-                        </span>
+                        <button type="button" id="add_perm" class="btn btn-link btn-xs">
+                            <i class="icon-plus"></i>${_('Add new')}
+                        </button>
                     </td>
                 </tr>
             </table>
         </div>
-        <div class="buttons">
-          ${h.submit('save',_('Save'),class_="btn")}
-          ${h.reset('reset',_('Reset'),class_="btn")}
+        <div class="form-group">
+            ${h.submit('save',_('Save'),class_="btn btn-default")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-default")}
         </div>
-   </div>
 </div>
 ${h.end_form()}
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_repo_perms_revoke',repo_name=c.repo_name)}";
+        url = ${h.js(h.url('edit_repo_perms_revoke',repo_name=c.repo_name))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
@@ -119,7 +101,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction('repository');
         });
     });
 </script>
--- a/kallithea/templates/admin/repos/repo_edit_remote.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_remote.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,25 @@
-%if c.repo_info.clone_uri:
-<div style="font-size: 20px; padding: 0px 0px 10px 0px">
-   ${_('Remote repository URL')}: <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a></li>
-</div>
-${h.form(url('edit_repo_remote', repo_name=c.repo_name), method='put')}
+%if c.repo_info.clone_uri or c.repo_info.fork:
+<h4>
+  %if c.repo_info.fork:
+    ${_('Fork of repository')}:
+    <a href="${h.url('summary_home',repo_name=c.repo_info.fork.repo_name)}">${c.repo_info.fork.repo_name}</a>
+  %else:
+    ${_('Remote repository URL')}:
+    <a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri_hidden}</a>
+  %endif
+</h4>
+${h.form(url('edit_repo_remote_update', repo_name=c.repo_name))}
 <div class="form">
-    <div class="fields">
+    <div>
         ${h.submit('remote_pull_%s' % c.repo_info.repo_name,
             _('Pull Changes from Remote Repository'),
-            class_="btn btn-small",
+            class_="btn btn-default btn-sm",
             onclick="return confirm('"+_('Confirm to pull changes from remote repository.')+"');")}
     </div>
 </div>
 ${h.end_form()}
 %else:
-  <div style="font-size: 20px">
+  <h3>
     ${_('This repository does not have a remote repository URL.')}
-  </div>
+  </h3>
 %endif
--- a/kallithea/templates/admin/repos/repo_edit_settings.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_settings.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,28 +1,22 @@
-${h.form(url('put_repo', repo_name=c.repo_info.repo_name), method='put')}
+${h.form(url('update_repo', repo_name=c.repo_info.repo_name))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="repo_name">${_('Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('repo_name',class_="medium")}
+            <div class="form-group">
+                <label class="control-label" for="repo_name">${_('Name')}:</label>
+                <div>
+                    ${h.text('repo_name',class_='form-control')}
                     <span class="help-block">${_('Permanent Repository ID')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('What is that?')}</a></span></span>
                     <span id="clone_id" class="help-block" style="display: none">
-                        ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
+                        ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}`<br/>
                         ${_('''In case this repository is renamed or moved into another group the repository URL changes.
                                Using the above permanent URL guarantees that this repository always will be accessible on that URL.
                                This is useful for CI systems, or any other cases that you need to hardcode the URL into a 3rd party service.''')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="clone_uri">${_('Remote repository')}:</label>
-                </div>
-                <div class="input">
+            <div class="form-group">
+                <label class="control-label" for="clone_uri">${_('Remote repository')}:</label>
+                <div>
                   <div id="alter_clone_uri">
-                        ${h.text('clone_uri',class_="medium", placeholder=_('Repository URL'))}
+                        ${h.text('clone_uri',class_='form-control', placeholder=_('Repository URL'))}
                         ${h.hidden('clone_uri_hidden', c.repo_info.clone_uri_hidden)}
                   </div>
                   <span id="alter_clone_uri_help_block" class="help-block">
@@ -30,78 +24,59 @@
                   </span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="repo_group">${_('Repository group')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('repo_group','',c.repo_groups,class_="medium")}
+            <div class="form-group">
+                <label class="control-label" for="repo_group">${_('Repository group')}:</label>
+                <div>
+                    ${h.select('repo_group','',c.repo_groups,class_='form-control')}
                     <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="repo_landing_rev">${_('Landing revision')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
+            <div class="form-group">
+                <label class="control-label" for="repo_landing_rev">${_('Landing revision')}:</label>
+                <div>
+                    ${h.select('repo_landing_rev','',c.landing_revs,class_='form-control')}
                     <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label">
-                    <label for="user">${_('Owner')}:</label>
-                </div>
-                <div class="input input-medium ac">
-                    <div class="perm_ac">
-                       ${h.text('user',class_='yui-ac-input')}
-                       <span class="help-block">${_('Change owner of this repository.')}</span>
-                       <div id="owner_container"></div>
-                    </div>
+            <div class="form-group">
+                <label class="control-label" for="owner">${_('Owner')}:</label>
+                <div>
+                   ${h.text('owner',class_='form-control', placeholder=_('Type name of user'))}
+                   <span class="help-block">${_('Change owner of this repository.')}</span>
                 </div>
              </div>
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="repo_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('repo_description', style="height:165px")}
+            <div class="form-group">
+                <label class="control-label" for="repo_description">${_('Description')}:</label>
+                <div>
+                    ${h.textarea('repo_description',class_='form-control')}
                     <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_private">${_('Private repository')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="repo_private">${_('Private repository')}:</label>
+                <div>
                     ${h.checkbox('repo_private',value="True")}
                     <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="repo_enable_statistics">${_('Enable statistics')}:</label>
+                <div>
                     ${h.checkbox('repo_enable_statistics',value="True")}
                     <span class="help-block">${_('Enable statistics window on summary page.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="repo_enable_downloads">${_('Enable downloads')}:</label>
+                <div>
                     ${h.checkbox('repo_enable_downloads',value="True")}
                     <span class="help-block">${_('Enable download menu on summary page.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="repo_enable_locking">${_('Enable locking')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="repo_enable_locking">${_('Enable locking')}:</label>
+                <div>
                     ${h.checkbox('repo_enable_locking',value="True")}
                     <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
                 </div>
@@ -110,12 +85,10 @@
             %if c.visual.repository_fields:
               ## EXTRA FIELDS
               %for field in c.repo_fields:
-                <div class="field">
-                    <div class="label">
-                        <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
-                    </div>
-                    <div class="input input-medium">
-                        ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
+                <div class="form-group">
+                    <label class="control-label" for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
+                    <div>
+                        ${h.text(field.field_key_prefixed, field.field_value, class_='form-control')}
                         %if field.field_desc:
                           <span class="help-block">${field.field_desc}</span>
                         %endif
@@ -123,11 +96,12 @@
                  </div>
               %endfor
             %endif
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 
@@ -146,7 +120,6 @@
         });
 
         // autocomplete
-        var _USERS_AC_DATA = ${c.users_array|n};
-        SimpleUserAutoComplete($('#user'), $('#owner_container'), _USERS_AC_DATA);
+        SimpleUserAutoComplete($('#owner'));
     });
 </script>
--- a/kallithea/templates/admin/repos/repo_edit_statistics.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repo_edit_statistics.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,13 @@
-${h.form(url('edit_repo_statistics', repo_name=c.repo_info.repo_name), method='put')}
+${h.form(url('edit_repo_statistics_update', repo_name=c.repo_info.repo_name))}
 <div class="form">
-    <div class="fields">
-       <div class="field" style="border:none;color:#888">
-        <ul>
+      <div class="form-group text-muted">
+        <ul class="list-unstyled">
             <li>${_('Processed commits')}: ${c.stats_revision}/${c.repo_last_rev}</li>
             <li>${_('Processed progress')}: ${c.stats_percentage}%</li>
         </ul>
-       </div>
-        ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset Statistics'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to remove current statistics.')+"');")}
-   </div>
+      </div>
+      <div class="form-group">
+        ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset Statistics'),class_="btn btn-default btn-sm",onclick="return confirm('"+_('Confirm to remove current statistics.')+"');")}
+      </div>
 </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/repos/repos.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/repos/repos.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,54 +6,47 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="repo_count">0</span> ${_('Repositories')}
+    <span id="repo_count">0</span> ${_('Repositories')}
 </%def>
 <%block name="header_menu">
     ${self.menu('admin')}
 </%block>
 <%def name="main()">
-<div class="box">
+<div class="panel panel-primary">
 
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
          %if h.HasPermissionAny('hg.admin','hg.create.repository')():
-          <li>
-            <a href="${h.url('new_repo')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add Repository')}</a>
-          </li>
+            <a href="${h.url('new_repo')}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Add Repository')}</a>
          %endif
-        </ul>
+        </div>
     </div>
-    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
-    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
-
+    <div class="panel-body">
+        <table class="table" id="datatable_list_wrap" width="100%"></table>
+    </div>
 
 </div>
 <script>
-  var data = ${c.data|n};
-  var fields = [
-    {key:"menu"},
-    {key:"raw_name"},
-    {key:"name"},
-    {key:"desc"},
-    {key:"last_changeset"},
-    {key:"last_rev_raw"},
-    {key:"owner"},
-    {key:"state"},
-    {key:"action"}
-  ];
-  var column_defs = [
-    {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-    {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
-    {key:"desc",label:"${_('Description')}",sortable:true},
-    {key:"last_changeset",label:"${_('Tip')}",sortable:true, sortOptions: { sortFunction: revisionSort }},
-    {key:"owner",label:"${_('Owner')}",sortable:true},
-    {key:"state",label:"${_('State')}",sortable:true},
-    {key:"action",label:"${_('Action')}",sortable:false}
-  ];
-  var counter = YUD.get('repo_count');
-  var sort_key = "name";
-  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+  var data = ${h.js(c.data)};
+  var myDataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "raw_name", visible: false, searchable: false},
+            {data: "name", orderData: 1, title: ${h.jshtml(_('Name'))}},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+            {data: "last_rev_raw", visible: false, searchable: false},
+            {data: "last_changeset", orderData: 4, title: ${h.jshtml(_('Tip'))}, searchable: false},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "state", title: ${h.jshtml(_('State'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, sortable: false, searchable: false}
+        ],
+        drawCallback: updateRowCountCallback($("#repo_count")),
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
 </script>
 
 </%def>
--- a/kallithea/templates/admin/settings/settings.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings.html	Sun Mar 31 21:28:56 2019 +0200
@@ -16,13 +16,13 @@
 </%block>
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="panel-body settings">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='vcs' else ''}"><a href="${h.url('admin_settings')}">${_('VCS')}</a></li>
         <li class="${'active' if c.active=='mapping' else ''}"><a href="${h.url('admin_settings_mapping')}">${_('Remap and Rescan')}</a></li>
@@ -33,10 +33,10 @@
         <li class="${'active' if c.active=='search' else ''}"><a href="${h.url('admin_settings_search')}">${_('Full Text Search')}</a></li>
         <li class="${'active' if c.active=='system' else ''}"><a href="${h.url('admin_settings_system')}">${_('System Info')}</a></li>
       </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
-        <%include file="/admin/settings/settings_${c.active}.html"/>
+      <div>
+          <%include file="/admin/settings/settings_${c.active}.html"/>
+      </div>
     </div>
 </div>
 
--- a/kallithea/templates/admin/settings/settings_email.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_email.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,16 @@
 ${h.form(url('admin_settings_email'), method='post')}
     <div class="form">
-
-        <div class="fields">
-            <div class="field">
-                <div class="label">
-                    <label for="test_email">${_('Send test email to')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('test_email',size=30)}
+            <div class="form-group">
+                <label class="control-label" for="test_email">${_('Send test email to')}:</label>
+                <div>
+                    ${h.text('test_email',size=30,class_='form-control')}
                 </div>
             </div>
 
-            <div class="buttons">
-            ${h.submit('send',_('Send'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('send',_('Send'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/settings/settings_global.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_global.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,61 +1,54 @@
 ${h.form(url('admin_settings_global'), method='post')}
     <div class="form">
-
-        <div class="fields">
-
-             <div class="field">
-                <div class="label">
-                    <label for="title">${_('Site branding')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('title',size=30)}
+             <div class="form-group">
+                <label class="control-label" for="title">${_('Site branding')}:</label>
+                <div>
+                    ${h.text('title',size=30,class_='form-control')}
                     <span class="help-block">${_('Set a custom title for your Kallithea Service.')}</span>
                 </div>
              </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="realm">${_('HTTP authentication realm')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('realm',size=30)}
+            <div class="form-group">
+                <label class="control-label" for="realm">${_('HTTP authentication realm')}:</label>
+                <div>
+                    ${h.text('realm',size=30,class_='form-control')}
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="ga_code">${_('Analytics HTML block')}:</label>
-                </div>
-                <div class="input">
-                    ${h.textarea('ga_code', cols=80, rows=10)}
-                    <span class="help-block">${_('HTML with JavaScript for web analytics systems like Google Analytics or Piwik. This will be added at the bottom of every page.')}</span>
+            <div class="form-group">
+                <label class="control-label" for="ga_code">${_('HTML/JavaScript/CSS customization block')}:</label>
+                <div>
+                    ${h.textarea('ga_code', cols=80, rows=10,class_='form-control')}
+                    <span class="help-block">${_('HTML (possibly with \
+                        JavaScript and/or CSS) that will be added to the bottom \
+                        of every page. This can be used for web analytics \
+                        systems, but also to \
+                        perform instance-specific customizations like adding a \
+                        project banner at the top of every page.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="captcha_public_key">${_('ReCaptcha public key')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('captcha_public_key',size=60)}
+            <div class="form-group">
+                <label class="control-label" for="captcha_public_key">${_('ReCaptcha public key')}:</label>
+                <div>
+                    ${h.text('captcha_public_key',size=60,class_='form-control')}
                     <span class="help-block">${_('Public key for reCaptcha system.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="captcha_private_key">${_('ReCaptcha private key')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('captcha_private_key',size=60)}
+            <div class="form-group">
+                <label class="control-label" for="captcha_private_key">${_('ReCaptcha private key')}:</label>
+                <div>
+                    ${h.text('captcha_private_key',size=60,class_='form-control')}
                     <span class="help-block">${_('Private key for reCaptcha system. Setting this value will enable captcha on registration.')}</span>
                 </div>
             </div>
 
-            <div class="buttons">
-                ${h.submit('save',_('Save Settings'),class_="btn")}
-                ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save Settings'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
            </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/settings/settings_hooks.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_hooks.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,72 +1,65 @@
-<h4>${_('Built-in Mercurial Hooks (Read-Only)')}</h4>
 <div class="form">
-    <div class="fields">
+      <div class="form-group">
+      <h4>${_('Built-in Mercurial Hooks (Read-Only)')}</h4>
       % for hook in c.hooks:
-        <div class="field">
-            <div class="label label">
-                <label for="${hook.ui_key}">${hook.ui_key}</label>
+        <% input_id = hook.ui_key.replace('.', '_') %>
+            <label class="control-label" for="${input_id}" title="${hook.ui_key}">${hook.ui_key}</label>
+            <div>
+              ${h.text(hook.ui_key,hook.ui_value,id=input_id,size=60,readonly="readonly",class_='form-control')}
             </div>
-            <div class="input" style="margin-left:280px">
-              ${h.text(hook.ui_key,hook.ui_value,size=60,readonly="readonly")}
-            </div>
-        </div>
       % endfor
-    </div>
-    <span class="help-block">${_('Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications.')}</span>
+      </div>
 </div>
 
 % if c.visual.allow_custom_hooks_settings:
-<h4>${_('Custom Hooks')}</h4>
 ${h.form(url('admin_settings_hooks'), method='post')}
 <div class="form">
-    <div class="fields">
+        <h4>${_('Custom Hooks')}</h4>
+        <span class="help-block">${_('Hooks can be used to trigger actions on certain events such as push / pull. They can trigger Python functions or external applications.')}</span>
+        %for hook in c.custom_hooks:
+            <div class="form-group form-inline" id="${'id%s' % hook.ui_id }">
+                <% input_id = hook.ui_key.replace('.', '_') %>
+                    <label class="control-label" for="${input_id}" title="${hook.ui_key}">${hook.ui_key}</label>
+                    <div>
+                        ${h.hidden('hook_ui_key',hook.ui_key,id='hook_ui_key_'+input_id)}
+                        ${h.hidden('hook_ui_value',hook.ui_value,id='hook_ui_value_'+input_id)}
+                        ${h.text('hook_ui_value_new',hook.ui_value,id=input_id,size=50,class_='form-control')}
+                        <button type="button" class="btn btn-default btn-xs"
+                            onclick="delete_hook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
+                            <i class="icon-trashcan"></i>
+                            ${_('Delete')}
+                        </button>
+                    </div>
+            </div>
+        %endfor
 
-      % for hook in c.custom_hooks:
-      <div class="field"  id="${'id%s' % hook.ui_id }">
-        <div class="label label">
-            <label for="${hook.ui_key}">${hook.ui_key}</label>
-        </div>
-        <div class="input" style="margin-left:280px">
-            ${h.hidden('hook_ui_key',hook.ui_key)}
-            ${h.hidden('hook_ui_value',hook.ui_value)}
-            ${h.text('hook_ui_value_new',hook.ui_value,size=60)}
-            <span class="action_button"
-                onclick="delete_hook(${hook.ui_id},'${'id%s' % hook.ui_id }')">
-            <i class="icon-minus-circled" style="color:#FF4444"></i>
-            ${_('Delete')}
-            </span>
+        <div class="form-group form-inline">
+            <label>
+                ${h.text('new_hook_ui_key',size=15,class_='form-control')}
+            </label>
+            <div>
+                ${h.text('new_hook_ui_value',size=50,class_='form-control')}
+            </div>
         </div>
-      </div>
-      % endfor
-
-      <div class="field">
-        <div class="input" style="margin-left:-135px;position: absolute;">
-          <div class="input">
-             ${h.text('new_hook_ui_key',size=20)}
-          </div>
+        <div class="form-group">
+            <div class="buttons">
+                ${h.submit('save',_('Save'),class_="btn btn-default")}
+            </div>
         </div>
-        <div class="input" style="margin-left:280px">
-            ${h.text('new_hook_ui_value',size=60)}
-        </div>
-      </div>
-      <div class="buttons" style="margin-left:280px">
-         ${h.submit('save',_('Save'),class_="btn")}
-      </div>
-    </div>
 </div>
 ${h.end_form()}
 % endif
 
 <script type="text/javascript">
 function delete_hook(hook_id, field_id) {
-    var sUrl = "${h.url('admin_settings_hooks')}";
+    var sUrl = ${h.js(h.url('admin_settings_hooks_delete'))};
     var success = function (o) {
             $('#' + field_id).remove();
         };
     var failure = function (o) {
-            alert("${_('Failed to remove hook')}");
+            alert(${h.js(_('Failed to remove hook'))});
         };
-    var postData = {'_method': 'delete', 'hook_id': hook_id};
+    var postData = {'hook_id': hook_id};
     ajaxPOST(sUrl, postData, success, failure);
 };
 </script>
--- a/kallithea/templates/admin/settings/settings_mapping.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_mapping.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,39 +1,45 @@
 ${h.form(url('admin_settings_mapping'), method='post')}
     <div class="form">
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="destroy">${_('Rescan option')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="destroy">${_('Rescan options')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('destroy',True)}
-                        <label for="destroy">${_('Delete records of missing repositories')}</label>
+                        <label>
+                            ${h.checkbox('destroy',True)}
+                            ${_('Delete records of missing repositories')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Check this option to remove all comments, pull requests and other records related to repositories that no longer exist in the filesystem.')}</span>
 
                     <div class="checkbox">
-                        ${h.checkbox('invalidate',True)}
-                        <label for="invalidate"> ${_('Invalidate cache for all repositories')}</label>
+                        <label>
+                            ${h.checkbox('invalidate',True)}
+                            ${_('Invalidate cache for all repositories')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Check this to reload data and clear cache keys for all repositories.')}</span>
 
                     <div class="checkbox">
-                        ${h.checkbox('hooks',True)}
-                        <label for="hooks"> ${_('Install Git hooks')} </label>
+                        <label>
+                            ${h.checkbox('hooks',True)}
+                            ${_('Install Git hooks')}
+                        </label>
                     </div>
                     <span class="help-block">${_("Verify if Kallithea's Git hooks are installed for each repository. Current hooks will be updated to the latest version.")}</span>
                     <div class="checkbox">
-                        ${h.checkbox('hooks_overwrite', True)}
-                        <label for="hooks_overwrite"> ${_('Overwrite existing Git hooks')}</label>
+                        <label>
+                            ${h.checkbox('hooks_overwrite', True)}
+                            ${_('Overwrite existing Git hooks')}
+                        </label>
                     </div>
                     <span class="help-block">${_("If installing Git hooks, overwrite any existing hooks, even if they do not seem to come from Kallithea. WARNING: This operation will destroy any custom git hooks you may have deployed by hand!")}</span>
                 </div>
             </div>
 
-            <div class="buttons">
-            ${h.submit('rescan',_('Rescan Repositories'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('rescan',_('Rescan Repositories'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/settings/settings_search.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_search.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,25 +1,22 @@
 ${h.form(url('admin_settings_search'), method='post')}
     <div class="form">
-
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Index build option')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label">${_('Index build option')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('full_index',True)}
-                        <label for="full_index">${_('Build from scratch')}</label>
-
+                        <label>
+                            ${h.checkbox('full_index',True)}
+                            ${_('Build from scratch')}
+                        </label>
                     </div>
                     <span class="help-block">${_('This option completely reindexeses all of the repositories for proper fulltext search capabilities.')}</span>
-
                 </div>
             </div>
 
-            <div class="buttons">
-            ${h.submit('reindex',_('Reindex'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('reindex',_('Reindex'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/settings/settings_system.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_system.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,34 +1,31 @@
-<dl class="dl-horizontal">
+<div id="update_notice" style="display: none">
+    <div>${_('Checking for updates...')}</div>
+</div>
+
 <%
  elems = [
-    (_('Kallithea version'), h.literal('%s <b><span style="color:#036185; text-decoration: underline;cursor: pointer" id="check_for_update" >%s</span></b>' % (c.kallithea_version, _('Check for updates'))), ''),
+    (_('Kallithea version'), h.literal('%s <b><span id="check_for_update" style="display:none">%s</span></b>' % (c.kallithea_version, _('Check for updates'))), ''),
     (_('Kallithea configuration file'), c.ini['__file__'], ''),
     (_('Python version'), c.py_version, ''),
     (_('Platform'), c.platform, ''),
     (_('Git version'), c.git_version, ''),
     (_('Git path'), c.ini.get('git_path'), ''),
-    (_('Upgrade info endpoint'), h.literal('%s <br/><span style="color:#999999">%s.</span>' % (c.update_url, _('Note: please make sure this server can access this URL'))), ''),
+    (_('Upgrade info endpoint'), h.literal('%s <br/><span class="text-muted">%s.</span>' % (c.update_url, _('Note: please make sure this server can access this URL'))), ''),
  ]
 %>
-
-<div id="update_notice" style="display: none">
-    <div style="padding: 5px 0px 5px 0px; color: #000000; font-weight: bold">${_('Checking for updates...')}</div>
-</div>
+<dl class="dl-horizontal">
 %for dt, dd, tt in elems:
-  <dt style="width:150px; text-align: left">${dt}:</dt>
-  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+  <dt title="${dt}">${dt}:</dt>
+  <dd title="${tt}">${dd}</dd>
 %endfor
 </dl>
 
 <h4>${_('Python Packages')}</h4>
-<table class="table" style="margin:0px 0px 0px 0px">
-  <colgroup>
-      <col style="width:180px">
-  </colgroup>
+<table class="table">
   <tbody>
       %for key, value in c.modules:
           <tr>
-              <td style="padding-right:5px;">${key}</td>
+              <td>${key}</td>
               <td>${value}</td>
           </tr>
       %endfor
@@ -39,6 +36,6 @@
     $('#check_for_update').click(function(e){
         var $update_notice = $('#update_notice');
         $update_notice.show();
-        asynchtml("${h.url('admin_settings_system_update')}", $update_notice);
+        asynchtml(${h.js(h.url('admin_settings_system_update'))}, $update_notice);
     });
 </script>
--- a/kallithea/templates/admin/settings/settings_system_update.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_system_update.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,8 +2,7 @@
 ## upgrade block rendered afte on-click check
 
 <div class="alert ${'alert-warning' if c.should_upgrade else 'alert-success'}">
-<p style="padding: 2px 0px 5px 0px; margin: 0px">
-
+<p>
 %if c.should_upgrade:
     A <b>new version</b> is available:
     %if c.latest_data.get('title'):
@@ -17,11 +16,13 @@
 </p>
 
 % if c.should_upgrade and c.important_notices:
-<div style="color: #5f5f5f; padding: 3px 0px 5px 0px;">Important notes for this release:</div>
+<div class="alert alert-warning">
+    Important notes for this release:
     <ul>
     % for notice in c.important_notices:
         <li>- ${notice}</li>
     % endfor
     </ul>
+</div>
 % endif
 </div>
--- a/kallithea/templates/admin/settings/settings_vcs.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_vcs.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,74 +1,70 @@
 ${h.form(url('admin_settings'), method='post')}
     <div class="form">
-        <div class="fields">
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Web')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label">${_('Hooks')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('web_push_ssl', 'True')}
-                        <label for="web_push_ssl">${_('Require SSL for vcs operations')}</label>
+                        <label>
+                            ${h.checkbox('hooks_changegroup_repo_size','True')}
+                            ${_('Show repository size after push')}
+                        </label>
                     </div>
-                    <span class="help-block">${_('Activate to require SSL both pushing and pulling. If SSL certificate is missing, it will return an HTTP Error 406: Not Acceptable.')}</span>
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Hooks')}:</label>
-                </div>
-                <div class="checkboxes">
                     <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_repo_size','True')}
-                        <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
+                        <label>
+                            ${h.checkbox('hooks_changegroup_push_logger','True')}
+                            ${_('Log user push commands')}
+                        </label>
                     </div>
                     <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_push_logger','True')}
-                        <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
+                        <label>
+                            ${h.checkbox('hooks_outgoing_pull_logger','True')}
+                            ${_('Log user pull commands')}
+                        </label>
                     </div>
                     <div class="checkbox">
-                        ${h.checkbox('hooks_outgoing_pull_logger','True')}
-                        <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
-                    </div>
-                    <div class="checkbox">
-                        ${h.checkbox('hooks_changegroup_update','True')}
-                        <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
+                        <label>
+                            ${h.checkbox('hooks_changegroup_update','True')}
+                            ${_('Update repository after push (hg update)')}
+                        </label>
                     </div>
                 </div>
-             </div>
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Mercurial extensions')}:</label>
-                </div>
-                <div class="checkboxes">
+            </div>
+            <div class="form-group">
+                <label class="control-label">${_('Mercurial extensions')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('extensions_largefiles','True')}
-                        <label for="extensions_largefiles">${_('Enable largefiles extension')}</label>
+                        <label>
+                            ${h.checkbox('extensions_largefiles','True')}
+                            ${_('Enable largefiles extension')}
+                        </label>
                     </div>
                     <div class="checkbox">
-                        ${h.checkbox('extensions_hgsubversion','True')}
-                        <label for="extensions_hgsubversion">${_('Enable hgsubversion extension')}</label>
+                        <label>
+                            ${h.checkbox('extensions_hgsubversion','True')}
+                            ${_('Enable hgsubversion extension')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Requires hgsubversion library to be installed. Enables cloning of remote Subversion repositories while converting them to Mercurial.')}</span>
                     ##<div class="checkbox">
-                    ##    ${h.checkbox('extensions_hggit','True')}
-                    ##    <label for="extensions_hggit">${_('Enable hg-git extension')}</label>
+                    ##    <label>
+                    ##        ${h.checkbox('extensions_hggit','True')}
+                    ##        ${_('Enable hg-git extension')}
+                    ##    </label>
                     ##</div>
                     ##<span class="help-block">${_('Requires hg-git library to be installed. Enables cloning of remote Git repositories while converting them to Mercurial.')}</span>
                 </div>
             </div>
             %if c.visual.allow_repo_location_change:
-            <div class="field">
-                <div class="label">
-                    <label for="paths_root_path">${_('Location of repositories')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('paths_root_path',size=60,readonly="readonly")}
-                    <span id="path_unlock" class="tooltip" style="cursor: pointer"
+            <div class="form-group">
+                <label class="control-label" for="paths_root_path">${_('Location of repositories')}:</label>
+                <div>
+                    <div class="input-group">
+                        ${h.text('paths_root_path',size=60,readonly="readonly",class_='form-control')}
+                        <span id="path_unlock" data-toggle="tooltip" class="input-group-btn"
                             title="${_('Click to unlock. You must restart Kallithea in order to make this setting take effect.')}">
-                        <div class="btn btn-small"><i id="path_unlock_icon" class="icon-lock"></i></div>
-                    </span>
+                            <button type="button" class="btn btn-default btn-sm"><i id="path_unlock_icon" class="icon-lock"></i></button>
+                        </span>
+                    </div>
                     <span class="help-block">${_('Filesystem location where repositories are stored. After changing this value, a restart and rescan of the repository folder are both required.')}</span>
                 </div>
             </div>
@@ -76,11 +72,12 @@
             ## form still requires this but we cannot internally change it anyway
             ${h.hidden('paths_root_path',size=30,readonly="readonly")}
             %endif
-            <div class="buttons">
-                ${h.submit('save',_('Save Settings'),class_="btn")}
-                ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save Settings'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
            </div>
-        </div>
     </div>
     ${h.end_form()}
 
--- a/kallithea/templates/admin/settings/settings_visual.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/settings/settings_visual.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,122 +1,120 @@
 ${h.form(url('admin_settings_visual'), method='post')}
     <div class="form">
-
-        <div class="fields">
-
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('General')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label">${_('General')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('repository_fields','True')}
-                        <label for="repository_fields">${_('Use repository extra fields')}</label>
+                        <label>
+                            ${h.checkbox('repository_fields','True')}
+                            ${_('Use repository extra fields')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
+
                     <div class="checkbox">
-                        ${h.checkbox('show_version','True')}
-                        <label for="show_version">${_('Show Kallithea version')}</label>
+                        <label>
+                            ${h.checkbox('show_version','True')}
+                            ${_('Show Kallithea version')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Shows or hides a version number of Kallithea displayed in the footer.')}</span>
 
                     <div class="checkbox">
-                        ${h.checkbox('use_gravatar','True')}
-                        <label for="use_gravatar">${_('Use Gravatars in Kallithea')}</label>
+                        <label>
+                            ${h.checkbox('use_gravatar','True')}
+                            ${_('Show user Gravatars')}
+                        </label>
                     </div>
-                </div>
-                <div class="field">
-                    <div class="input">
-                        ${h.text('gravatar_url', size=80)}
-                        <span class="help-block">${_('''Gravatar URL allows you to use another avatar server application.
+                    ${h.text('gravatar_url', size=80, class_='form-control')}
+                    <span class="help-block">${_('''Gravatar URL allows you to use another avatar server application.
                                                         The following variables of the URL will be replaced accordingly.
                                                         {scheme}    'http' or 'https' sent from running Kallithea server,
                                                         {email}     user email,
                                                         {md5email}  md5 hash of the user email (like at gravatar.com),
                                                         {size}      size of the image that is expected from the server application,
                                                         {netloc}    network location/server host of running Kallithea server''')}</span>
-                    </div>
-                </div>
-                <div class="field">
-                    <div class="input">
-                        ${h.text('clone_uri_tmpl', size=80)}
-                        <span class="help-block">${_('''Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.
-                                                        The following variables are available:
-                                                        {scheme} 'http' or 'https' sent from running Kallithea server,
-                                                        {user}   current user username,
-                                                        {netloc} network location/server host of running Kallithea server,
-                                                        {repo}   full repository name,
-                                                        {repoid} ID of repository, can be used to contruct clone-by-id''')}</span>
-                    </div>
-                </div>
-             </div>
-
-            <div class="field">
-                <div class="label">
-                    <label for="dashboard_items">${_('Dashboard items')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('dashboard_items',size=5)}
-                    <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="admin_grid_items">${_('Admin pages items')}:</label>
+            <div class="form-group">
+                <label class="control-label">${_('Clone URL')}:</label>
+                <div>
+                    ${h.text('clone_uri_tmpl', size=80, class_='form-control')}
+                    <span class="help-block">${_('''Schema of clone URL construction eg. '{scheme}://{user}@{netloc}/{repo}'.
+                                                    The following variables are available:
+                                                    {scheme} 'http' or 'https' sent from running Kallithea server,
+                                                    {user}   current user username,
+                                                    {netloc} network location/server host of running Kallithea server,
+                                                    {repo}   full repository name,
+                                                    {repoid} ID of repository, can be used to construct clone-by-id''')}</span>
                 </div>
-                <div class="input">
-                    ${h.text('admin_grid_items',size=5)}
+            </div>
+
+            <div class="form-group">
+                <label class="control-label" for="dashboard_items">${_('Repository page size')}:</label>
+                <div>
+                    ${h.text('dashboard_items',size=5,class_='form-control')}
+                    <span class="help-block">${_('Number of items displayed in the repository pages before pagination is shown.')}</span>
+                </div>
+            </div>
+
+            <div class="form-group">
+                <label class="control-label" for="admin_grid_items">${_('Admin page size')}:</label>
+                <div>
+                    ${h.text('admin_grid_items',size=5,class_='form-control')}
                     <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
                 </div>
             </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Icons')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label">${_('Icons')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('show_public_icon','True')}
-                        <label for="show_public_icon">${_('Show public repository icon on repositories')}</label>
+                        <label>
+                            ${h.checkbox('show_public_icon','True')}
+                            ${_('Show public repository icon on repositories')}
+                        </label>
                     </div>
                     <div class="checkbox">
-                        ${h.checkbox('show_private_icon','True')}
-                        <label for="show_private_icon">${_('Show private repository icon on repositories')}</label>
+                        <label>
+                            ${h.checkbox('show_private_icon','True')}
+                            ${_('Show private repository icon on repositories')}
+                        </label>
                     </div>
                     <span class="help-block">${_('Show public/private icons next to repository names.')}</span>
                  </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label>${_('Meta Tagging')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="stylify_metalabels">${_('Meta Tagging')}:</label>
+                <div>
                     <div class="checkbox">
-                        ${h.checkbox('stylify_metatags','True')}
-                        <label for="stylify_metatags">${_('Stylify recognised meta tags:')}</label>
+                        <label>
+                            ${h.checkbox('stylify_metalabels','True')}
+                            ${_('Parses meta tags from the repository description field and turns them into colored tags.')}
+                        </label>
                     </div>
-                    <div style="padding-left: 20px;">
-                        <ul> <!-- Fix style here -->
-                            <li>[featured] <span class="metatag" tag="featured">featured</span></li>
-                            <li>[stale] <span class="metatag" tag="stale">stale</span></li>
-                            <li>[dead] <span class="metatag" tag="dead">dead</span></li>
-                            <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
-                            <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
-                            <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
-                            <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
-                            <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
+                    <div class="help-block">
+                        ${_('Stylify recognised meta tags:')}
+                        <ul class="list-unstyled"> <!-- Fix style here -->
+                            <li>[featured] <span class="label label-meta" data-tag="featured">featured</span></li>
+                            <li>[stale] <span class="label label-meta" data-tag="stale">stale</span></li>
+                            <li>[dead] <span class="label label-meta" data-tag="dead">dead</span></li>
+                            <li>[lang =&gt; lang] <span class="label label-meta" data-tag="lang">lang</span></li>
+                            <li>[license =&gt; License] <span class="label label-meta" data-tag="license"><a href="http://www.opensource.org/licenses/License">License</a></span></li>
+                            <li>[requires =&gt; Repo] <span class="label label-meta" data-tag="requires">requires =&gt; <a href="#">Repo</a></span></li>
+                            <li>[recommends =&gt; Repo] <span class="label label-meta" data-tag="recommends">recommends =&gt; <a href="#">Repo</a></span></li>
+                            <li>[see =&gt; URI] <span class="label label-meta" data-tag="see">see =&gt; <a href="#">URI</a> </span></li>
                         </ul>
                     </div>
-                    <span class="help-block">${_('Parses meta tags from the repository description field and turns them into colored tags.')}</span>
                  </div>
-             </div>
+            </div>
 
-             <div class="buttons">
-                 ${h.submit('save',_('Save Settings'),class_="btn")}
-                 ${h.reset('reset',_('Reset'),class_="btn")}
-             </div>
-
-        </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save Settings'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
+            </div>
     </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/user_groups/user_group_add.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_add.html	Sun Mar 31 21:28:56 2019 +0200
@@ -17,46 +17,37 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
     ${h.form(url('users_groups'))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="users_group_name">${_('Group name')}:</label>
+    <div class="form panel-body settings">
+            <div class="form-group">
+                <label class="control-label" for="users_group_name">${_('Group name')}:</label>
+                <div>
+                    ${h.text('users_group_name',class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('users_group_name',class_='small')}
-                </div>
-             </div>
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="user_group_description">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('user_group_description')}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="user_group_description">${_('Description')}:</label>
+                <div>
+                    ${h.textarea('user_group_description',class_='form-control')}
                     <span class="help-block">${_('Short, optional description for this user group.')}</span>
                 </div>
-             </div>
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="users_group_active">${_('Active')}:</label>
-                </div>
-                <div class="checkboxes">
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="users_group_active">${_('Active')}:</label>
+                <div>
                     ${h.checkbox('users_group_active',value=True, checked='checked')}
                 </div>
-             </div>
+            </div>
 
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/user_groups/user_group_edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -18,13 +18,13 @@
 </%block>
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="form panel-body settings">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
         <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
@@ -32,10 +32,10 @@
         <li class="${'active' if c.active=='default_perms' else ''}"><a href="${h.url('edit_user_group_default_perms', id=c.user_group.users_group_id)}">${_('Show Permissions')}</a></li>
         <li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', id=c.user_group.users_group_id)}">${_('Show Members')}</a></li>
       </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
+      <div>
         <%include file="/admin/user_groups/user_group_edit_${c.active}.html"/>
+      </div>
     </div>
 </div>
 </%def>
--- a/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_advanced.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,23 +1,23 @@
-<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('User Group: %s') % c.user_group.users_group_name}</div>
+<h3>${_('User Group: %s') % c.user_group.users_group_name}</h3>
 
 <dl class="dl-horizontal">
 <%
  elems = [
     (_('Members'), len(c.group_members_obj), ''),
     (_('Created on'), h.fmt_date(c.user_group.created_on), ''),
-    (_('Owner'), h.person(c.user_group.user), ''),
+    (_('Owner'), h.person(c.user_group.owner), ''),
     ]
 %>
 %for dt, dd, tt in elems:
-  <dt style="width:150px; text-align: left">${dt}:</dt>
-  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+  <dt>${dt}:</dt>
+  <dd title="${tt}">${dd}</dd>
 %endfor
 </dl>
 
-${h.form(h.url('users_group', id=c.user_group.users_group_id),method='delete')}
-    <button class="btn btn-small btn-danger" type="submit"
+${h.form(h.url('delete_users_group', id=c.user_group.users_group_id))}
+    <button class="btn btn-danger btn-sm" type="submit"
             onclick="return confirm('${_('Confirm to delete this user group: %s') % c.user_group.users_group_name}');">
-        <i class="icon-minus-circled"></i>
+        <i class="icon-trashcan"></i>
         ${_('Delete this user group')}
     </button>
 ${h.end_form()}
--- a/kallithea/templates/admin/user_groups/user_group_edit_default_perms.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_default_perms.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,5 @@
 <%namespace name="dpb" file="/base/default_perms_box.html"/>
-${dpb.default_perms_box(url('edit_user_group_default_perms', id=c.user_group.users_group_id))}
+${dpb.default_perms_box(url('edit_user_group_default_perms_update', id=c.user_group.users_group_id))}
 
 ## permissions overview
 <%namespace name="p" file="/base/perms_summary.html"/>
--- a/kallithea/templates/admin/user_groups/user_group_edit_members.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_members.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,12 @@
-<div class="group_members_wrap">
 % if c.group_members_obj:
-  <ul class="group_members">
+  <ul class="user_group_member">
   %for user in c.group_members_obj:
     <li>
-      <div class="group_member">
-        <div class="gravatar">
-          ${h.gravatar(user.email, size=24)}
-        </div>
-        <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
-        <div>${user.full_name}</div>
-      </div>
+        ${h.gravatar_div(user.email, size=24)}
+        <div>${h.link_to(user.full_name_and_username, h.url('edit_user',id=user.user_id))}</div>
     </li>
   %endfor
   </ul>
-  %else:
-    <span class="empty_data">${_('No members yet')}</span>
-  %endif
-</div>
+%else:
+    <span class="text-muted">${_('No members yet')}</span>
+%endif
--- a/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_perms.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,8 +1,8 @@
-${h.form(url('edit_user_group_perms', id=c.user_group.users_group_id),method='put')}
+${h.form(url('edit_user_group_perms_update', id=c.user_group.users_group_id))}
 <div class="form">
-   <div class="fields">
-        <div class="field">
-            <table id="permissions_manage" class="noborder">
+   <div>
+        <div>
+            <table id="permissions_manage" class="table">
                 <tr>
                     <td>${_('None')}</td>
                     <td>${_('Read')}</td>
@@ -15,12 +15,12 @@
                 %for r2p in c.user_group.user_user_group_to_perm:
                     ##forbid revoking permission from yourself, except if you're an super admin
                     <tr id="id${id(r2p.user.username)}">
-                      %if c.authuser.user_id != r2p.user.user_id or c.authuser.is_admin:
+                      %if request.authuser.user_id != r2p.user.user_id or request.authuser.is_admin:
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
                             %if h.HasPermissionAny('hg.admin')() and r2p.user.username != 'default':
                              <a href="${h.url('edit_user',id=r2p.user.user_id)}">${r2p.user.username}</a>
@@ -30,9 +30,9 @@
                         </td>
                         <td>
                           %if r2p.user.username !='default':
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
-                             <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button type="button" class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}', '${r2p.user.username}')">
+                             <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                           %endif
                         </td>
                       %else:
@@ -40,11 +40,11 @@
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
                         <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             ${h.gravatar(r2p.user.email, cls="perm-gravatar", size=14)}
                             ${r2p.user.username if r2p.user.username != 'default' else _('Default')}
                         </td>
-                        <td><i class="icon-user"></i> ${_('Admin')}</td>
+                        <td><i class="icon-user"></i>${_('Admin')}</td>
                       %endif
                     </tr>
                 %endfor
@@ -56,7 +56,7 @@
                         <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}</td>
                         <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')}</td>
-                        <td style="white-space: nowrap;">
+                        <td>
                             <i class="icon-users"></i>
                             %if h.HasPermissionAny('hg.admin')():
                              <a href="${h.url('edit_users_group',id=g2p.user_group.users_group_id)}">
@@ -67,42 +67,26 @@
                             %endif
                         </td>
                         <td>
-                            <span style="color:#da4f49" class="action_button" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
-                            <i class="icon-minus-circled"></i> ${_('Revoke')}
-                            </span>
+                            <button class="btn btn-default btn-xs" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}', '${g2p.user_group.users_group_name}')">
+                            <i class="icon-minus-circled"></i>${_('Revoke')}
+                            </button>
                         </td>
                     </tr>
                 %endfor
-
-                <%
-                _tmpl = h.literal("""' \
-                    <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="usergroup.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td><input type="radio" value="usergroup.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
-                    <td class="ac"> \
-                        <div class="perm_ac" id="perm_ac_{0}"> \
-                            <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
-                            <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">  \
-                            <div id="perm_container_{0}"></div> \
-                        </div> \
-                    </td> \
-                    <td></td>'""")
-                %>
-                ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
-                <tr class="new_members last_new_member" id="add_perm_input"></tr>
+                ## New entries added by addPermAction here.
+                <tr class="new_members last_new_member" id="add_perm_input"><td colspan="6"></td></tr>
                 <tr>
                     <td colspan="6">
-                        <span id="add_perm" style="cursor: pointer;">
-                            <i class="icon-plus"></i> ${_('Add new')}
-                        </span>
+                        <button id="add_perm" class="btn btn-link btn-xs">
+                            <i class="icon-plus"></i>${_('Add new')}
+                        </button>
                     </td>
                 </tr>
             </table>
         </div>
         <div class="buttons">
-          ${h.submit('save',_('Save'),class_="btn")}
-          ${h.reset('reset',_('Reset'),class_="btn")}
+            ${h.submit('save',_('Save'),class_="btn btn-default")}
+            ${h.reset('reset',_('Reset'),class_="btn btn-default")}
         </div>
    </div>
 </div>
@@ -110,7 +94,7 @@
 
 <script type="text/javascript">
     function ajaxActionRevoke(obj_id, obj_type, field_id, obj_name) {
-        url = "${h.url('edit_user_group_perms', id=c.user_group.users_group_id)}";
+        url = ${h.js(h.url('edit_user_group_perms_delete', id=c.user_group.users_group_id))};
         var revoke_msg = _TM['Confirm to revoke permission for {0}: {1} ?'].format(obj_type.replace('_', ' '), obj_name);
         if (confirm(revoke_msg)){
             ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
@@ -122,7 +106,7 @@
             $('#add_perm_input').hide();
         }
         $('#add_perm').click(function () {
-            addPermAction(${_tmpl}, ${c.users_array|n}, ${c.user_groups_array|n});
+            addPermAction('usergroup');
         });
     });
 </script>
--- a/kallithea/templates/admin/user_groups/user_group_edit_settings.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_group_edit_settings.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,65 +1,51 @@
-${h.form(url('users_group', id=c.user_group.users_group_id),method='put', id='edit_users_group')}
+${h.form(url('update_users_group', id=c.user_group.users_group_id), id='edit_users_group')}
     <div class="form">
-        <!-- fields -->
-            <div class="fields">
-                 <div class="field">
-                    <div class="label">
-                        <label for="users_group_name">${_('Group name')}:</label>
+                <div class="form-group">
+                    <label class="control-label" for="users_group_name">${_('Group name')}:</label>
+                    <div>
+                        ${h.text('users_group_name',class_='form-control')}
                     </div>
-                    <div class="input">
-                        ${h.text('users_group_name',class_='large')}
-                    </div>
-                 </div>
-                 <div class="field">
-                    <div class="label label-textarea">
-                        <label for="user_group_description">${_('Description')}:</label>
-                    </div>
-                    <div class="textarea-small editor">
-                        ${h.textarea('user_group_description')}
+                </div>
+                <div class="form-group">
+                    <label class="control-label" for="user_group_description">${_('Description')}:</label>
+                    <div>
+                        ${h.textarea('user_group_description',class_='form-control')}
                         <span class="help-block">${_('Short, optional description for this user group.')}</span>
                     </div>
-                 </div>
-                 <div class="field">
-                    <div class="label label-checkbox">
-                        <label for="users_group_active">${_('Active')}:</label>
-                    </div>
-                    <div class="checkboxes">
+                </div>
+                <div class="form-group">
+                    <label class="control-label" for="users_group_active">${_('Active')}:</label>
+                    <div>
                         ${h.checkbox('users_group_active',value=True)}
                     </div>
-                 </div>
-                <div class="field">
-                    <div class="label">
-                        <label for="users_group_active">${_('Members')}:</label>
+                </div>
+                <div class="form-group">
+                    <label class="control-label">${_('Members')}:</label>
+                    <div>
+                        <div class="pull-left">
+                            <div class="text">${_('Chosen group members')}</div>
+                            ${h.select('users_group_members',[],c.group_members,multiple=True,size=8,style="width:210px",class_='form-control')}
+                        </div>
+                        <div class="pull-left">
+                          <div class="text">&nbsp;</div>
+                          <div>
+                            <i id="add_element" class="icon-left-open" title="Choose selected available"></i>
+                          </div>
+                          <div>
+                            <i id="remove_element" class="icon-right-open" title="Remove selected chosen"></i>
+                          </div>
+                        </div>
+                        <div class="pull-left">
+                            <div class="text">${_('Available members')}</div>
+                            ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="width:210px",class_='form-control')}
+                        </div>
                     </div>
-                    <div class="select">
-                        <table>
-                                <tr>
-                                    <td>
-                                        <div>
-                                            <div style="float:left">
-                                                <div class="text" style="padding: 0px 0px 6px;">${_('Chosen group members')}</div>
-                                                ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
-                                            </div>
-                                            <div style="float:left;width:20px;padding-top:50px">
-                                                <i style="cursor:pointer; font-size: 16px" id="add_element" class="icon-left-open"></i>
-                                                <br />
-                                                <i style="cursor:pointer; font-size: 16px" id="remove_element" class="icon-right-open"></i>
-                                            </div>
-                                            <div style="float:left">
-                                                 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
-                                                 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
-                                            </div>
-                                        </div>
-                                    </td>
-                                </tr>
-                        </table>
+                </div>
+                <div class="form-group">
+                    <div class="buttons">
+                        ${h.submit('Save',_('Save'),class_="btn btn-default")}
                     </div>
-
                 </div>
-                <div class="buttons">
-                  ${h.submit('Save',_('Save'),class_="btn")}
-                </div>
-            </div>
     </div>
 ${h.end_form()}
 <script type="text/javascript">
--- a/kallithea/templates/admin/user_groups/user_groups.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/user_groups/user_groups.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,8 +6,7 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
-    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('User Groups')}
+    ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('User Groups')}
 </%def>
 
 <%block name="header_menu">
@@ -15,43 +14,37 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
         %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
-          <li>
-            <a href="${h.url('new_users_group')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add User Group')}</a>
-          </li>
+            <a href="${h.url('new_users_group')}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Add User Group')}</a>
         %endif
-        </ul>
+        </div>
     </div>
-    <!-- end box / title -->
-    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
-    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+    <div class="panel-body">
+        <table class="table" id="datatable_list_wrap" width="100%"></table>
+    </div>
 </div>
 <script>
-  var data = ${c.data|n};
-  var fields = [
-    {key: "group_name"},
-    {key: "raw_name"},
-    {key: "desc"},
-    {key: "members"},
-    {key: "active"},
-    {key: "owner"},
-    {key: "action"}
-  ];
-  var column_defs = [
-    {key:"group_name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: nameSort }},
-    {key:"desc",label:"${_('Description')}",sortable:true},
-    {key:"members",label:"${_('Members')}",sortable:false},
-    {key:"active",label:"${_('Active')}",sortable:true},
-    {key:"owner",label:"${_('Owner')}",sortable:true},
-    {key:"action",label:"${_('Action')}",sortable:false}
-  ];
-  var counter = YUD.get('user_group_count');
-  var sort_key = "group_name";
-  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+    var data = ${h.js(c.data)};
+    var $dataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "raw_name", visible: false, searchable: false},
+            {data: "group_name", title: ${h.jshtml(_('Name'))}, orderData: 0},
+            {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+            {data: "members", title: ${h.jshtml(_('Members'))}, searchable: false},
+            {data: "active", title: ${h.jshtml(_('Active'))}, searchable: false, 'sType': 'str'},
+            {data: "owner", title: ${h.jshtml(_('Owner'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, searchable: false, sortable: false}
+        ],
+        order: [[1, "asc"]],
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
 </script>
 </%def>
--- a/kallithea/templates/admin/users/user_add.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_add.html	Sun Mar 31 21:28:56 2019 +0200
@@ -17,85 +17,69 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    ${h.form(url('users'))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="username">${_('Username')}:</label>
+    ${h.form(url('new_user'))}
+    <div class="form panel-body settings">
+            <div class="form-group">
+                <label class="control-label" for="username">${_('Username')}:</label>
+                <div>
+                    ${h.text('username',class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('username',class_='small')}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="password">${_('Password')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="password">${_('Password')}:</label>
+                <div>
+                    ${h.password('password',class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.password('password',class_='small')}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="password_confirmation">${_('Password confirmation')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="password_confirmation">${_('Password confirmation')}:</label>
+                <div>
+                    ${h.password('password_confirmation',class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.password('password_confirmation',class_="small")}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="firstname">${_('First Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('firstname',class_='small')}
+            <div class="form-group">
+                <label class="control-label" for="firstname">${_('First Name')}:</label>
+                <div>
+                    ${h.text('firstname',class_='form-control')}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="lastname">${_('Last Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('lastname',class_='small')}
+            <div class="form-group">
+                <label class="control-label" for="lastname">${_('Last Name')}:</label>
+                <div>
+                    ${h.text('lastname',class_='form-control')}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="email">${_('Email')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="email">${_('Email')}:</label>
+                <div>
+                    ${h.text('email',class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('email',class_='small')}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="active">${_('Active')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="active">${_('Active')}:</label>
+                <div>
                     ${h.checkbox('active',value=True,checked='checked')}
                 </div>
-             </div>
+            </div>
 
             ${h.hidden('extern_type', c.default_extern_type)}
             ${h.hidden('extern_name', c.default_extern_name)}
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
+
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/users/user_edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -18,13 +18,13 @@
 </%block>
 
 <%def name="main()">
-<div class="box" style="overflow:auto">
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
     ##main
-    <div style="width: 150px; float:left">
+    <div class="panel-body settings">
       <ul class="nav nav-pills nav-stacked">
         <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', id=c.user.user_id)}">${_('Profile')}</a></li>
         <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', id=c.user.user_id)}">${_('Emails')}</a></li>
@@ -33,10 +33,10 @@
         <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)}">${_('Show Permissions')}</a></li>
       </ul>
-    </div>
 
-    <div style="width:750px; float:left; padding: 10px 0px 0px 20px;margin: 0px 0px 0px 10px; border-left: 1px solid #DDDDDD">
-        <%include file="/admin/users/user_edit_${c.active}.html"/>
+      <div>
+          <%include file="/admin/users/user_edit_${c.active}.html"/>
+      </div>
     </div>
 </div>
 
--- a/kallithea/templates/admin/users/user_edit_advanced.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_advanced.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-<div style="font-size: 24px; color: #666666; padding: 0px 0px 10px 0px">${_('User: %s') % c.user.username}</div>
+<h3>${_('User: %s') % c.user.username}</h3>
 
 <dl class="dl-horizontal">
 <%
@@ -11,15 +11,15 @@
  ]
 %>
 %for dt, dd, tt in elems:
-  <dt style="width:150px; text-align: left">${dt}:</dt>
-  <dd style="margin-left: 160px" title="${tt}">${dd}</dd>
+  <dt>${dt}:</dt>
+  <dd title="${tt}">${dd}</dd>
 %endfor
 </dl>
 
-${h.form(h.url('delete_user', id=c.user.user_id),method='delete')}
-    <button class="btn btn-small btn-danger" type="submit"
+${h.form(h.url('delete_user', id=c.user.user_id))}
+    <button class="btn btn-danger btn-sm" type="submit"
             onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');">
-        <i class="icon-minus-circled"></i>
+        <i class="icon-trashcan"></i>
         ${_('Delete this user')}
     </button>
 ${h.end_form()}
--- a/kallithea/templates/admin/users/user_edit_api_keys.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_api_keys.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,16 +1,15 @@
-<div class="apikeys_wrap">
-  <table class="noborder">
+<table class="table">
     <tr>
-        <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${c.user.api_key}</div></td>
+        <td><div class="truncate autoexpand">${c.user.api_key}</div></td>
         <td>
-            <span class="btn btn-mini btn-success disabled">${_('Built-in')}</span>
+            <span class="label label-success">${_('Built-in')}</span>
         </td>
         <td>${_('Expires')}: ${_('Never')}</td>
         <td>
-            ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
+            ${h.form(url('edit_user_api_keys_delete', id=c.user.user_id))}
                 ${h.hidden('del_api_key',c.user.api_key)}
                 ${h.hidden('del_api_key_builtin',1)}
-                <button class="btn btn-mini btn-danger" type="submit"
+                <button class="btn btn-danger btn-xs" type="submit"
                         onclick="return confirm('${_('Confirm to reset this API key: %s') % c.user.api_key}');">
                     ${_('Reset')}
                 </button>
@@ -19,14 +18,14 @@
     </tr>
     %if c.user_api_keys:
         %for api_key in c.user_api_keys:
-          <tr class="${'expired' if api_key.expired else ''}">
-            <td style="width: 450px"><div class="truncate autoexpand" style="width:120px;font-size:16px;font-family: monospace">${api_key.api_key}</div></td>
+          <tr class="${'expired' if api_key.is_expired else ''}">
+            <td><div class="truncate autoexpand">${api_key.api_key}</div></td>
             <td>${api_key.description}</td>
-            <td style="min-width: 80px">
+            <td>
                  %if api_key.expires == -1:
                   ${_('Expires')}: ${_('Never')}
                  %else:
-                    %if api_key.expired:
+                    %if api_key.is_expired:
                         ${_('Expired')}: ${h.age(h.time_to_datetime(api_key.expires))}
                     %else:
                         ${_('Expires')}: ${h.age(h.time_to_datetime(api_key.expires))}
@@ -34,11 +33,11 @@
                  %endif
             </td>
             <td>
-                ${h.form(url('edit_user_api_keys', id=c.user.user_id),method='delete')}
+                ${h.form(url('edit_user_api_keys_delete', id=c.user.user_id))}
                     ${h.hidden('del_api_key',api_key.api_key)}
-                    <button class="btn btn-mini btn-danger" type="submit"
+                    <button class="btn btn-danger btn-xs" type="submit"
                             onclick="return confirm('${_('Confirm to remove this API key: %s') % api_key.api_key}');">
-                        <i class="icon-minus-circled"></i>
+                        <i class="icon-trashcan"></i>
                         ${_('Remove')}
                     </button>
                 ${h.end_form()}
@@ -48,28 +47,32 @@
     %else:
     <tr><td><div class="ip">${_('No additional API keys specified')}</div></td></tr>
     %endif
-  </table>
-</div>
+</table>
 
 <div>
-    ${h.form(url('edit_user_api_keys', id=c.user.user_id), method='post')}
+    ${h.form(url('edit_user_api_keys_update', id=c.user.user_id))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="description">${_('New API key')}:</label>
+            <div class="form-group">
+                <label class="control-label">${_('New API key')}</label>
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="description">${_('Description')}:</label>
+                <div>
+                    ${h.text('description', class_='form-control', placeholder=_('Description'))}
                 </div>
-                <div class="input">
-                    ${h.text('description', class_='medium', placeholder=_('Description'))}
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="lifetime">${_('Lifetime')}:</label>
+                <div>
                     ${h.select('lifetime', '', c.lifetime_options)}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
+            </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/users/user_edit_emails.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_emails.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,51 +1,52 @@
 <div class="emails_wrap">
-  <table class="noborder">
+  <table class="table">
     <tr>
-    <td><div class="gravatar">${h.gravatar(c.user.email, size=16)}</div></td>
+    %if c.visual.use_gravatar:
+    <td>${h.gravatar_div(c.user.email, size=16)}</td>
+    %endif
     <td><div class="email">${c.user.email}</div></td>
     <td>
-        <span class="btn btn-mini btn-success disabled">${_('Primary')}</span>
+        <span class="label label-success">${_('Primary')}</span>
     </td>
     </tr>
     %if c.user_email_map:
         %for em in c.user_email_map:
           <tr>
-            <td><div class="gravatar">${h.gravatar(c.user.email, size=16)}</div></td>
+            %if c.visual.use_gravatar:
+            <td>${h.gravatar_div(c.user.email, size=16)}</td>
+            %endif
             <td><div class="email">${em.email}</div></td>
             <td>
-                ${h.form(url('edit_user_emails', id=c.user.user_id),method='delete')}
+                ${h.form(url('edit_user_emails_delete', id=c.user.user_id))}
                     ${h.hidden('del_email_id',em.email_id)}
-                    <i class="icon-minus-circled" style="color:#FF4444"></i>
+                    <i class="icon-trashcan"></i>
                     ${h.submit('remove_',_('Delete'),id="remove_email_%s" % em.email_id,
-                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
+                    class_="btn btn-default btn-xs", onclick="return  confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
                 ${h.end_form()}
             </td>
           </tr>
         %endfor
     %else:
-    <tr><td><div class="ip">${_('No additional emails specified.')}</div></td></tr>
+    <tr><td colspan="${3 if c.visual.use_gravatar else 2}"><div class="ip">${_('No additional emails specified.')}</div></td></tr>
     %endif
   </table>
 </div>
 
 <div>
-    ${h.form(url('edit_user_emails', id=c.user.user_id),method='put')}
+    ${h.form(url('edit_user_emails_update', id=c.user.user_id))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_email">${_('New email address')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_email">${_('New email address')}:</label>
+                <div>
+                    ${h.text('new_email', class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('new_email', class_='medium')}
+            </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/users/user_edit_ips.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_ips.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,4 @@
-<div class="ips_wrap">
-  <table class="noborder">
+<table class="table">
     %if c.default_user_ip_map and c.inherit_default_ips:
         %for ip in c.default_user_ip_map:
           <tr>
@@ -16,11 +15,11 @@
             <td><div class="ip">${ip.ip_addr}</div></td>
             <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
             <td>
-                ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
+                ${h.form(url('edit_user_ips_delete', id=c.user.user_id))}
                     ${h.hidden('del_ip_id',ip.ip_id)}
-                    <i class="icon-minus-circled" style="color:#FF4444"></i>
+                    <i class="icon-trashcan"></i>
                     ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
-                    class_="action_button", onclick="return  confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
+                        class_="btn btn-default btn-xs", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
                 ${h.end_form()}
             </td>
           </tr>
@@ -29,27 +28,23 @@
     %if not c.default_user_ip_map and not c.user_ip_map:
         <tr><td><div class="ip">${_('All IP addresses are allowed.')}</div></td></tr>
     %endif
-  </table>
-</div>
+</table>
 
 <div>
-    ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
+    ${h.form(url('edit_user_ips_update', id=c.user.user_id))}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label">
-                    <label for="new_ip">${_('New IP address')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="new_ip">${_('New IP address')}:</label>
+                <div>
+                    ${h.text('new_ip', class_='form-control')}
                 </div>
-                <div class="input">
-                    ${h.text('new_ip', class_='medium')}
+            </div>
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Add'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
                 </div>
-             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Add'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/admin/users/user_edit_perms.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_perms.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,5 @@
 <%namespace name="dpb" file="/base/default_perms_box.html"/>
-${dpb.default_perms_box(url('edit_user_perms', id=c.user.user_id))}
+${dpb.default_perms_box(url('edit_user_perms_update', id=c.user.user_id))}
 
 ## permissions overview
 <%namespace name="p" file="/base/perms_summary.html"/>
--- a/kallithea/templates/admin/users/user_edit_profile.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/user_edit_profile.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,118 +1,94 @@
-${h.form(url('update_user', id=c.user.user_id),method='put')}
-<div class="form">
-        <div class="field">
-           <div class="gravatar_box">
-                <div class="gravatar">${h.gravatar(c.user.email)}</div>
-                <p>
-                %if c.visual.use_gravatar:
-                <strong>${_('Change avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
-                <br/>${_('Using')} ${c.user.email}
-                %else:
-                <strong>${_('Avatars are disabled')}</strong>
-                <br/>${c.user.email or _('Missing email, please update this user email address.')}
-                        ##show current ip just if we show ourself
-                        %if c.authuser.username == c.user.username:
-                            [${_('Current IP')}: ${c.ip_addr}]
-                        %endif
-                %endif
-           </div>
-        </div>
-        <div class="fields">
+${h.form(url('update_user', id=c.user.user_id))}
+    <div class="form">
+            <div class="form-group">
+                <label class="control-label">${_('Gravatar')}:</label>
+                <div class="gravatar_box clearfix">
+                    ${h.gravatar_div(c.user.email)}
+                </div>
+                <div>
+                    %if c.visual.use_gravatar:
+                        ${_('Change %s avatar at') % c.user.email} <a href="http://gravatar.com">gravatar.com</a>
+                    %else:
+                        ${_('Avatars are disabled')}
+                    %endif
+                </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="username">${_('Username')}:</label>
-                </div>
-                <div class="input">
-                  ${h.text('username',class_='medium', readonly=c.readonly('username'))}
-                </div>
-             </div>
-
-             <div class="field">
-                <div class="label">
-                    <label for="email">${_('Email')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="username">${_('Username')}:</label>
+                <div>
+                    ${h.text('username',class_='form-control', readonly=c.readonly('username'))}
                 </div>
-                <div class="input">
-                    ${h.text('email',class_='medium', readonly=c.readonly('email'))}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="extern_type">${_('Source of Record')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="email">${_('Email')}:</label>
+                <div>
+                    ${h.text('email',class_='form-control', readonly=c.readonly('email'))}
                 </div>
-                <div class="input">
-                    ${h.text('extern_type',class_='medium',readonly="readonly")}
+            </div>
+
+            <div class="form-group">
+                <label class="control-label" for="extern_type">${_('Source of Record')}:</label>
+                <div>
+                    ${h.text('extern_type',class_='form-control',readonly="readonly")}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="extern_name">${_('Name in Source of Record')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="extern_name">${_('Name in Source of Record')}:</label>
+                <div>
+                    ${h.text('extern_name',class_='form-control',readonly="readonly")}
                 </div>
-                <div class="input">
-                    ${h.text('extern_name',class_='medium',readonly="readonly")}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="new_password">${_('New password')}:</label>
-                </div>
-                <div class="input">
-                    ${h.password('new_password',class_='medium',readonly=c.readonly('password'))}
+            <div class="form-group">
+                <label class="control-label" for="new_password">${_('New password')}:</label>
+                <div>
+                    ${h.password('new_password',class_='form-control',readonly=c.readonly('password'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="password_confirmation">${_('New password confirmation')}:</label>
-                </div>
-                <div class="input">
-                    ${h.password('password_confirmation',class_="medium",readonly=c.readonly('password'))}
+            <div class="form-group">
+                <label class="control-label" for="password_confirmation">${_('New password confirmation')}:</label>
+                <div>
+                    ${h.password('password_confirmation',class_='form-control',readonly=c.readonly('password'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="firstname">${_('First Name')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="firstname">${_('First Name')}:</label>
+                <div>
+                    ${h.text('firstname',class_='form-control', readonly=c.readonly('firstname'))}
                 </div>
-                <div class="input">
-                    ${h.text('firstname',class_='medium', readonly=c.readonly('firstname'))}
-                </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="lastname">${_('Last Name')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('lastname',class_='medium', readonly=c.readonly('lastname'))}
+            <div class="form-group">
+                <label class="control-label" for="lastname">${_('Last Name')}:</label>
+                <div>
+                    ${h.text('lastname',class_='form-control', readonly=c.readonly('lastname'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="active">${_('Active')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="active">${_('Active')}:</label>
+                <div>
                     ${h.checkbox('active',value=True, readonly=c.readonly('active'))}
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="admin">${_('Admin')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="admin">${_('Admin')}:</label>
+                <div>
                     ${h.checkbox('admin',value=True, readonly=c.readonly('admin'))}
                 </div>
-             </div>
+            </div>
 
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
-</div>
+    </div>
 ${h.end_form()}
--- a/kallithea/templates/admin/users/users.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/admin/users/users.html	Sun Mar 31 21:28:56 2019 +0200
@@ -6,7 +6,6 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
     ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span> ${_('Users')}
 </%def>
 
@@ -15,50 +14,41 @@
 </%block>
 
 <%def name="main()">
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-          <li>
-            <a href="${h.url('new_user')}" class="btn btn-small btn-success"><i class="icon-plus"></i> ${_('Add User')}</a>
-          </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="${h.url('new_user')}" class="btn btn-success btn-xs"><i class="icon-plus"></i>${_('Add User')}</a>
+        </div>
     </div>
-    <!-- end box / title -->
-    <div class="table-grid table yui-skin-sam" id="datatable_list_wrap"></div>
-    <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
+    <div class="panel-body">
+        <table class="table" id="datatable_list_wrap" width="100%"></table>
+    </div>
 </div>
 
 <script>
-  var data = ${c.data|n};
-  var fields = [
-    {key: "gravatar"},
-    {key: "raw_name"},
-    {key: "username"},
-    {key: "firstname"},
-    {key: "lastname"},
-    {key: "last_login"},
-    {key: "last_login_raw"},
-    {key: "active"},
-    {key: "admin"},
-    {key: "extern_type"},
-    {key: "action"}
-  ];
-  var column_defs = [
-    {key:"gravatar",label:"",sortable:false},
-    {key:"username",label:"${_('Username')}",sortable:true},
-    {key:"firstname",label:"${_('First Name')}",sortable:true},
-    {key:"lastname",label:"${_('Last Name')}",sortable:true},
-    {key:"last_login",label:"${_('Last Login')}",sortable:true, sortOptions: { sortFunction: lastLoginSort }},
-    {key:"active",label:"${_('Active')}",sortable:true},
-    {key:"admin",label:"${_('Admin')}",sortable:true},
-    {key:"extern_type",label:"${_('Auth Type')}",sortable:true},
-    {key:"action",label:"${_('Action')}",sortable:false}
-  ];
-  var counter = YUD.get('user_count');
-  var sort_key = "username";
-  YUI_datatable(data, fields, column_defs, counter, sort_key, ${c.visual.admin_grid_items});
+    var data = ${h.js(c.data)};
+    var $dataTable = $("#datatable_list_wrap").DataTable({
+        data: data.records,
+        columns: [
+            {data: "gravatar", sortable: false, searchable: false},
+            {data: "username", title: ${h.jshtml(_('Username'))}},
+            {data: "firstname", title: ${h.jshtml(_('First Name'))}},
+            {data: "lastname", title: ${h.jshtml(_('Last Name'))}},
+            {data: "last_login_raw", visible: false, searchable: false},
+            {data: "last_login", title: ${h.jshtml(_('Last Login'))}, orderData: 4, searchable: false},
+            {data: "active", title: ${h.jshtml(_('Active'))}, searchable: false, 'sType': 'str'},
+            {data: "admin", title: ${h.jshtml(_('Admin'))}, searchable: false, 'sType': 'str'},
+            {data: "extern_type", title: ${h.jshtml(_('Auth Type'))}, searchable: false},
+            {data: "action", title: ${h.jshtml(_('Action'))}, searchable: false, sortable: false}
+        ],
+        order: [[1, "asc"]],
+        drawCallback: updateRowCountCallback($("#user_count")),
+        dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+        pageLength: 100
+    });
 </script>
 
 </%def>
--- a/kallithea/templates/base/base.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/base/base.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,7 +2,7 @@
 <%inherit file="root.html"/>
 
 <!-- CONTENT -->
-<div id="content">
+<div id="content" class="container-fluid">
     ${self.flash_msg()}
     <div id="main">
         ${next.main()}
@@ -11,27 +11,23 @@
 <!-- END CONTENT -->
 
 <!-- FOOTER -->
-<div id="footer">
-   <div id="footer-inner" class="title">
-       <div>
-           <p class="footer-link">
-               ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
-           </p>
-           <p class="footer-link-right">
-               This site is powered by
-               %if c.visual.show_version:
-                   <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
-               %else:
-                   <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
-               %endif
-               which is
-               <a href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2019 by various authors &amp; licensed under GPLv3</a>.
-               %if c.issues_url:
-                   &ndash; <a href="${c.issues_url}" target="_blank">${_('Support')}</a>
-               %endif
-           </p>
-       </div>
-   </div>
+<div class="footer navbar navbar-inverse">
+    <span class="navbar-text pull-left">
+        ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
+    </span>
+    <span class="navbar-text pull-right">
+        This site is powered by
+        %if c.visual.show_version:
+            <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
+        %else:
+            <a class="navbar-link" href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
+        %endif
+        which is
+        <a class="navbar-link" href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2019 by various authors &amp; licensed under GPLv3</a>.
+        %if c.issues_url:
+            &ndash; <a class="navbar-link" href="${c.issues_url}" target="_blank">${_('Support')}</a>
+        %endif
+    </span>
 </div>
 
 <!-- END FOOTER -->
@@ -49,22 +45,22 @@
 </%def>
 
 <%def name="breadcrumbs()">
-    <div class="breadcrumbs">
+    <div class="panel-title">
     ${self.breadcrumbs_links()}
     </div>
 </%def>
 
 <%def name="admin_menu()">
-  <ul class="admin_menu">
-      <li><a href="${h.url('admin_home')}"><i class="icon-book"></i> ${_('Admin Journal')}</a></li>
-      <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
-      <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
-      <li><a href="${h.url('users')}"><i class="icon-user"></i> ${_('Users')}</a></li>
-      <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
-      <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i> ${_('Default Permissions')}</a></li>
-      <li><a href="${h.url('auth_home')}"><i class="icon-key"></i> ${_('Authentication')}</a></li>
-      <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Repository Defaults')}</a></li>
-      <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
+  <ul class="dropdown-menu" role="menu">
+      <li><a href="${h.url('admin_home')}"><i class="icon-book"></i>${_('Admin Journal')}</a></li>
+      <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
+      <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
+      <li><a href="${h.url('users')}"><i class="icon-user"></i>${_('Users')}</a></li>
+      <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
+      <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i>${_('Default Permissions')}</a></li>
+      <li><a href="${h.url('auth_home')}"><i class="icon-key"></i>${_('Authentication')}</a></li>
+      <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i>${_('Repository Defaults')}</a></li>
+      <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i>${_('Settings')}</a></li>
   </ul>
 
 </%def>
@@ -72,521 +68,475 @@
 
 ## admin menu used for people that have some admin resources
 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
-  <ul>
+  <ul class="dropdown-menu" role="menu">
    %if repositories:
-      <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
+      <li><a href="${h.url('repos')}"><i class="icon-database"></i>${_('Repositories')}</a></li>
    %endif
    %if repository_groups:
-      <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
+      <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i>${_('Repository Groups')}</a></li>
    %endif
    %if user_groups:
-      <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
+      <li><a href="${h.url('users_groups')}"><i class="icon-users"></i>${_('User Groups')}</a></li>
    %endif
   </ul>
 </%def>
 
-<%def name="repotag(repo)">
+<%def name="repolabel(repo)">
   %if h.is_hg(repo):
-    <span class="repotag" title="${_('Mercurial repository')}">hg</span>
+    <span class="label label-repo" title="${_('Mercurial repository')}">hg</span>
   %endif
   %if h.is_git(repo):
-    <span class="repotag" title="${_('Git repository')}">git</span>
+    <span class="label label-repo" title="${_('Git repository')}">git</span>
   %endif
 </%def>
 
 <%def name="repo_context_bar(current=None, rev=None)">
   <% rev = None if rev == 'tip' else rev %>
-  <%
-    def is_current(selected):
-        if selected == current:
-            return h.literal('class="current"')
-    %>
-
   <!--- CONTEXT BAR -->
-  <div id="context-bar" class="box">
-      <h2>
-        ${repotag(c.db_repo)}
+  <nav id="context-bar" class="navbar navbar-inverse">
+    <div class="container-fluid">
+    <div class="navbar-header">
+      <div class="navbar-brand">
+        ${repolabel(c.db_repo)}
 
         ## public/private
         %if c.db_repo.private:
-          <i class="icon-keyhole-circled"></i>
+          <i class="icon-lock"></i>
         %else:
           <i class="icon-globe"></i>
         %endif
-        ${h.repo_link(c.db_repo.groups_and_repo)}
+        %for group in c.db_repo.groups_with_parents:
+          ${h.link_to(group.name, url('repos_group_home', group_name=group.group_name), class_='navbar-link')}
+          &raquo;
+        %endfor
+        ${h.link_to(c.db_repo.just_name, url('summary_home', repo_name=c.db_repo.repo_name), class_='navbar-link')}
 
         %if current == 'createfork':
          - ${_('Create Fork')}
         %endif
-      </h2>
-      <!--
-      <div id="breadcrumbs">
-        ${h.link_to(_('Repositories'),h.url('home'))}
-        &raquo;
-        ${h.repo_link(c.db_repo.groups_and_repo)}
       </div>
-      -->
-      <ul id="context-pages" class="horizontal-list">
-        <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i> ${_('Summary')}</a></li>
+      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#context-pages" aria-expanded="false">
+        <span class="sr-only">Toggle navigation</span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </button>
+    </div>
+    <div id="context-pages" class="navbar-collapse collapse">
+    <ul class="nav navbar-nav navbar-right">
+        <li class="${'active' if current == 'summary' else ''}" data-context="summary"><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i>${_('Summary')}</a></li>
         %if rev:
-        <li ${is_current('changelog')}><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
+        <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
         %else:
-        <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
+        <li class="${'active' if current == 'changelog' else ''}" data-context="changelog"><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i>${_('Changelog')}</a></li>
         %endif
-        <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i> ${_('Files')}</a></li>
-        <li ${is_current('switch-to')}>
-          <a href="#" id="branch_tag_switcher_2" class="dropdown"><i class="icon-exchange"></i> ${_('Switch To')}</a>
-          <ul id="switch_to_list_2" class="switch_to submenu">
-            <li><a href="#">${_('Loading...')}</a></li>
-          </ul>
+        <li class="${'active' if current == 'files' else ''}" data-context="files"><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i>${_('Files')}</a></li>
+        <li class="${'active' if current == 'showpullrequest' else ''}" data-context="showpullrequest">
+          <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i>${_('Pull Requests')}
+            %if c.repository_pull_requests:
+              <span class="badge">${c.repository_pull_requests}</span>
+            %endif
+          </a>
         </li>
-        <li ${is_current('options')}>
-             %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
-               <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
+        <li class="${'active' if current == 'switch-to' else ''}" data-context="switch-to">
+          <input id="branch_switcher" name="branch_switcher" type="hidden">
+        </li>
+        <li class="${'active' if current == 'options' else ''} dropdown" data-context="options">
+             %if h.HasRepoPermissionLevel('admin')(c.repo_name):
+               <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
              %else:
-               <a href="#" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
+               <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true"><i class="icon-wrench"></i>${_('Options')} <i class="caret"></i></a>
              %endif
-          <ul>
-             %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
-                   <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
+          <ul class="dropdown-menu" role="menu" aria-hidden="true">
+             %if h.HasRepoPermissionLevel('admin')(c.repo_name):
+                   <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i>${_('Settings')}</a></li>
              %endif
               %if c.db_repo.fork:
                <li><a href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}">
-                   <i class="icon-git-compare"></i> ${_('Compare Fork')}</a></li>
+                   <i class="icon-git-compare"></i>${_('Compare Fork')}</a></li>
               %endif
-              <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i> ${_('Compare')}</a></li>
+              <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i>${_('Compare')}</a></li>
 
-              <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i> ${_('Search')}</a></li>
+              <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i>${_('Search')}</a></li>
 
-              %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.db_repo.enable_locking:
+              %if h.HasRepoPermissionLevel('write')(c.repo_name) and c.db_repo.enable_locking:
                 %if c.db_repo.locked[0]:
-                  <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i> ${_('Unlock')}</a></li>
+                  <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i>${_('Unlock')}</a></li>
                 %else:
-                  <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i> ${_('Lock')}</li>
+                  <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i>${_('Lock')}</a></li>
                 %endif
               %endif
               ## TODO: this check feels wrong, it would be better to have a check for permissions
               ## also it feels like a job for the controller
-              %if c.authuser.username != 'default':
+              %if request.authuser.username != 'default':
                   <li>
-                   <a class="${'following' if c.repository_following else 'follow'}" onclick="toggleFollowingRepo(this, ${c.db_repo.repo_id});">
-                    <span class="show-follow"><i class="icon-heart-empty"></i> ${_('Follow')}</span>
-                    <span class="show-following"><i class="icon-heart"></i> ${_('Unfollow')}</span>
+                   <a href="#" class="${'following' if c.repository_following else 'follow'}" onclick="toggleFollowingRepo(this, ${c.db_repo.repo_id});">
+                    <span class="show-follow"><i class="icon-heart-empty"></i>${_('Follow')}</span>
+                    <span class="show-following"><i class="icon-heart"></i>${_('Unfollow')}</span>
                    </a>
                   </li>
-                  <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Fork')}</a></li>
-                  <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Create Pull Request')}</a></li>
+                  <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Fork')}</a></li>
+                  <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i>${_('Create Pull Request')}</a></li>
               %endif
              </ul>
         </li>
-        <li ${is_current('showpullrequest')}>
-          <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i> ${_('Pull Requests')}
-            %if c.repository_pull_requests:
-              <span>${c.repository_pull_requests}</span>
-            %endif
-          </a>
-        </li>
-      </ul>
-  </div>
+    </ul>
+    </div>
+    </div>
+  </nav>
   <script type="text/javascript">
-      YUE.on('branch_tag_switcher_2','mouseover',function(){
-         var $branch_tag_switcher_2 = $('#branch_tag_switcher_2');
-         var loaded = $branch_tag_switcher_2.hasClass('loaded');
-         if(!loaded){
-             $branch_tag_switcher_2.addClass('loaded');
-             asynchtml("${h.url('branch_tag_switcher',repo_name=c.repo_name)}", $('#switch_to_list_2'));
-         }
-         return false;
+    $(document).ready(function() {
+      var bcache = {};
+
+      var branch_switcher_placeholder = '<i class="icon-exchange"></i>' + ${h.jshtml(_('Switch To'))} + ' <span class="caret"></span>';
+      $("#branch_switcher").select2({
+          placeholder: branch_switcher_placeholder,
+          dropdownAutoWidth: true,
+          sortResults: prefixFirstSort,
+          formatResult: function(obj) {
+              return obj.text.html_escape();
+          },
+          formatSelection: function(obj) {
+              return obj.text.html_escape();
+          },
+          formatNoMatches: function(term) {
+              return ${h.jshtml(_('No matches found'))};
+          },
+          escapeMarkup: function(m) {
+              if (m == branch_switcher_placeholder)
+                  return branch_switcher_placeholder;
+              return Select2.util.escapeMarkup(m);
+          },
+          containerCssClass: "branch-switcher",
+          dropdownCssClass: "repo-switcher-dropdown",
+          query: function(query) {
+              var key = 'cache';
+              var cached = bcache[key];
+              if (cached) {
+                  var data = {
+                      results: []
+                  };
+                  // filter results
+                  $.each(cached.results, function() {
+                      var section = this.text;
+                      var children = [];
+                      $.each(this.children, function() {
+                          if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
+                              children.push({
+                                  'id': this.id,
+                                  'text': this.text,
+                                  'type': this.type,
+                                  'obj': this.obj
+                              });
+                          }
+                      });
+                      if (children.length !== 0) {
+                          data.results.push({
+                              'text': section,
+                              'children': children
+                          });
+                      }
+
+                  });
+                  query.callback(data);
+              } else {
+                  $.ajax({
+                      url: pyroutes.url('repo_refs_data', {
+                          'repo_name': ${h.js(c.repo_name)}
+                      }),
+                      data: {},
+                      dataType: 'json',
+                      type: 'GET',
+                      success: function(data) {
+                          bcache[key] = data;
+                          query.callback(data);
+                      }
+                  });
+              }
+          }
       });
+
+      $("#branch_switcher").on('select2-selecting', function(e) {
+          e.preventDefault();
+          var context = $('#context-bar .active').data('context');
+          if (context == 'files') {
+              window.location = pyroutes.url('files_home', {
+                  'repo_name': REPO_NAME,
+                  'revision': e.choice.id,
+                  'f_path': '',
+                  'at': e.choice.text
+              });
+          } else if (context == 'changelog') {
+              if (e.choice.type == 'tag' || e.choice.type == 'book') {
+                  $("#branch_filter").append($('<'+'option/>').val(e.choice.text));
+              }
+              $("#branch_filter").val(e.choice.text).change();
+          } else {
+              window.location = pyroutes.url('changelog_home', {
+                  'repo_name': ${h.js(c.repo_name)},
+                  'branch': e.choice.text
+              });
+          }
+      });
+    });
   </script>
   <!--- END CONTEXT BAR -->
 </%def>
 
 <%def name="menu(current=None)">
-  <%
-  def is_current(selected):
-      if selected == current:
-          return h.literal('class="current"')
-  %>
-
-  <ul id="quick" class="horizontal-list">
+  <ul id="quick" class="nav navbar-nav navbar-right">
     <!-- repo switcher -->
-    <li ${is_current('repositories')}>
+    <li class="${'active' if current == 'repositories' else ''}">
       <input id="repo_switcher" name="repo_switcher" type="hidden">
     </li>
 
     ##ROOT MENU
-    %if c.authuser.username != 'default':
-      <li ${is_current('journal')}>
+    %if request.authuser.username != 'default':
+      <li class="${'active' if current == 'journal' else ''}">
         <a class="menu_link" title="${_('Show recent activity')}"  href="${h.url('journal')}">
-          <i class="icon-book"></i> ${_('Journal')}
+          <i class="icon-book"></i>${_('Journal')}
         </a>
       </li>
     %else:
-      <li ${is_current('journal')}>
+      <li class="${'active' if current == 'journal' else ''}">
         <a class="menu_link" title="${_('Public journal')}"  href="${h.url('public_journal')}">
-          <i class="icon-book"></i> ${_('Public journal')}
+          <i class="icon-book"></i>${_('Public journal')}
         </a>
       </li>
     %endif
-      <li ${is_current('gists')}>
-        <a class="menu_link childs" title="${_('Show public gists')}"  href="${h.url('gists')}">
-          <i class="icon-clippy"></i> ${_('Gists')}
+      <li class="${'active' if current == 'gists' else ''} dropdown">
+        <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Show public gists')}"  href="${h.url('gists')}">
+          <i class="icon-clippy"></i>${_('Gists')} <span class="caret"></span>
         </a>
-          <ul class="admin_menu">
-            <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i> ${_('Create New Gist')}</a></li>
-            <li><a href="${h.url('gists')}"><i class="icon-globe"></i> ${_('All Public Gists')}</a></li>
-            %if c.authuser.username != 'default':
-              <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i> ${_('My Public Gists')}</a></li>
-              <li><a href="${h.url('gists', private=1)}"><i class="icon-keyhole-circled"></i> ${_('My Private Gists')}</a></li>
+          <ul class="dropdown-menu" role="menu">
+            <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i>${_('Create New Gist')}</a></li>
+            <li><a href="${h.url('gists')}"><i class="icon-globe"></i>${_('All Public Gists')}</a></li>
+            %if request.authuser.username != 'default':
+              <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i>${_('My Public Gists')}</a></li>
+              <li><a href="${h.url('gists', private=1)}"><i class="icon-lock"></i>${_('My Private Gists')}</a></li>
             %endif
           </ul>
       </li>
-    <li ${is_current('search')}>
+    <li class="${'active' if current == 'search' else ''}">
         <a class="menu_link" title="${_('Search in repositories')}"  href="${h.url('search')}">
-          <i class="icon-search"></i> ${_('Search')}
+          <i class="icon-search"></i>${_('Search')}
         </a>
     </li>
-    % if h.HasPermissionAll('hg.admin')('access admin main page'):
-      <li ${is_current('admin')}>
-        <a class="menu_link childs" title="${_('Admin')}" href="${h.url('admin_home')}">
-          <i class="icon-gear"></i> ${_('Admin')}
+    % if h.HasPermissionAny('hg.admin')('access admin main page'):
+      <li class="${'active' if current == 'admin' else ''} dropdown">
+        <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="${h.url('admin_home')}">
+          <i class="icon-gear"></i>${_('Admin')} <span class="caret"></span>
         </a>
         ${admin_menu()}
       </li>
-    % elif c.authuser.repositories_admin or c.authuser.repository_groups_admin or c.authuser.user_groups_admin:
-    <li ${is_current('admin')}>
-        <a class="menu_link childs" title="${_('Admin')}">
-          <i class="icon-gear"></i> ${_('Admin')}
+    % elif request.authuser.repositories_admin or request.authuser.repository_groups_admin or request.authuser.user_groups_admin:
+    <li class="${'active' if current == 'admin' else ''} dropdown">
+        <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" title="${_('Admin')}" href="">
+          <i class="icon-gear"></i>${_('Admin')}
         </a>
-        ${admin_menu_simple(c.authuser.repositories_admin,
-                            c.authuser.repository_groups_admin,
-                            c.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
+        ${admin_menu_simple(request.authuser.repositories_admin,
+                            request.authuser.repository_groups_admin,
+                            request.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
     </li>
     % endif
 
-    <li ${is_current('my_pullrequests')}>
+    <li class="${'active' if current == 'my_pullrequests' else ''}">
       <a class="menu_link" title="${_('My Pull Requests')}" href="${h.url('my_pullrequests')}">
-        <i class="icon-git-pull-request"></i> ${_('My Pull Requests')}
+        <i class="icon-git-pull-request"></i>${_('My Pull Requests')}
         %if c.my_pr_count != 0:
-          <span class="menu_link_notifications">${c.my_pr_count}</span>
+          <span class="badge">${c.my_pr_count}</span>
         %endif
       </a>
     </li>
 
     ## USER MENU
-    <li>
-      <a class="menu_link childs" id="quick_login_link">
-          <span class="icon">
-            ${h.gravatar(c.authuser.email, size=20)}
-          </span>
-          %if c.authuser.username != 'default':
-            <span class="menu_link_user">${c.authuser.username}</span>
-            %if c.unread_notifications != 0:
-              <span class="menu_link_notifications">${c.unread_notifications}</span>
-            %endif
+    <li class="dropdown">
+      <a class="menu_link dropdown-toggle" data-toggle="dropdown" role="button" id="quick_login_link"
+        aria-expanded="false" aria-controls="quick_login" href="#">
+          ${h.gravatar_div(request.authuser.email, size=20, div_class="icon")}
+          %if request.authuser.username != 'default':
+            <span class="menu_link_user">${request.authuser.username}</span>
           %else:
               <span>${_('Not Logged In')}</span>
           %endif
+          <i class="caret"></i>
       </a>
 
-      <div class="user-menu">
-        <div id="quick_login">
-          %if c.authuser.username == 'default' or c.authuser.user_id is None:
-            <h4>${_('Login to Your Account')}</h4>
-            ${h.form(h.url('login_home', came_from=request.path_qs))}
-            <div class="form">
-                <div class="fields">
-                    <div class="field">
-                        <div class="label">
-                            <label for="username">${_('Username')}:</label>
-                        </div>
-                        <div class="input">
-                            ${h.text('username',class_='focus')}
-                        </div>
-
-                    </div>
-                    <div class="field">
-                        <div class="label">
-                            <label for="password">${_('Password')}:</label>
-                        </div>
-                        <div class="input">
-                            ${h.password('password',class_='focus')}
-                        </div>
-
-                    </div>
-                    <div class="buttons">
-                        <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
-                        <div class="register">
-                        %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
-                         ${h.link_to(_("Don't have an account ?"),h.url('register'))}
-                        %endif
-                        </div>
-                        <div class="submit">
-                            ${h.submit('sign_in',_('Log In'),class_="btn btn-mini")}
-                        </div>
-                    </div>
+      <div class="dropdown-menu user-menu" role="menu">
+        <div id="quick_login" role="form" aria-describedby="quick_login_h" aria-hidden="true" class="container-fluid">
+          %if request.authuser.username == 'default' or request.authuser.user_id is None:
+            ${h.form(h.url('login_home', came_from=request.path_qs), class_='form clearfix')}
+                <h4 id="quick_login_h">${_('Login to Your Account')}</h4>
+                <label>
+                    ${_('Username')}:
+                    ${h.text('username',class_='form-control')}
+                </label>
+                <label>
+                    ${_('Password')}:
+                    ${h.password('password',class_='form-control')}
+                </label>
+                <div class="password_forgotten">
+                    ${h.link_to(_('Forgot password?'),h.url('reset_password'))}
                 </div>
-            </div>
+                <div class="register">
+                    %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
+                        ${h.link_to(_("Don't have an account?"),h.url('register'))}
+                    %endif
+                </div>
+                <div class="submit">
+                    ${h.submit('sign_in',_('Log In'),class_="btn btn-default btn-xs")}
+                </div>
             ${h.end_form()}
           %else:
-            <div class="links_left">
-                <div class="big_gravatar">
-                  ${h.gravatar(c.authuser.email, size=48)}
-                </div>
-                <div class="full_name">${c.authuser.full_name_or_username}</div>
-                <div class="email">${c.authuser.email}</div>
+            <div class="pull-left">
+                ${h.gravatar_div(request.authuser.email, size=48, div_class="big_gravatar")}
+                <b class="full_name">${request.authuser.full_name_or_username}</b>
+                <div class="email">${request.authuser.email}</div>
             </div>
-            <div class="links_right">
-            <ol class="links">
-              <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
-              <li>${h.link_to(_('My Account'),h.url('my_account'))}</li>
-              %if not c.authuser.is_external_auth:
+            <div id="quick_login_h" class="pull-right list-group text-right">
+              ${h.link_to(_('My Account'),h.url('my_account'),class_='list-group-item')}
+              %if not request.authuser.is_external_auth:
                 ## Cannot log out if using external (container) authentication.
-                <li class="logout">${h.link_to(_('Log Out'), h.url('logout_home'))}</li>
+                ${h.link_to(_('Log Out'), h.url('logout_home'),class_='list-group-item')}
               %endif
-            </ol>
             </div>
           %endif
         </div>
       </div>
     </li>
+  </ul>
 
     <script type="text/javascript">
-        var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
-        var cache = {}
-        /*format the look of items in the list*/
-        var format = function(state){
-            if (!state.id){
-              return state.text; // optgroup
-            }
-            var obj_dict = state.obj;
-            var tmpl = '';
-
-            if(obj_dict && state.type == 'repo'){
-                tmpl += '<span class="repo-icons">';
-                if(obj_dict['repo_type'] === 'hg'){
-                    tmpl += '<span class="repotag">hg</span> ';
+        $(document).ready(function(){
+            var visual_show_public_icon = ${h.js(c.visual.show_public_icon)};
+            var cache = {}
+            /*format the look of items in the list*/
+            var format = function(state){
+                if (!state.id){
+                  return state.text.html_escape(); // optgroup
                 }
-                else if(obj_dict['repo_type'] === 'git'){
-                    tmpl += '<span class="repotag">git</span> ';
-                }
-                if(obj_dict['private']){
-                    tmpl += '<i class="icon-keyhole-circled"></i> ';
-                }
-                else if(visual_show_public_icon){
-                    tmpl += '<i class="icon-globe"></i> ';
-                }
-                tmpl += '</span>';
-            }
-            if(obj_dict && state.type == 'group'){
-                    tmpl += '<i class="icon-folder"></i> ';
-            }
-            tmpl += state.text;
-            return tmpl;
-        }
+                var obj_dict = state.obj;
+                var tmpl = '';
 
-        $("#repo_switcher").select2({
-            placeholder: '<i class="icon-database"></i> ${_('Repositories')}',
-            dropdownAutoWidth: true,
-            formatResult: format,
-            formatSelection: format,
-            formatNoMatches: function(term){
-                return "${_('No matches found')}";
-            },
-            containerCssClass: "repo-switcher",
-            dropdownCssClass: "repo-switcher-dropdown",
-            escapeMarkup: function(m){
-                // don't escape our custom placeholder
-                if(m.substr(0,29) == '<i class="icon-database"></i>'){
-                    return m;
+                if(obj_dict && state.type == 'repo'){
+                    tmpl += '<span class="repo-icons">';
+                    if(obj_dict['repo_type'] === 'hg'){
+                        tmpl += '<span class="label label-repo" title="${_('Mercurial repository')}">hg</span> ';
+                    }
+                    else if(obj_dict['repo_type'] === 'git'){
+                        tmpl += '<span class="label label-repo" title="${_('Git repository')}">git</span> ';
+                    }
+                    if(obj_dict['private']){
+                        tmpl += '<i class="icon-lock"></i>';
+                    }
+                    else if(visual_show_public_icon){
+                        tmpl += '<i class="icon-globe"></i>';
+                    }
+                    tmpl += '</span>';
                 }
-
-                return Select2.util.escapeMarkup(m);
-            },
-            query: function(query){
-              var key = 'cache';
-              var cached = cache[key] ;
-              if(cached) {
-                var data = {results: []};
-                //filter results
-                $.each(cached.results, function(){
-                    var section = this.text;
-                    var children = [];
-                    $.each(this.children, function(){
-                        if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                            children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj});
-                        }
-                    });
-                    if(children.length !== 0){
-                        data.results.push({'text': section, 'children': children});
-                    }
+                if(obj_dict && state.type == 'group'){
+                        tmpl += '<i class="icon-folder"></i>';
+                }
+                tmpl += state.text.html_escape();
+                return tmpl;
+            }
 
-                });
-                query.callback(data);
-              }else{
-                  $.ajax({
-                    url: "${h.url('repo_switcher_data')}",
-                    data: {},
-                    dataType: 'json',
-                    type: 'GET',
-                    success: function(data) {
-                      cache[key] = data;
-                      query.callback({results: data.results});
-                    }
-                  });
-              }
-            }
-        });
-
-        $("#repo_switcher").on('select2-selecting', function(e){
-            e.preventDefault();
-            window.location = pyroutes.url('summary_home', {'repo_name': e.val});
-        });
-
-        ## Global mouse bindings ##
-
-        // general help "?"
-        Mousetrap.bind(['?'], function(e) {
-            $('#help_kb').modal({});
-        });
-
-        // / open the quick filter
-        Mousetrap.bind(['/'], function(e) {
-            $("#repo_switcher").select2("open");
-
-            // return false to prevent default browser behavior
-            // and stop event from bubbling
-            return false;
-        });
+            var repo_switcher_placeholder = '<i class="icon-database"></i>' + ${h.jshtml(_('Repositories'))} + ' <span class="caret"></span>';
+            $("#repo_switcher").select2({
+                placeholder: repo_switcher_placeholder,
+                dropdownAutoWidth: true,
+                sortResults: prefixFirstSort,
+                formatResult: format,
+                formatSelection: format,
+                formatNoMatches: function(term){
+                    return ${h.jshtml(_('No matches found'))};
+                },
+                containerCssClass: "repo-switcher",
+                dropdownCssClass: "repo-switcher-dropdown",
+                escapeMarkup: function(m){
+                    if (m == repo_switcher_placeholder)
+                        return repo_switcher_placeholder;
+                    return Select2.util.escapeMarkup(m);
+                },
+                query: function(query){
+                  var key = 'cache';
+                  var cached = cache[key] ;
+                  if(cached) {
+                    var data = {results: []};
+                    //filter results
+                    $.each(cached.results, function(){
+                        var section = this.text;
+                        var children = [];
+                        $.each(this.children, function(){
+                            if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                                children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj});
+                            }
+                        });
+                        if(children.length !== 0){
+                            data.results.push({'text': section, 'children': children});
+                        }
 
-        // ctrl/command+b, show the the main bar
-        Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
-            if($('#header-inner').hasClass('hover') && $('#content').hasClass('hover')){
-                $('#header-inner').removeClass('hover');
-                $('#content').removeClass('hover');
-            }
-            else{
-                $('#header-inner').addClass('hover');
-                $('#content').addClass('hover');
-            }
-            return false;
-        });
+                    });
+                    query.callback(data);
+                  }else{
+                      $.ajax({
+                        url: ${h.js(h.url('repo_switcher_data'))},
+                        data: {},
+                        dataType: 'json',
+                        type: 'GET',
+                        success: function(data) {
+                          cache[key] = data;
+                          query.callback({results: data.results});
+                        }
+                      });
+                  }
+                }
+            });
 
-        // general nav g + action
-        Mousetrap.bind(['g h'], function(e) {
-            window.location = pyroutes.url('home');
-        });
-        Mousetrap.bind(['g g'], function(e) {
-            window.location = pyroutes.url('gists', {'private':1});
-        });
-        Mousetrap.bind(['g G'], function(e) {
-            window.location = pyroutes.url('gists', {'public':1});
-        });
-        Mousetrap.bind(['n g'], function(e) {
-            window.location = pyroutes.url('new_gist');
+            $("#repo_switcher").on('select2-selecting', function(e){
+                e.preventDefault();
+                window.location = pyroutes.url('summary_home', {'repo_name': e.val});
+            });
+
+            $(document).on('shown.bs.dropdown', function(event) {
+                var dropdown = $(event.target);
+
+                dropdown.attr('aria-expanded', true);
+                dropdown.find('.dropdown-menu').attr('aria-hidden', false);
+            });
+
+            $(document).on('hidden.bs.dropdown', function(event) {
+                var dropdown = $(event.target);
+
+                dropdown.attr('aria-expanded', false);
+                dropdown.find('.dropdown-menu').attr('aria-hidden', true);
+            });
         });
-        Mousetrap.bind(['n r'], function(e) {
-            window.location = pyroutes.url('new_repo');
-        });
-
-        % if hasattr(c, 'repo_name') and hasattr(c, 'db_repo'):
-            // nav in repo context
-            Mousetrap.bind(['g s'], function(e) {
-                window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
-            });
-            Mousetrap.bind(['g c'], function(e) {
-                window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
-            });
-            Mousetrap.bind(['g F'], function(e) {
-                window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
-            });
-            Mousetrap.bind(['g f'], function(e) {
-                window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': ''});
-            });
-            Mousetrap.bind(['g o'], function(e) {
-                window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
-            });
-            Mousetrap.bind(['g O'], function(e) {
-                window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
-            });
-        % endif
-
     </script>
 </%def>
 
-%if 0:
-<div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
-    <div class="modal-dialog">
-      <div class="modal-content">
-        <div class="modal-header">
-          <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
-          <h4 class="modal-title">${_('Keyboard shortcuts')}</h4>
+<%def name="parent_child_navigation()">
+    <div class="pull-left">
+        <div class="parent-child-link"
+             data-ajax-url="${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
+             data-linktype="parent"
+             data-reponame="${c.repo_name}">
+            <i class="icon-left-open"></i><a href="#">${_('Parent rev.')}</a>
         </div>
-        <div class="modal-body">
-           <div class="row">
-              <div class="col-md-5">
-                <table class="keyboard-mappings">
-                    <tbody>
-                  <tr>
-                    <th></th>
-                    <th>${_('Site-wide shortcuts')}</th>
-                  </tr>
-                  <%
-                     elems = [
-                         ('/', 'Open quick search box'),
-                         ('ctrl/cmd+b', 'Show main settings bar'),
-                         ('g h', 'Goto home page'),
-                         ('g g', 'Goto my private gists page'),
-                         ('g G', 'Goto my public gists page'),
-                         ('n r', 'New repository page'),
-                         ('n g', 'New gist page'),
-                     ]
-                  %>
-                  %for key, desc in elems:
-                  <tr>
-                    <td class="keys">
-                      <span class="key">${key}</span>
-                    </td>
-                    <td>${desc}</td>
-                  </tr>
-                %endfor
-                </tbody>
-                  </table>
-              </div>
-              <div class="col-md-offset-5">
-                <table class="keyboard-mappings">
-                <tbody>
-                  <tr>
-                    <th></th>
-                    <th>${_('Repositories')}</th>
-                  </tr>
-                  <%
-                     elems = [
-                         ('g s', 'Goto summary page'),
-                         ('g c', 'Goto changelog page'),
-                         ('g f', 'Goto files page'),
-                         ('g F', 'Goto files page with file search activated'),
-                         ('g o', 'Goto repository settings'),
-                         ('g O', 'Goto repository permissions settings'),
-                     ]
-                  %>
-                  %for key, desc in elems:
-                  <tr>
-                    <td class="keys">
-                      <span class="key">${key}</span>
-                    </td>
-                    <td>${desc}</td>
-                  </tr>
-                %endfor
-                </tbody>
-            </table>
-              </div>
-            </div>
+    </div>
+
+    <div class="pull-right">
+        <div class="parent-child-link"
+             data-ajax-url="${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}"
+             data-linktype="child"
+             data-reponame="${c.repo_name}">
+            <a href="#">${_('Child rev.')}</a><i class="icon-right-open"></i>
         </div>
-        <div class="modal-footer">
-        </div>
-      </div><!-- /.modal-content -->
-    </div><!-- /.modal-dialog -->
-</div><!-- /.modal -->
-%endif
+    </div>
+
+    <script type="text/javascript">
+      $(document).ready(function(){
+          activate_parent_child_links();
+      });
+    </script>
+</%def>
--- a/kallithea/templates/base/default_perms_box.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/base/default_perms_box.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,66 +5,58 @@
 
 
 <%def name="default_perms_box(form_url)">
-${h.form(form_url, method='put')}
+${h.form(form_url)}
     <div class="form">
-        <!-- fields -->
-        <div class="fields">
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="inherit_default_permissions">${_('Inherit defaults')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="inherit_default_permissions">${_('Inherit defaults')}:</label>
+                <div>
                     ${h.checkbox('inherit_default_permissions',value=True)}
                     <span class="help-block">
-                    ${h.literal(_('Select to inherit global settings, IP whitelist and permissions from the %s.')
-                                % h.link_to('default permissions', url('admin_permissions')))}
+                        ${h.literal(_('Select to inherit global settings, IP whitelist and permissions from the %s.')
+                                    % h.link_to('default permissions', url('admin_permissions')))}
                     </span>
                 </div>
-             </div>
+            </div>
 
-             <div id="inherit_overlay">
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="create_repo_perm">${_('Create repositories')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div id="inherit_overlay">
+            <div class="form-group">
+                <label class="control-label" for="create_repo_perm">${_('Create repositories')}:</label>
+                <div>
                     ${h.checkbox('create_repo_perm',value=True)}
                     <span class="help-block">
-                    ${_('Select this option to allow repository creation for this user')}
+                        ${_('Select this option to allow repository creation for this user')}
                     </span>
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="create_user_group_perm">${_('Create user groups')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="create_user_group_perm">${_('Create user groups')}:</label>
+                <div>
                     ${h.checkbox('create_user_group_perm',value=True)}
                     <span class="help-block">
                         ${_('Select this option to allow user group creation for this user')}
                     </span>
                 </div>
-             </div>
+            </div>
 
-             <div class="field">
-                <div class="label label-checkbox">
-                    <label for="fork_repo_perm">${_('Fork repositories')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="fork_repo_perm">${_('Fork repositories')}:</label>
+                <div>
                     ${h.checkbox('fork_repo_perm',value=True)}
                     <span class="help-block">
                         ${_('Select this option to allow repository forking for this user')}
                     </span>
                 </div>
-             </div>
+            </div>
 
             </div>
-            <div class="buttons">
-              ${h.submit('save',_('Save'),class_="btn")}
-              ${h.reset('reset',_('Reset'),class_="btn")}
+
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('save',_('Save'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
 ${h.end_form()}
 
--- a/kallithea/templates/base/flash_msg.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/base/flash_msg.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,8 +1,9 @@
 <div class="flash_msg">
     <% messages = h.flash.pop_messages() %>
     % if messages:
+        <% alert_categories = {'warning': 'alert-warning', 'notice': 'alert-info', 'error': 'alert-danger', 'success': 'alert-success'} %>
         % for message in messages:
-            <div class="alert alert-dismissable alert-${message.category}">
+            <div class="alert alert-dismissable ${alert_categories[message.category]}" role="alert">
               <button type="button" class="close" data-dismiss="alert" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
               ${message}
             </div>
--- a/kallithea/templates/base/perms_summary.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/base/perms_summary.html	Sun Mar 31 21:28:56 2019 +0200
@@ -4,38 +4,39 @@
 ##    ${p.perms_summary(c.perm_user.permissions)}
 
 <%def name="perms_summary(permissions, show_all=False, actions=True)">
-<div id="perms" class="table">
+<div id="perms">
      %for section in sorted(permissions.keys()):
         <div class="perms_section_head">
-            ${section.replace("_"," ").capitalize()}
+            <h4>${section.replace("_"," ").capitalize()}</h4>
             %if section != 'global':
-                <div style="float: right">
+              <div class="pull-right checkbox">
                 ${_('Show')}:
-                ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('None')}</span></label>
-                ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('Read')}</span></label>
-                ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('Write')}</span></label>
-                ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('Admin')}</span></label>
-                </div>
+                <label>${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, **{'data-section':section, 'data-perm_type':'none'})}<span class="label label-none">${_('None')}</span></label>
+                <label>${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, **{'data-section':section, 'data-perm_type':'read'})}<span class="label label-read">${_('Read')}</span></label>
+                <label>${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, **{'data-section':section, 'data-perm_type':'write'})}<span class="label label-write">${_('Write')}</span></label>
+                <label>${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, **{'data-section':section, 'data-perm_type':'admin'})}<span class="label label-admin">${_('Admin')}</span></label>
+              </div>
             %endif
         </div>
         %if not permissions[section]:
-            <span class="empty_data">${_('No permissions defined yet')}</span>
+            <span class="text-muted">${_('No permissions defined yet')}</span>
         %else:
-        <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
-         <table id="tbl_list_${section}">
+        <div id='tbl_list_wrap_${section}'>
+         <table id="tbl_list_${section}" class="table">
           ## global permission box
           %if section == 'global':
               <thead>
-                  <tr>
-                  <th colspan="2" class="left">${_('Permission')}</th>
+                <tr>
+                  <th class="left col-xs-9">${_('Permission')}</th>
                   %if actions:
-                  <th class="left">${_('Edit Permission')}</th>
+                  <th class="left col-xs-3">${_('Edit Permission')}</th>
                   %endif
+                </tr>
               </thead>
               <tbody>
               %for k in permissions[section]:
                   <tr>
-                      <td colspan="2">
+                      <td>
                           ${h.get_permission_name(k)}
                       </td>
                       %if actions:
@@ -49,12 +50,13 @@
           %else:
              ## none/read/write/admin permissions on groups/repos etc
               <thead>
-                  <tr>
-                  <th class="left">${_('Name')}</th>
-                  <th class="left">${_('Permission')}</th>
+                <tr>
+                  <th class="left col-xs-7">${_('Name')}</th>
+                  <th class="left col-xs-2">${_('Permission')}</th>
                   %if actions:
-                  <th class="left">${_('Edit Permission')}</th>
+                  <th class="left col-xs-3">${_('Edit Permission')}</th>
                   %endif
+                </tr>
               </thead>
               <tbody class="section_${section}">
               %for k, section_perm in sorted(permissions[section].items(), key=lambda s: {'none':0, 'read':1,'write':2,'admin':3}.get(s[1].split('.')[-1])):
@@ -71,7 +73,7 @@
                           %endif
                       </td>
                       <td>
-                           <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
+                           <span class="label label-${section_perm.split('.')[-1]}">${section_perm}</span>
                       </td>
                       %if actions:
                       <td>
@@ -87,7 +89,7 @@
                   </tr>
                   %endif
               %endfor
-              <tr id="empty_${section}" style="display: none"><td colspan="6">${_('No permission defined')}</td></tr>
+              <tr id="empty_${section}" style="display: none"><td colspan="${3 if actions else 2}">${_('No permission defined')}</td></tr>
               </tbody>
           %endif
          </table>
@@ -107,10 +109,10 @@
             }
         }
         var update_show = function($checkbox){
-            var section = $checkbox.attr('section');
+            var section = $checkbox.data('section');
 
             var elems = $('.filter_' + section).each(function(el){
-                var perm_type = $checkbox.attr('perm_type');
+                var perm_type = $checkbox.data('perm_type');
                 var checked = $checkbox.prop('checked');
                 if(checked){
                     $('.'+section+'_'+perm_type).show();
--- a/kallithea/templates/base/root.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/base/root.html	Sun Mar 31 21:28:56 2019 +0200
@@ -4,122 +4,124 @@
     <head>
         <title><%block name="title"/><%block name="branding_title"/></title>
         <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+        <meta http-equiv="X-UA-Compatible" content="IE=10"/>
         <meta name="robots" content="index, nofollow"/>
-        <link rel="icon" href="${h.url('/images/favicon.ico')}" type="image/png" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <link rel="shortcut icon" href="${h.url('/images/favicon.ico')}" type="image/x-icon" />
+        <link rel="icon" type="image/png" href="${h.url('/images/favicon-32x32.png')}" sizes="32x32">
+        <link rel="icon" type="image/png" href="${h.url('/images/favicon-16x16.png')}" sizes="16x16">
+        <link rel="apple-touch-icon" sizes="180x180" href="${h.url('/images/apple-touch-icon.png')}">
+        <link rel="manifest" href="${h.url('/images/manifest.json')}">
+        <link rel="mask-icon" href="${h.url('/images/safari-pinned-tab.svg')}" color="#b1d579">
+        <meta name="msapplication-config" content="${h.url('/images/browserconfig.xml')}">
+        <meta name="theme-color" content="#ffffff">
 
         ## CSS ###
-        <link rel="stylesheet" type="text/css" href="${h.url('/js/select2/select2.css', ver=c.kallithea_version)}"/>
-        <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css', ver=c.kallithea_version)}"/>
         <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.kallithea_version)}" media="screen"/>
-        <link rel="stylesheet" type="text/css" href="${h.url('/css/contextbar.css', ver=c.kallithea_version)}" media="screen"/>
-        <link rel="stylesheet" type="text/css" href="${h.url('/fontello/css/kallithea.css', ver=c.kallithea_version)}">
         <%block name="css_extra"/>
 
         ## JAVASCRIPT ##
         <script type="text/javascript">
             ## JS translations map
             var TRANSLATION_MAP = {
-                'Add Another Comment':'${_("Add Another Comment")}',
-                'Stop following this repository':"${_('Stop following this repository')}",
-                'Start following this repository':"${_('Start following this repository')}",
-                'Group':"${_('Group')}",
-                'members':"${_('members')}",
-                'Loading ...':"${_('Loading ...')}",
-                'loading ...':"${_('loading ...')}",
-                'Search truncated': "${_('Search truncated')}",
-                'No matching files': "${_('No matching files')}",
-                'Open New Pull Request from {0}': "${_('Open New Pull Request from {0}')}",
-                'Open New Pull Request for {0} &rarr; {1}': "${h.literal(_('Open New Pull Request for {0} &rarr; {1}'))}",
-                'Show Selected Changesets {0} &rarr; {1}': "${h.literal(_('Show Selected Changesets {0} &rarr; {1}'))}",
-                'Selection Link': "${_('Selection Link')}",
-                'Collapse Diff': "${_('Collapse Diff')}",
-                'Expand Diff': "${_('Expand Diff')}",
-                'Failed to revoke permission': "${_('Failed to revoke permission')}",
-                'Confirm to revoke permission for {0}: {1} ?': "${_('Confirm to revoke permission for {0}: {1} ?')}",
-                'enabled': "${_('enabled')}",
-                'disabled': "${_('disabled')}",
-                'Select changeset': "${_('Select changeset')}",
-                'Specify changeset': "${_('Specify changeset')}",
-                'MSG_SORTASC': "${_('Click to sort ascending')}",
-                'MSG_SORTDESC': "${_('Click to sort descending')}",
-                'MSG_EMPTY': "${_('No records found.')}",
-                'MSG_ERROR': "${_('Data error.')}",
-                'MSG_LOADING': "${_('Loading...')}"
+                'Cancel': ${h.jshtml(_("Cancel"))},
+                'Retry': ${h.jshtml(_("Retry"))},
+                'Submitting ...': ${h.jshtml(_("Submitting ..."))},
+                'Unable to post': ${h.jshtml(_("Unable to post"))},
+                'Add Another Comment': ${h.jshtml(_("Add Another Comment"))},
+                'Stop following this repository': ${h.jshtml(_('Stop following this repository'))},
+                'Start following this repository': ${h.jshtml(_('Start following this repository'))},
+                'Group': ${h.jshtml(_('Group'))},
+                'Loading ...': ${h.jshtml(_('Loading ...'))},
+                'loading ...': ${h.jshtml(_('loading ...'))},
+                'Search truncated': ${h.jshtml(_('Search truncated'))},
+                'No matching files': ${h.jshtml(_('No matching files'))},
+                'Open New Pull Request from {0}': ${h.jshtml(_('Open New Pull Request from {0}'))},
+                'Open New Pull Request for {0} &rarr; {1}': ${h.js(_('Open New Pull Request for {0} &rarr; {1}'))},
+                'Show Selected Changesets {0} &rarr; {1}': ${h.js(_('Show Selected Changesets {0} &rarr; {1}'))},
+                'Selection Link': ${h.jshtml(_('Selection Link'))},
+                'Collapse Diff': ${h.jshtml(_('Collapse Diff'))},
+                'Expand Diff': ${h.jshtml(_('Expand Diff'))},
+                'No revisions': ${h.jshtml(_('No revisions'))},
+                'Type name of user or member to grant permission': ${h.jshtml(_('Type name of user or member to grant permission'))},
+                'Failed to revoke permission': ${h.jshtml(_('Failed to revoke permission'))},
+                'Confirm to revoke permission for {0}: {1} ?': ${h.jshtml(_('Confirm to revoke permission for {0}: {1} ?'))},
+                'Enabled': ${h.jshtml(_('Enabled'))},
+                'Disabled': ${h.jshtml(_('Disabled'))},
+                'Select changeset': ${h.jshtml(_('Select changeset'))},
+                'Specify changeset': ${h.jshtml(_('Specify changeset'))},
+                'MSG_SORTASC': ${h.jshtml(_('Click to sort ascending'))},
+                'MSG_SORTDESC': ${h.jshtml(_('Click to sort descending'))},
+                'MSG_EMPTY': ${h.jshtml(_('No records found.'))},
+                'MSG_ERROR': ${h.jshtml(_('Data error.'))},
+                'MSG_LOADING': ${h.jshtml(_('Loading...'))}
             };
             var _TM = TRANSLATION_MAP;
 
-            var TOGGLE_FOLLOW_URL  = "${h.url('toggle_following')}";
+            var TOGGLE_FOLLOW_URL  = ${h.js(h.url('toggle_following'))};
 
             var REPO_NAME = "";
             %if hasattr(c, 'repo_name'):
-                var REPO_NAME = "${c.repo_name}";
+                var REPO_NAME = ${h.js(c.repo_name)};
             %endif
 
-            var _authentication_token = "${h.authentication_token()}";
+            var _authentication_token = ${h.js(h.authentication_token())};
         </script>
-        <script type="text/javascript" src="${h.url('/js/yui.2.9.js', ver=c.kallithea_version)}"></script>
-        <script type="text/javascript" src="${h.url('/js/jquery-1.11.1.min.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/jquery.min.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/jquery.dataTables.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/dataTables.bootstrap.js', ver=c.kallithea_version)}"></script>
         <script type="text/javascript" src="${h.url('/js/bootstrap.js', ver=c.kallithea_version)}"></script>
-        <script type="text/javascript" src="${h.url('/js/select2/select2.js', ver=c.kallithea_version)}"></script>
-        <script type="text/javascript" src="${h.url('/js/mousetrap.js', ver=c.kallithea_version)}"></script>
-        <script type="text/javascript" src="${h.url('/js/yui.flot.js', ver=c.kallithea_version)}"></script>
-        <script type="text/javascript" src="${h.url('/js/native.history.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/select2.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/jquery.caret.min.js', ver=c.kallithea_version)}"></script>
+        <script type="text/javascript" src="${h.url('/js/jquery.atwho.min.js', ver=c.kallithea_version)}"></script>
         <script type="text/javascript" src="${h.url('/js/base.js', ver=c.kallithea_version)}"></script>
         ## EXTRA FOR JS
         <%block name="js_extra"/>
         <script type="text/javascript">
-            (function(window,undefined){
-                var History = window.History; // Note: We are using a capital H instead of a lower h
-                if ( !History.enabled ) {
-                     // History.js is disabled for this browser.
-                     // This is because we can optionally choose to support HTML4 browsers or not.
-                    return false;
-                }
-            })(window);
-
             $(document).ready(function(){
               tooltip_activate();
               show_more_event();
-              show_changeset_tooltip();
               // routes registration
-              pyroutes.register('home', "${h.url('home')}", []);
-              pyroutes.register('new_gist', "${h.url('new_gist')}", []);
-              pyroutes.register('gists', "${h.url('gists')}", []);
-              pyroutes.register('new_repo', "${h.url('new_repo')}", []);
+              pyroutes.register('home', ${h.js(h.url('home'))}, []);
+              pyroutes.register('new_gist', ${h.js(h.url('new_gist'))}, []);
+              pyroutes.register('gists', ${h.js(h.url('gists'))}, []);
+              pyroutes.register('new_repo', ${h.js(h.url('new_repo'))}, []);
 
-              pyroutes.register('summary_home', "${h.url('summary_home', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('changelog_home', "${h.url('changelog_home', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('files_home', "${h.url('files_home', repo_name='%(repo_name)s',revision='%(revision)s',f_path='%(f_path)s')}", ['repo_name', 'revision', 'f_path']);
-              pyroutes.register('edit_repo', "${h.url('edit_repo', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('edit_repo_perms', "${h.url('edit_repo_perms', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('pullrequest_home', "${h.url('pullrequest_home', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('summary_home', ${h.js(h.url('summary_home', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('changelog_home', ${h.js(h.url('changelog_home', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('files_home', ${h.js(h.url('files_home', repo_name='%(repo_name)s',revision='%(revision)s',f_path='%(f_path)s'))}, ['repo_name', 'revision', 'f_path']);
+              pyroutes.register('edit_repo', ${h.js(h.url('edit_repo', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('edit_repo_perms', ${h.js(h.url('edit_repo_perms', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('pullrequest_home', ${h.js(h.url('pullrequest_home', repo_name='%(repo_name)s'))}, ['repo_name']);
 
-              pyroutes.register('toggle_following', "${h.url('toggle_following')}");
-              pyroutes.register('changeset_info', "${h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
-              pyroutes.register('repo_size', "${h.url('repo_size', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('changeset_comment_preview', "${h.url('changeset_comment_preview', repo_name='%(repo_name)s')}", ['repo_name']);
-              pyroutes.register('repo_refs_data', "${h.url('repo_refs_data', repo_name='%(repo_name)s')}", ['repo_name']);
+              pyroutes.register('toggle_following', ${h.js(h.url('toggle_following'))});
+              pyroutes.register('changeset_info', ${h.js(h.url('changeset_info', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
+              pyroutes.register('changeset_home', ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
+              pyroutes.register('repo_size', ${h.js(h.url('repo_size', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('repo_refs_data', ${h.js(h.url('repo_refs_data', repo_name='%(repo_name)s'))}, ['repo_name']);
+              pyroutes.register('users_and_groups_data', ${h.js(h.url('users_and_groups_data'))}, []);
              });
         </script>
 
         <%block name="head_extra"/>
     </head>
-    <body id="body">
-      <div id="header">
-        <div id="header-inner" class="title">
-          <div id="logo">
-            <a href="${h.url('home')}" style="display: block;">
-              <div class="header">
-                <img src="${h.url('/images/kallithea-logo.svg')}" onerror="this.onerror='';this.src='${h.url('/images/kallithea-logo.png')}'" alt="Kallithea"/>
-              </div>
-              %if c.site_name:
-                <div class="branding">${c.site_name}</div>
-              %endif
+    <body>
+      <nav class="navbar navbar-inverse mainmenu">
+          <div class="navbar-header" id="logo">
+            <a class="navbar-brand" href="${h.url('home')}">
+              <span class="branding">${c.site_name}</span>
             </a>
+            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
+              <span class="sr-only">Toggle navigation</span>
+              <span class="icon-bar"></span>
+              <span class="icon-bar"></span>
+              <span class="icon-bar"></span>
+            </button>
           </div>
-          <%block name="header_menu"/>
-        </div>
-      </div>
+          <div id="navbar" class="navbar-collapse collapse">
+            <%block name="header_menu"/>
+          </div>
+      </nav>
 
       ${next.body()}
 
--- a/kallithea/templates/bookmarks/bookmarks.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%block name="title">
-    ${_('%s Bookmarks') % c.repo_name}
-</%block>
-
-<%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter_bookmarks" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
-    ${_('Bookmarks')}
-</%def>
-
-<%block name="header_menu">
-    ${self.menu('repositories')}
-</%block>
-
-<%def name="main()">
-${self.repo_context_bar('switch-to')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    %if c.repo_bookmarks:
-    <div class="info_box" id="compare_bookmarks" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-small">${_('Compare Bookmarks')}</a></div>
-    %endif
-    <div class="table">
-        <%include file='bookmarks_data.html'/>
-    </div>
-</div>
-
-<script type="text/javascript">
-$('#compare_bookmarks').click(function(e){
-    e.preventDefault();
-    var org = $('input[name=compare_org]:checked')[0];
-    var other = $('input[name=compare_other]:checked')[0];
-
-    if(org && other){
-        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='book',org_ref_name='__ORG__',other_ref_type='book',other_ref_name='__OTHER__')}";
-        var u = compare_url.replace('__ORG__',org.value)
-                           .replace('__OTHER__',other.value);
-        window.location=u;
-    }
-});
-
-// main table sorting
-var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true,
-        sortOptions: { sortFunction: nameSort }},
-    {key:"date",label:"${_('Date')}",sortable:true,
-        sortOptions: { sortFunction: dateSort }},
-    {key:"author",label:"${_('Author')}",sortable:true},
-    {key:"revision",label:"${_('Revision')}",sortable:true,
-        sortOptions: { sortFunction: revisionSort }},
-    {key:"compare",label:"${_('Compare')}",sortable:false}
-];
-
-var myDataSource = new YAHOO.util.DataSource(YUD.get("bookmarks_data"));
-
-myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-
-myDataSource.responseSchema = {
-    fields: [
-        {key:"raw_name"},
-        {key:"name"},
-        {key:"raw_date"},
-        {key:"date"},
-        {key:"author"},
-        {key:"last_rev_raw"},
-        {key:"revision"},
-        {key:"compare"}
-    ]
-};
-
-var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
-    {
-         sortedBy:{key:"name",dir:"asc"},
-         MSG_SORTASC:"${_('Click to sort ascending')}",
-         MSG_SORTDESC:"${_('Click to sort descending')}",
-         MSG_EMPTY:"${_('No records found.')}",
-         MSG_ERROR:"${_('Data error.')}",
-         MSG_LOADING:"${_('Loading...')}"
-    }
-);
-myDataTable.subscribe('postRenderEvent',function(oArgs) {
-    tooltip_activate();
-    var func = function(node){
-        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
-    }
-    q_filter('q_filter_bookmarks',$('div.table tr td .logbooks .booktag a'),func);
-});
-
-</script>
-
-</%def>
--- a/kallithea/templates/bookmarks/bookmarks_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-%if c.repo_bookmarks:
-   <div id="table_wrap" class="yui-skin-sam">
-    <table id="bookmarks_data">
-    <thead>
-        <tr>
-            <th class="left">Raw name</th> ##notranslation
-            <th class="left">${_('Name')}</th>
-            <th class="left">Raw date</th> ##notranslation
-            <th class="left">${_('Date')}</th>
-            <th class="left">${_('Author')}</th>
-            <th class="left">Raw rev</th> ##notranslation
-            <th class="left">${_('Revision')}</th>
-            <th class="left">${_('Compare')}</th>
-        </tr>
-    </thead>
-    %for cnt,book in enumerate(c.repo_bookmarks.items()):
-        <tr class="parity${cnt%2}">
-            <td>${book[0]}</td>
-            <td>
-                <span class="logbooks">
-                    <span class="booktag">${h.link_to(book[0],
-                    h.url('changeset_home',repo_name=c.repo_name,revision=book[1].raw_id))}</span>
-                </span>
-            </td>
-            <td>${book[1]._timestamp}</td>
-            <td><span class="tooltip" title="${h.age(book[1].date)}">${h.fmt_date(book[1].date)}</span></td>
-            <td title="${book[1].author}">${h.person(book[1].author)}</td>
-            <td>${book[1].revision}</td>
-            <td>
-              <div>
-                  <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=book[1].raw_id)}" class="revision-link">${h.show_id(book[1])}</a>
-              </div>
-            </td>
-            <td>
-                <input class="branch-compare" type="radio" name="compare_org" value="${book[0]}"/>
-                <input class="branch-compare" type="radio" name="compare_other" value="${book[0]}"/>
-            </td>
-        </tr>
-    %endfor
-    </table>
-    </div>
-%else:
-    ${_('There are no bookmarks yet')}
-%endif
--- a/kallithea/templates/branches/branches.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%block name="title">
-    ${_('%s Branches') % c.repo_name}
-</%block>
-
-<%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter_branches" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
-    ${_('Branches')}
-</%def>
-
-<%block name="header_menu">
-    ${self.menu('repositories')}
-</%block>
-
-<%def name="main()">
-${self.repo_context_bar('switch-to')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    %if c.repo_branches:
-    <div class="info_box" id="compare_branches" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-small">${_('Compare Branches')}</a></div>
-    %endif
-    <div class="table">
-        <%include file='branches_data.html'/>
-    </div>
-</div>
-
-<script type="text/javascript">
-$('#compare_branches').click(function(e){
-    e.preventDefault();
-    var org = $('input[name=compare_org]:checked')[0];
-    var other = $('input[name=compare_other]:checked')[0];
-
-    if(org && other){
-        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref_name='__ORG__',other_ref_type='branch',other_ref_name='__OTHER__')}";
-        var u = compare_url.replace('__ORG__',org.value)
-                           .replace('__OTHER__',other.value);
-        window.location=u;
-    }
-});
-
-// main table sorting
-var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true,
-        sortOptions: { sortFunction: nameSort }},
-    {key:"date",label:"${_('Date')}",sortable:true,
-        sortOptions: { sortFunction: dateSort }},
-    {key:"author",label:"${_('Author')}",sortable:true},
-    {key:"revision",label:"${_('Revision')}",sortable:true,
-        sortOptions: { sortFunction: revisionSort }},
-    {key:"compare",label:"${_('Compare')}",sortable:false}
-];
-
-var myDataSource = new YAHOO.util.DataSource(YUD.get("branches_data"));
-
-myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-
-myDataSource.responseSchema = {
-    fields: [
-        {key:"raw_name"},
-        {key:"name"},
-        {key:"raw_date"},
-        {key:"date"},
-        {key:"author"},
-        {key:"last_rev_raw"},
-        {key:"revision"},
-        {key:"compare"}
-    ]
-};
-
-var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
-    {
-         sortedBy:{key:"name",dir:"asc"},
-         MSG_SORTASC:"${_('Click to sort ascending')}",
-         MSG_SORTDESC:"${_('Click to sort descending')}",
-         MSG_EMPTY:"${_('No records found.')}",
-         MSG_ERROR:"${_('Data error.')}",
-         MSG_LOADING:"${_('Loading...')}"
-    }
-);
-myDataTable.subscribe('postRenderEvent',function(oArgs) {
-    tooltip_activate();
-    var func = function(node){
-        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
-    }
-    q_filter('q_filter_branches',$('div.table tr td .logtags .branchtag a'),func);
-});
-
-</script>
-
-</%def>
--- a/kallithea/templates/branches/branches_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-%if c.repo_branches:
-   <div id="table_wrap" class="yui-skin-sam">
-    <table id="branches_data">
-      <thead>
-        <tr>
-            <th class="left">Raw name</th> ##notranslation
-            <th class="left">${_('Name')}</th>
-            <th class="left">Raw date</th> ##notranslation
-            <th class="left">${_('Date')}</th>
-            <th class="left">${_('Author')}</th>
-            <th class="left">Raw rev</th> ##notranslation
-            <th class="left">${_('Revision')}</th>
-            <th class="left">${_('Compare')}</th>
-        </tr>
-      </thead>
-        %for cnt,branch in enumerate(c.repo_branches.items()):
-        <tr class="parity${cnt%2}">
-            <td>${branch[0]}</td>
-            <td>
-                <span class="logtags">
-                    <span class="branchtag">
-                    ${h.link_to(branch[0],h.url('changelog_home',repo_name=c.repo_name,branch=branch[0]))}
-                    </span>
-                </span>
-            </td>
-            <td>${branch[1]._timestamp}</td>
-            <td><span class="tooltip" title="${h.age(branch[1].date)}">${h.fmt_date(branch[1].date)}</span></td>
-            <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
-            <td>${branch[1].revision}</td>
-            <td>
-                <div>
-                    <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id)}" class="revision-link">${h.show_id(branch[1])}</a>
-                </div>
-            </td>
-            <td>
-                <input class="branch-compare" type="radio" name="compare_org" value="${branch[0]}"/>
-                <input class="branch-compare" type="radio" name="compare_other" value="${branch[0]}"/>
-            </td>
-        </tr>
-        %endfor
-        ## closed branches if any
-        % if hasattr(c,'repo_closed_branches') and c.repo_closed_branches:
-          %for cnt,branch in enumerate(c.repo_closed_branches.items()):
-          <tr class="parity${cnt%2}">
-              <td>${branch[0]}</td>
-              <td>
-                  <span class="logtags">
-                      <span class="branchtag">
-                      ${h.link_to(branch[0]+' [closed]',h.url('changelog_home',repo_name=c.repo_name,branch=branch[0]))}
-                      </span>
-                  </span>
-              </td>
-              <td>${branch[1]._timestamp}</td>
-              <td><span class="tooltip" title="${h.age(branch[1].date)}">${h.fmt_date(branch[1].date)}</span></td>
-              <td title="${branch[1].author}">${h.person(branch[1].author)}</td>
-              <td>${branch[1].revision}</td>
-              <td>
-                <div>
-                    <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=branch[1].raw_id)}" class="revision-link">${h.show_id(branch[1])}</a>
-                </div>
-              </td>
-              <td></td>
-          </tr>
-          %endfor
-        %endif
-    </table>
-    </div>
-%else:
-    ${_('There are no branches yet')}
-%endif
--- a/kallithea/templates/changelog/changelog.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changelog/changelog.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,6 +2,8 @@
 
 <%inherit file="/base/base.html"/>
 
+<%namespace name="changelog_table" file="changelog_table.html"/>
+
 <%block name="title">
     ${_('%s Changelog') % c.repo_name}
     %if c.changelog_for_path:
@@ -27,149 +29,66 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog', c.first_revision.raw_id if c.first_revision else None)}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <div class="table">
-        % if c.pagination:
-            <div>
-                <div style="overflow:auto; ${'display:none' if c.changelog_for_path else ''}">
-                    <div class="container_header">
-                        <div style="float:left; margin-left:20px;">
-                          ${h.form(h.url.current(),method='get',style="display:inline")}
-                            ${h.submit('set',_('Show'),class_="btn btn-small")}
-                            ${h.text('size',size=3,value=c.size)}
+    <div class="panel-body changelog-panel">
+        %if c.cs_pagination:
+                <div class="changelog-heading clearfix" style="${'display:none' if c.changelog_for_path else ''}">
+                    <div class="pull-left">
+                        ${h.form(h.url.current(),method='get',class_="form-inline")}
+                            ${h.submit(None,_('Show'),id='set_size_submit',class_="btn btn-default btn-sm")}
+                            ${h.text('size',size=3,value=c.size,class_='form-control')}
                             ${_('revisions')}
                             %if c.branch_name:
-                            ${h.hidden('branch', c.branch_name)}
+                                ${h.hidden('branch', c.branch_name)}
                             %endif
-                          ${h.end_form()}
-                          <a href="#" class="btn btn-small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
-                        </div>
-                        <div style="float: right; margin: 0px 0px 0px 4px">
-                            <a href="#" class="btn btn-small" id="rev_range_container" style="display:none"></a>
-                            %if c.revision:
-                                <a class="btn btn-small" href="${h.url('changelog_home', repo_name=c.repo_name)}">
-                                    ${_('Go to tip of repository')}
-                                </a>
-                            %endif
-                            %if c.db_repo.fork:
-                                <a id="compare_fork"
-                                   title="${_('Compare fork with %s' % c.db_repo.fork.repo_name)}"
-                                   href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}"
-                                   class="btn btn-small"><i class="icon-git-compare"></i> ${_('Compare fork with parent repository (%s)' % c.db_repo.fork.repo_name)}</a>
-                            %endif
-                            ## text and href of open_new_pr is controlled from javascript
-                            <a id="open_new_pr" class="btn btn-small"></a>
-                            ${_("Branch filter:")} ${h.select('branch_filter',c.branch_name,c.branch_filters)}
-                        </div>
+                            <a href="#" class="btn btn-default btn-sm" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
+                        ${h.end_form()}
+                    </div>
+                    <div class="pull-right">
+                        <a href="#" class="btn btn-default btn-sm" id="rev_range_container" style="display:none"></a>
+                        %if c.revision:
+                            <a class="btn btn-default btn-sm" href="${h.url('changelog_home', repo_name=c.repo_name)}">
+                                ${_('Go to tip of repository')}
+                            </a>
+                        %endif
+                        %if c.db_repo.fork:
+                            <a id="compare_fork"
+                               title="${_('Compare fork with %s' % c.db_repo.fork.repo_name)}"
+                               href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}"
+                               class="btn btn-default btn-sm"><i class="icon-git-compare"></i>${_('Compare fork with parent repository (%s)' % c.db_repo.fork.repo_name)}</a>
+                        %endif
+                        ## text and href of open_new_pr is controlled from javascript
+                        <a id="open_new_pr" class="btn btn-default btn-sm"></a>
+                        ${_("Branch filter:")} ${h.select('branch_filter',c.branch_name,c.branch_filters)}
                     </div>
                 </div>
 
-                <div id="changelog" style="clear:both">
-
                 <div id="graph_nodes">
                     <canvas id="graph_canvas" style="width:0"></canvas>
                 </div>
+
                 <div id="graph_content" style="${'margin: 0px' if c.changelog_for_path else ''}">
-
-                <table id="changesets">
-                <tbody>
-                %for cnt,cs in enumerate(c.pagination):
-                    <tr id="chg_${cnt+1}" class="container ${'mergerow' if len(cs.parents) > 1 else ''}">
-                        <td class="checkbox">
-                            %if c.changelog_for_path:
-                                ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
-                            %else:
-                                ${h.checkbox(cs.raw_id,class_="changeset_range")}
-                            %endif
-                        <td class="status">
-                          %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') % (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') % c.statuses.get(cs.raw_id)[1]}" href="${c.comments[cs.raw_id][0].url()}">
-                                  <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
-                              </a>
-                            %endif
-                            </div>
-                          %endif
-                        </td>
-                        <td class="author">
-                            ${h.gravatar(h.email_or_none(cs.author), size=16)}
-                            <span title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</span>
-                        </td>
-                        <td class="hash" style="width:${len(h.show_id(cs))*6.5}px">
-                            <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
-                                <span class="changeset_hash">${h.show_id(cs)}</span>
-                            </a>
-                        </td>
-                        <td class="date">
-                            <div class="date tooltip" title="${h.fmt_date(cs.date)}">${h.age(cs.date,True)}</div>
-                        </td>
-                        <td class="expand_commit" commit_id="${cs.raw_id}" title="${_('Expand commit message')}">
-                            <i class="icon-align-left" style="color:#999"></i>
-                        </td>
-                        <td class="mid">
-                            <div class="log-container">
-                                <div class="message" id="C-${cs.raw_id}">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
-                                <div class="extra-container">
-                                    %if c.comments.get(cs.raw_id):
-                                        <div class="comments-container">
-                                            <div class="comments-cnt" title="${_('Changeset has comments')}">
-                                                <a href="${c.comments[cs.raw_id][0].url()}">
-                                                    ${len(c.comments[cs.raw_id])}
-                                                    <i class="icon-comment-discussion"></i>
-                                                </a>
-                                            </div>
-                                        </div>
-                                    %endif
-                                    %if h.is_hg(c.db_repo_scm_instance):
-                                        %for book in cs.bookmarks:
-                                            <div class="booktag" title="${_('Bookmark %s') % book}">
-                                                ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-                                            </div>
-                                        %endfor
-                                    %endif
-                                    %for tag in cs.tags:
-                                        <div class="tagtag" title="${_('Tag %s') % tag}">
-                                            ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-                                        </div>
-                                    %endfor
-                                    %if (not c.branch_name) and cs.branch:
-                                        <div class="branchtag" title="${_('Branch %s' % cs.branch)}">
-                                            ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
-                                        </div>
-                                    %endif
-                                </div>
-                            </div>
-                        </td>
-                    </tr>
-                %endfor
-                </tbody>
-                </table>
-
-                <input type="checkbox" id="singlerange" style="display:none"/>
-
+                  ${changelog_table.changelog(c.repo_name, c.cs_pagination, c.cs_statuses, c.cs_comments,
+                                              show_checkbox=not c.changelog_for_path,
+                                              show_branch=not c.branch_name,
+                                              resize_js='graph.render(jsdata)')}
+                  <input type="checkbox" id="singlerange" style="display:none"/>
                 </div>
 
-                <div class="pagination-wh pagination-left">
-                    ${c.pagination.pager('$link_previous ~2~ $link_next')}
-                </div>
-            </div>
-        </div>
+                ${c.cs_pagination.pager()}
 
         <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
         <script type="text/javascript">
+            var jsdata = ${h.js(c.jsdata)};
+            var graph = new BranchRenderer('graph_canvas', 'graph_content', 'chg_');
+
             $(document).ready(function(){
                 var $checkboxes = $('.changeset_range');
 
-                pyroutes.register('changeset_home', "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}", ['repo_name', 'revision']);
+                pyroutes.register('changeset_home', ${h.js(h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s'))}, ['repo_name', 'revision']);
 
                 var checkbox_checker = function(e) {
                     var $checked_checkboxes = $checkboxes.filter(':checked');
@@ -186,19 +105,19 @@
                         if ($checked_checkboxes.length > 1 || singlerange) {
                             var rev_start = $checked_checkboxes.last().prop('name');
                             $('#rev_range_container').prop('href',
-                                pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}',
+                                pyroutes.url('changeset_home', {'repo_name': ${h.js(c.repo_name)},
                                                                 'revision': rev_start + '...' + rev_end}));
                             $('#rev_range_container').html(
                                  _TM['Show Selected Changesets {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
                             $('#rev_range_container').show();
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
+                                                                        {'repo_name': ${h.js(c.repo_name)},
                                                                          'rev_start': rev_start,
                                                                          'rev_end': rev_end}));
                             $('#open_new_pr').html(_TM['Open New Pull Request for {0} &rarr; {1}'].format(rev_start.substr(0, 12), rev_end.substr(0, 12)));
                         } else {
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
+                                                                        {'repo_name': ${h.js(c.repo_name)},
                                                                          'rev_end': rev_end}));
                             $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(rev_end.substr(0, 12)));
                         }
@@ -232,14 +151,14 @@
                         $('#rev_range_clear').hide();
                         %if c.revision:
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
-                                                                         'rev_end':'${c.first_revision.raw_id}'}));
-                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format('${c.revision}'));
+                                                                        {'repo_name': ${h.js(c.repo_name)},
+                                                                         'rev_end':${h.js(c.first_revision.raw_id)}}));
+                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.revision)}));
                         %else:
                             $('#open_new_pr').prop('href', pyroutes.url('pullrequest_home',
-                                                                        {'repo_name': '${c.repo_name}',
-                                                                        'branch':'${c.first_revision.branch}'}));
-                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format('${c.first_revision.branch}'));
+                                                                        {'repo_name': ${h.js(c.repo_name)},
+                                                                        'branch':${h.js(c.first_revision.branch)}}));
+                            $('#open_new_pr').html(_TM['Open New Pull Request from {0}'].format(${h.jshtml(c.first_revision.branch)}));
                         %endif
                         $('#compare_fork').show();
                         $checkboxes.closest('tr').removeClass('out-of-range');
@@ -248,19 +167,19 @@
                 checkbox_checker();
                 $checkboxes.click(function() {
                     checkbox_checker();
-                    r.render(jsdata,100);
+                    graph.render(jsdata);
                 });
                 $('#singlerange').click(checkbox_checker);
 
                 $('#rev_range_clear').click(function(e){
                     $checkboxes.prop('checked', false);
                     checkbox_checker();
-                    r.render(jsdata,100);
+                    graph.render(jsdata);
                 });
 
                 var $msgs = $('.message');
                 // get first element height
-                var el = $('#graph_content .container')[0];
+                var el = $('#graph_content tr')[0];
                 var row_h = el.clientHeight;
                 $msgs.each(function() {
                     var m = this;
@@ -273,37 +192,30 @@
                     }
                 });
 
-                $('.expand_commit').on('click',function(e){
-                    var cid = $(this).attr('commit_id');
-                    $('#C-'+cid).toggleClass('expanded');
-
-                    //redraw the graph, r and jsdata are bound outside function
-                    r.render(jsdata,100);
-                });
-
                 // change branch filter
                 $("#branch_filter").select2({
                     dropdownAutoWidth: true,
-                    minimumInputLength: 1,
+                    maxResults: 50,
                     sortResults: branchSort
                     });
 
                 $("#branch_filter").change(function(e){
                     var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
                     if(selected_branch != ''){
-                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}',
+                        window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)},
                                                                           'branch': selected_branch});
                     }else{
-                        window.location = pyroutes.url('changelog_home', {'repo_name': '${c.repo_name}'});
+                        window.location = pyroutes.url('changelog_home', {'repo_name': ${h.js(c.repo_name)}});
                     }
                     $("#changelog").hide();
                 });
 
-                var jsdata = ${c.jsdata|n};
-                var r = new BranchRenderer('graph_canvas', 'graph_content', 'chg_');
-                r.render(jsdata,100);
+                graph.render(jsdata);
             });
 
+            $(window).resize(function(){
+                graph.render(jsdata);
+            });
         </script>
         %else:
             ${_('There are no changes yet')}
--- a/kallithea/templates/changelog/changelog_details.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changelog/changelog_details.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,11 +1,11 @@
 ## small box that displays changed/added/removed details fetched by AJAX
 
 % if len(c.cs.affected_files) <= c.affected_files_cut_off:
-<span class="removed tooltip" title="<b>${_('Removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
-<span class="changed tooltip" title="<b>${_('Changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
-<span class="added tooltip"   title="<b>${_('Added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
+<span class="removed" data-toggle="tooltip" title="<b>${_('Removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
+<span class="changed" data-toggle="tooltip" title="<b>${_('Changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
+<span class="added" data-toggle="tooltip" title="<b>${_('Added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
 % else:
- <span class="removed tooltip" title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
- <span class="changed tooltip" title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
- <span class="added tooltip"   title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
+ <span class="removed" data-toggle="tooltip" title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
+ <span class="changed" data-toggle="tooltip" title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
+ <span class="added" data-toggle="tooltip" title="${_('Affected %s files') % len(c.cs.affected_files)}">!</span>
 % endif
--- a/kallithea/templates/changelog/changelog_summary_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-## -*- coding: utf-8 -*-
-%if c.repo_changesets:
-<table>
-    <tr>
-        <th class="left"></th>
-        <th class="left"></th>
-        <th class="left">${_('Revision')}</th>
-        <th class="left">${_('Commit Message')}</th>
-        <th class="left">${_('Age')}</th>
-        <th class="left">${_('Author')}</th>
-        <th class="left">${_('Refs')}</th>
-    </tr>
-%for cnt,cs in enumerate(c.repo_changesets):
-    <tr class="parity${cnt%2}">
-        <td class="compact">
-            <div class="changeset-status-container">
-              %if c.statuses.get(cs.raw_id):
-                <span class="changeset-status-ico shortlog">
-                %if c.statuses.get(cs.raw_id)[2]:
-                  <a class="tooltip" title="${_('Changeset status: %s\nClick to open associated pull request %s') % (c.statuses.get(cs.raw_id)[1], c.statuses.get(cs.raw_id)[4])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
-                    <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
-                  </a>
-                %else:
-                  <i class="icon-circle changeset-status-${c.statuses.get(cs.raw_id)[0]}"></i>
-                %endif
-                </span>
-              %endif
-            </div>
-        </td>
-        <td class="compact">
-              %if c.comments.get(cs.raw_id,[]):
-               <div class="comments-container">
-                   <div title="${('comments')}">
-                       <a href="${c.comments[cs.raw_id][0].url()}">
-                          <i class="icon-comment"></i>${len(c.comments[cs.raw_id])}
-                       </a>
-                   </div>
-               </div>
-              %endif
-        </td>
-        <td>
-            <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}" class="revision-link">${h.show_id(cs)}</a>
-        </td>
-        <td>
-            ${h.urlify_commit(h.chop_at(cs.message,'\n'),c.repo_name, h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-        </td>
-        <td><span class="tooltip" title="${h.fmt_date(cs.date)}">
-                      ${h.age(cs.date)}</span>
-        </td>
-        <td title="${cs.author}">${h.person(cs.author)}</td>
-        <td>
-            %if h.is_hg(c.db_repo_scm_instance):
-                %for book in cs.bookmarks:
-                    <div class="booktag" title="${_('Bookmark %s') % book}">
-                        ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-                    </div>
-                %endfor
-            %endif
-            %for tag in cs.tags:
-             <div class="tagtag" title="${_('Tag %s') % tag}">
-                 ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
-             </div>
-            %endfor
-            %if cs.branch:
-             <div class="branchtag" title="${_('Branch %s' % cs.branch)}">
-                 ${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
-             </div>
-            %endif
-        </td>
-    </tr>
-%endfor
-
-</table>
-
-<div class="pagination-wh pagination-left">
-${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
-</div>
-%else:
-
-%if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
-<h4>${_('Add or upload files directly via Kallithea')}</h4>
-<div style="margin: 20px 30px;">
-  <div id="add_node_id" class="add_node">
-      <a class="btn btn-mini" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}">${_('Add New File')}</a>
-  </div>
-</div>
-%endif
-
-
-<h4>${_('Push new repository')}</h4>
-<pre>
-    ${c.db_repo_scm_instance.alias} clone ${c.clone_repo_url}
-    ${c.db_repo_scm_instance.alias} add README # add first file
-    ${c.db_repo_scm_instance.alias} commit -m "Initial" # commit with message
-    ${c.db_repo_scm_instance.alias} push ${'origin master' if h.is_git(c.db_repo_scm_instance) else ''} # push changes back
-</pre>
-
-<h4>${_('Existing repository?')}</h4>
-<pre>
-%if h.is_git(c.db_repo_scm_instance):
-    git remote add origin ${c.clone_repo_url}
-    git push -u origin master
-%else:
-    hg push ${c.clone_repo_url}
-%endif
-</pre>
-%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/changelog/changelog_table.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,121 @@
+## Render changelog table with id 'changesets' with the range of changesets,
+## statuses, and comments.
+## Optionally, pass a js snippet to run whenever a table resize is triggered.
+<%def name="changelog(repo_name, cs_range, cs_statuses, cs_comments, show_checkbox=False, show_branch=True, show_index=False, resize_js='')">
+    <% num_cs = len(cs_range) %>
+    <table class="table" id="changesets">
+    <tbody>
+      %for cnt,cs in enumerate(cs_range):
+      <tr id="chg_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 else ''}">
+        %if show_checkbox:
+        <td class="checkbox-column">
+          ${h.checkbox(cs.raw_id,class_="changeset_range")}
+        </td>
+        %endif
+        %if show_index:
+        <td class="changeset-logical-index">
+          <%
+              index = num_cs - cnt
+              if index == 1:
+                  title = _('First (oldest) changeset in this list')
+              elif index == num_cs:
+                  title = _('Last (most recent) changeset in this list')
+              else:
+                  title = _('Position in this list of changesets')
+          %>
+          <span data-toggle="tooltip" title="${title}">
+            ${index}
+          </span>
+        </td>
+        %endif
+        <td class="status">
+          %if cs_statuses.get(cs.raw_id):
+            %if cs_statuses.get(cs.raw_id)[2]:
+              <a data-toggle="tooltip"
+                  title="${_('Changeset status: %s by %s\nClick to open associated pull request %s') % (cs_statuses.get(cs.raw_id)[1], cs_statuses.get(cs.raw_id)[5].username, cs_statuses.get(cs.raw_id)[4])}"
+                  href="${h.url('pullrequest_show',repo_name=cs_statuses.get(cs.raw_id)[3],pull_request_id=cs_statuses.get(cs.raw_id)[2])}">
+                <i class="icon-circle changeset-status-${cs_statuses.get(cs.raw_id)[0]}"></i>
+              </a>
+            %else:
+              <a data-toggle="tooltip"
+                  title="${_('Changeset status: %s by %s') % (cs_statuses.get(cs.raw_id)[1], cs_statuses.get(cs.raw_id)[5].username)}"
+                  href="${cs_comments[cs.raw_id][0].url()}">
+                <i class="icon-circle changeset-status-${cs_statuses.get(cs.raw_id)[0]}"></i>
+              </a>
+            %endif
+          %endif
+        </td>
+        <td class="author" data-toggle="tooltip" title="${cs.author}">
+          ${h.gravatar(h.email_or_none(cs.author), size=16)}
+          <span class="user">${h.person(cs.author)}</span>
+        </td>
+        <td class="hash">
+          ${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=repo_name,revision=cs.raw_id), class_='changeset_hash')}
+        </td>
+        <td class="date">
+          <div data-toggle="tooltip" title="${h.fmt_date(cs.date)}">${h.age(cs.date,True)}</div>
+        </td>
+        <% message_lines = cs.message.splitlines() %>
+        %if len(message_lines) > 1:
+        <td class="expand_commit" title="${_('Expand commit message')}">
+          <i class="icon-align-left"></i>
+        </td>
+        %else:
+        <td class="expand_commit"></td>
+        %endif
+        <td class="mid">
+          <div class="log-container">
+            <div class="message">
+              <div class="message-firstline">${h.urlify_text(message_lines[0], c.repo_name,h.url('changeset_home',repo_name=repo_name,revision=cs.raw_id))}</div>
+              %if len(message_lines) > 1:
+              <div class="message-full hidden">${h.urlify_text(cs.message, repo_name)}</div>
+              %endif
+            </div>
+            <div class="extra-container">
+              %if cs_comments.get(cs.raw_id):
+                <a class="comments-container comments-cnt" href="${cs_comments[cs.raw_id][0].url()}" data-toggle="tooltip" title="${_('%s comments') % len(cs_comments[cs.raw_id])}">${len(cs_comments[cs.raw_id])}<i class="icon-comment-discussion"></i>
+                </a>
+              %endif
+              %for book in cs.bookmarks:
+                <span class="label label-bookmark" title="${_('Bookmark %s') % book}">${h.link_to(book,h.url('changeset_home',repo_name=repo_name,revision=cs.raw_id))}</span>
+              %endfor
+              %for tag in cs.tags:
+                <span class="label label-tag" title="${_('Tag %s') % tag}">${h.link_to(tag,h.url('changeset_home',repo_name=repo_name,revision=cs.raw_id))}</span>
+              %endfor
+              %if cs.bumped:
+                <span class="label label-bumped" title="Bumped">Bumped</span>
+              %endif
+              %if cs.divergent:
+                <span class="label label-divergent" title="Divergent">Divergent</span>
+              %endif
+              %if cs.extinct:
+                <span class="label label-extinct" title="Extinct">Extinct</span>
+              %endif
+              %if cs.unstable:
+                <span class="label label-unstable" title="Unstable">Unstable</span>
+              %endif
+              %if cs.phase:
+                <span class="label label-phase" title="Phase">${cs.phase}</span>
+              %endif
+              %if show_branch:
+                %for branch in cs.branches:
+                  <span class="label label-branch" title="${_('Branch %s' % branch)}">${h.link_to(branch,h.url('changelog_home',repo_name=repo_name,branch=branch))}</span>
+                %endfor
+              %endif
+            </div>
+          </div>
+        </td>
+      </tr>
+      %endfor
+    </tbody>
+    </table>
+
+<script type="text/javascript">
+  $(document).ready(function() {
+    $('#changesets .expand_commit').on('click',function(e){
+      $(this).next('.mid').find('.message > div').toggleClass('hidden');
+      ${resize_js};
+    });
+  });
+</script>
+</%def>
--- a/kallithea/templates/changeset/changeset.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changeset/changeset.html	Sun Mar 31 21:28:56 2019 +0200
@@ -9,7 +9,7 @@
 </%block>
 
 <%def name="breadcrumbs_links()">
-    ${_('Changeset')} - <span class='hash'>${h.show_id(c.changeset)}</span>
+    ${_('Changeset')} - <span class='changeset_hash'>${h.show_id(c.changeset)}</span>
 </%def>
 
 <%block name="header_menu">
@@ -18,115 +18,88 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog', c.changeset.raw_id)}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <script>
-    var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
-    AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
-    AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
-    </script>
-    <div class="table">
-        <div class="diffblock">
-            <div class="parents">
-                <div id="parent_link" class="changeset_hash">
-                    <i style="color:#036185" class="icon-left-open"></i> <a href="#">${_('Parent rev.')}</a>
-                </div>
-            </div>
+<div class="panel panel-primary">
+  <div class="panel-heading clearfix">
+    ${self.breadcrumbs()}
+  </div>
+  <script>
+    AJAX_COMMENT_URL = ${h.js(url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id))};
+    AJAX_COMMENT_DELETE_URL = ${h.js(url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__'))};
+  </script>
+  <div class="panel-body">
+    <div class="panel panel-default">
+        <div class="panel-heading clearfix">
+            ${self.parent_child_navigation()}
 
-            <div class="children">
-                <div id="child_link" class="changeset_hash">
-                    <a href="#">${_('Child rev.')}</a> <i style="color:#036185" class="icon-right-open"></i>
-                </div>
-            </div>
-
-            <div class="code-header banner">
-                <div class="changeset-status-container">
+                <div class="pull-left" title="${_('Changeset status')}">
                     %if c.statuses:
-                        <span class="changeset-status-ico"><i class="icon-circle changeset-status-${c.statuses[0]}"></i></span>
-                        <span title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</span>
+                        <i class="icon-circle changeset-status-${c.statuses[0]}"></i>
+                        [${h.changeset_status_lbl(c.statuses[0])}]
                     %endif
                 </div>
-                <div class="diff-actions">
-                  <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${_('Raw diff')}">
-                      <i class="icon-diff"></i>
-                  </a>
-                  <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"  class="tooltip" title="${_('Patch diff')}">
-                      <i class="icon-file-powerpoint"></i>
-                  </a>
-                  <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${_('Download diff')}">
-                      <i class="icon-floppy"></i>
-                  </a>
+                <div class="diff-actions pull-left">
+                  <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"
+                     data-toggle="tooltip"
+                     title="${_('Raw diff')}"><i class="icon-diff"></i></a>
+                  <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}"
+                     data-toggle="tooltip"
+                     title="${_('Patch diff')}"><i class="icon-file-powerpoint"></i></a>
+                  <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}"
+                     data-toggle="tooltip"
+                     title="${_('Download diff')}"><i class="icon-floppy"></i></a>
                   ${c.ignorews_url(request.GET)}
                   ${c.context_url(request.GET)}
                 </div>
-                <div class="comments-number" style="float:right;padding-right:5px">
-                    ${comment.comment_count(c.inline_cnt, len(c.comments))}
-                </div>
-            </div>
         </div>
-        <div id="changeset_content">
-            <div class="container">
-
-                <div class="right">
-                    <div class="changes">
-                        % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
-                         <span class="removed" title="${_('Removed')}">${len(c.changeset.removed)}</span>
-                         <span class="changed" title="${_('Changed')}">${len(c.changeset.changed)}</span>
-                         <span class="added" title="${_('Added')}">${len(c.changeset.added)}</span>
-                        % else:
-                         <span class="removed" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
-                         <span class="changed" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
-                         <span class="added"   title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
-                        % endif
-                    </div>
-
-                    <span class="logtags">
+        <div class="panel-body">
+            <div class="form-group changeset_content_header clearfix">
+                <div class="pull-right">
+                    <span>
                         %if len(c.changeset.parents)>1:
-                        <span class="merge">${_('Merge')}</span>
+                        <span class="label label-merge">${_('Merge')}</span>
                         %endif
 
-                        %if h.is_hg(c.db_repo_scm_instance):
-                          %for book in c.changeset.bookmarks:
-                          <span class="booktag" title="${_('Bookmark %s') % book}">
-                             ${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
-                          </span>
-                          %endfor
-                        %endif
+                        %for book in c.changeset.bookmarks:
+                        <span class="label label-bookmark" title="${_('Bookmark %s') % book}">${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
+                        %endfor
 
                         %for tag in c.changeset.tags:
-                         <span class="tagtag"  title="${_('Tag %s') % tag}">
-                         ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
+                         <span class="label label-tag"  title="${_('Tag %s') % tag}">${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
                         %endfor
 
-                        %if c.changeset.branch:
-                         <span class="branchtag" title="${_('Branch %s') % c.changeset.branch}">
-                         ${h.link_to(c.changeset.branch,h.url('changelog_home',repo_name=c.repo_name,branch=c.changeset.branch))}
-                         </span>
-                        %endif
+                        %for branch in c.changeset.branches:
+                          <span class="label label-branch" title="${_('Branch %s') % branch}">${h.link_to(branch,h.url('changelog_home',repo_name=c.repo_name,branch=branch))}</span>
+                        %endfor
                     </span>
+
+                    <div class="changes">
+                        % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
+                         <span class="label deleted" title="${_('Removed')}">${len(c.changeset.removed)}</span>
+                         <span class="label changed" title="${_('Changed')}">${len(c.changeset.changed)}</span>
+                         <span class="label added" title="${_('Added')}">${len(c.changeset.added)}</span>
+                        % else:
+                         <span class="label deleted" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
+                         <span class="label changed" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
+                         <span class="label added"   title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
+                        % endif
+                    </div>
                 </div>
-                <div class="left">
+                <div class="pull-left">
                      <div class="author">
-                         <div class="gravatar">
-                           ${h.gravatar(h.email_or_none(c.changeset.author), size=20)}
-                         </div>
+                         ${h.gravatar_div(h.email_or_none(c.changeset.author), size=20)}
                          <span><b>${h.person(c.changeset.author,'full_name_and_username')}</b> - ${h.age(c.changeset.date,True)} ${h.fmt_date(c.changeset.date)}</span><br/>
                          <span>${h.email_or_none(c.changeset.author)}</span><br/>
                      </div>
                      <% rev = c.changeset.extra.get('source') %>
                      %if rev:
                      <div>
-                       ${_('Grafted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev))}
+                       ${_('Grafted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev), class_="changeset_hash")}
                      </div>
                      %endif
                      <% rev = c.changeset.extra.get('transplant_source', '').encode('hex') %>
                      %if rev:
                      <div>
-                       ${_('Transplanted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev))}
+                       ${_('Transplanted from:')} ${h.link_to(h.short_id(rev),h.url('changeset_home',repo_name=c.repo_name,revision=rev), class_="changeset_hash")}
                      </div>
                      %endif
 
@@ -144,53 +117,58 @@
                      </div>
                      % endif
 
-                     % if hasattr(c.changeset, 'precursors') and c.changeset.precursors:
-                     <div class='precursors'>
-                       <span class='precursors_header'>${_('Preceded by:')} </span>
-                       % for i, s in enumerate(c.changeset.precursors):
+                     % if hasattr(c.changeset, 'predecessors') and c.changeset.predecessors:
+                     <div class='predecessors'>
+                       <span class='predecessors_header'>${_('Preceded by:')} </span>
+                       % for i, s in enumerate(c.changeset.predecessors):
                            <%
                            comma = ""
-                           if i != len(c.changeset.precursors)-1:
+                           if i != len(c.changeset.predecessors)-1:
                              comma = ", "
                            %>
-                           <a class="precursors_hash" href="${h.url('changeset_home',repo_name=c.repo_name, revision=s)}">${s}</a>${comma}
+                           <a class="predecessors_hash" href="${h.url('changeset_home',repo_name=c.repo_name, revision=s)}">${s}</a>${comma}
                        % endfor
                      </div>
                      % endif
-
-                     <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
                 </div>
             </div>
-            <div class="changes_txt">
-            % if c.limited_diff:
-            ${ungettext('%s file changed','%s files changed',len(c.changeset.affected_files)) % (len(c.changeset.affected_files))}:
-            % else:
-            ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.changeset.affected_files)) % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}:
-            %endif
+            <div class="form-group formatted-fixed">${h.urlify_text(c.changeset.message, c.repo_name)}</div>
+            <div>
+              <% a_rev, cs_rev, file_diff_data = c.changes[c.changeset.raw_id] %>
+              % if c.limited_diff:
+                  ${ungettext('%s file changed', '%s files changed', len(file_diff_data)) % len(file_diff_data)}:
+              % else:
+                  ${ungettext('%s file changed with %s insertions and %s deletions', '%s files changed with %s insertions and %s deletions', len(file_diff_data)) % (len(file_diff_data), c.lines_added, c.lines_deleted)}:
+              %endif
+              </div>
+              <div class="cs_files">
+                %for fid, url_fid, op, a_path, path, diff, stats in file_diff_data:
+                    <div class="cs_${op} clearfix">
+                      <span class="node">
+                          <i class="icon-diff-${op}"></i>${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                      </span>
+                      <div class="changes">${h.fancy_file_stats(stats)}</div>
+                    </div>
+                %endfor
+                %if c.limited_diff:
+                  <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
+                %endif
             </div>
-            <div class="cs_files">
-              %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
-                  <div class="cs_${change}">
-                        <div class="node">
-                            <i class="icon-diff-${change}"></i>
-                            <a href="#${FID}">${h.safe_unicode(path)}</a>
-                        </div>
-                    <div class="changes">${h.fancy_file_stats(stats)}</div>
-                  </div>
-              %endfor
-              % if c.limited_diff:
-                <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
-              % endif
+            <div class="comments-number">
+                ${comment.comment_count(c.inline_cnt, len(c.comments))}
             </div>
         </div>
 
     </div>
 
     ## diff block
+
     <div class="commentable-diff">
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
-    ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
+    <% a_rev, cs_rev, file_diff_data = c.changes[c.changeset.raw_id] %>
+    ${diff_block.diff_block(c.repo_name, 'rev', a_rev, a_rev,
+                            c.repo_name, 'rev', cs_rev, cs_rev, file_diff_data)}
     % if c.limited_diff:
       <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h4>
     % endif
@@ -203,111 +181,19 @@
     ${comment.generate_comments()}
 
     ## main comment form and it status
-    ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
-                       h.changeset_status(c.db_repo, c.changeset.raw_id))}
+    ${comment.comments()}
+
+    </div>
 
     ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
     <script type="text/javascript">
       $(document).ready(function(){
-          $('.show-inline-comments').change(function(e){
-              var target = e.currentTarget;
-              if(target == null){
-                  target = this;
-              }
-              var boxid = $(target).attr('id_for');
-              if(target.checked){
-                  $('#{0} .inline-comments'.format(boxid)).show();
-                  $('#{0} .inline-comments-button'.format(boxid)).show();
-              }else{
-                  $('#{0} .inline-comments'.format(boxid)).hide();
-                  $('#{0} .inline-comments-button'.format(boxid)).hide();
-              }
-          });
-
           $('.code-difftable').on('click', '.add-bubble', function(e){
               show_comment_form($(this));
           });
 
           move_comments($(".comments .comments-list-chunk"));
 
-          pyroutes.register('changeset_home',
-                            "${h.url('changeset_home', repo_name='%(repo_name)s', revision='%(revision)s')}",
-                            ['repo_name', 'revision']);
-
-          //next links
-          $('#child_link').on('click', function(e){
-              //fetch via ajax what is going to be the next link, if we have
-              //>1 links show them to user to choose
-              if(!$('#child_link').hasClass('disabled')){
-                  $.ajax({
-                    url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
-                    success: function(data) {
-                      if(data.results.length === 0){
-                          $('#child_link').addClass('disabled');
-                          $('#child_link').html('${_('No revisions')}');
-                      }
-                      if(data.results.length === 1){
-                          var commit = data.results[0];
-                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
-                      }
-                      else if(data.results.length === 2){
-                          $('#child_link').addClass('disabled');
-                          $('#child_link').addClass('double');
-                          var _html = '';
-                          _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-right-open"></i>'
-                                  .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
-                                  .replace('__title__', data.results[0].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
-                          _html +='<br/>'
-                          _html +='<a title="__title__" href="__url__">__rev__</a> <i style="color:#036185" class="icon-right-open"></i>'
-                                  .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
-                                  .replace('__title__', data.results[1].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
-                          $('#child_link').html(_html);
-                      }
-                    }
-                  });
-              e.preventDefault();
-              }
-          });
-
-          //prev links
-          $('#parent_link').on('click', function(e){
-              //fetch via ajax what is going to be the next link, if we have
-              //>1 links show them to user to choose
-              if(!$('#parent_link').hasClass('disabled')){
-                  $.ajax({
-                    url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.changeset.raw_id)}',
-                    success: function(data) {
-                      if(data.results.length === 0){
-                          $('#parent_link').addClass('disabled');
-                          $('#parent_link').html('${_('No revisions')}');
-                      }
-                      if(data.results.length === 1){
-                          var commit = data.results[0];
-                          window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
-                      }
-                      else if(data.results.length === 2){
-                          $('#parent_link').addClass('disabled');
-                          $('#parent_link').addClass('double');
-                          var _html = '';
-                          _html +='<i style="color:#036185" class="icon-left-open"></i> <a title="__title__" href="__url__">__rev__</a>'
-                                  .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
-                                  .replace('__title__', data.results[0].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
-                          _html +='<br/>'
-                          _html +='<i style="color:#036185" class="icon-left-open"></i> <a title="__title__" href="__url__">__rev__</a>'
-                                  .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
-                                  .replace('__title__', data.results[1].message)
-                                  .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
-                          $('#parent_link').html(_html);
-                      }
-                    }
-                  });
-              e.preventDefault();
-              }
-          });
-
           // hack: re-navigate to target after JS is done ... if a target is set and setting href thus won't reload
           if (window.location.hash != "") {
               window.location.href = window.location.href;
@@ -316,5 +202,5 @@
 
     </script>
 
-    </div>
+  </div>
 </%def>
--- a/kallithea/templates/changeset/changeset_file_comment.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changeset/changeset_file_comment.html	Sun Mar 31 21:28:56 2019 +0200
@@ -4,83 +4,130 @@
 ## ${comment.comment_block(co)}
 ##
 <%def name="comment_block(co)">
-  <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
+  <div class="comment" id="comment-${co.comment_id}">
     <div class="comment-prev-next-links"></div>
-    <div class="comment-wrapp">
-      <div class="meta">
-          <div style="float:left">
-               ${h.gravatar(co.author.email, size=20)}
-          </div>
-          <div class="user">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+          ${h.gravatar_div(co.author.email, size=20)}
+          <span class="user">
               ${co.author.full_name_and_username}
-          </div>
+          </span>
 
-          <span>
+          <span data-toggle="tooltip" title="${h.fmt_date(co.modified_at)}">
               ${h.age(co.modified_at)}
+          </span>
               %if co.pull_request:
+                <a href="${co.url()}">${_("comment")}</a>
                 ${_('on pull request')}
                 <a href="${co.pull_request.url()}">"${co.pull_request.title or _("No title")}"</a>
               %else:
                 ${_('on this changeset')}
               %endif
               <a class="permalink" href="${co.url()}">&para;</a>
-          </span>
 
-          %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or co.author.user_id == c.authuser.user_id:
-            <div onClick="confirm('${_("Delete comment?")}') && deleteComment(${co.comment_id})" class="buttons delete-comment btn btn-mini">${_('Delete')}</div>
+          %if co.author_id == request.authuser.user_id or h.HasRepoPermissionLevel('admin')(c.repo_name):
+            %if co.deletable():
+              <button type="button" onClick="confirm('${_('Delete comment?')}') && deleteComment(${co.comment_id})" class="pull-right buttons delete-comment btn btn-default btn-xs">${_('Delete')}</button>
+            %endif
           %endif
       </div>
-      <div class="text">
+      <div class="panel-body">
         %if co.status_change:
-           <div class="rst-block automatic-comment">
+           <div class="automatic-comment">
              <p>
-               <span title="${_('Changeset status')}" class="changeset-status-lbl">${_("Status change")}: ${co.status_change[0].status_lbl}</span>
-               <span class="changeset-status-ico"><i class="icon-circle changeset-status-${co.status_change[0].status}"></i></span>
+               ${_("Status change")}: <span class="comment-status-label">${co.status_change[0].status_lbl}</span>
+               <i class="icon-circle changeset-status-${co.status_change[0].status}"></i>
              </p>
            </div>
         %endif
+        <div class="comment-text">
         %if co.text:
-          ${h.rst_w_mentions(co.text)|n}
+          ${h.render_w_mentions(co.text, c.repo_name)|n}
         %endif
+        </div>
       </div>
     </div>
   </div>
 </%def>
 
 
-## expanded with .format(f_path, line_no)
-## TODO: don't assume line_no is globally unique ...
 <%def name="comment_inline_form()">
-<div id='comment-inline-form-template' style="display:none">
+<div id='comment-inline-form-template' style="display: none;">
+  <div class="comment comment-preview submitting" style="display: none;">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+          ${h.gravatar_div(request.authuser.email, size=20)}
+          <span class="user">
+              ${request.authuser.full_name_or_username}
+          </span>
+
+          <span class="comment-submission-status">
+              ${_('Submitting ...')}
+          </span>
+      </div>
+      <div class="panel-body">
+           <div class="automatic-comment" style="display: none;">
+             <p>
+               ${_("Status change")}: <span class="comment-status-label"></span>
+               <i class="icon-circle"></i>
+             </p>
+           </div>
+           <div class="comment-text">
+             <div class="formatted-fixed">
+             </div>
+           </div>
+      </div>
+    </div>
+  </div>
   <div class="ac">
-  %if c.authuser.username != 'default':
+  %if request.authuser.username != 'default':
     ${h.form('#', class_='inline-form')}
-      <div id="edit-container_{1}" class="clearfix">
-        <div class="comment-help">${_('Commenting on line {1}.')}
-          ${(_('Comments parsed using %s syntax with %s support.') % (
-                 ('<a href="%s">RST</a>' % h.url('rst_help')),
-                   ('<span style="color:#577632" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to notify another user'))
-               )
-            )|n
-           }
+      <div class="well well-sm clearfix comment-inline-well">
+        <div class="comment-help">
+          <span class="text-muted">${_('Comments are in plain text. Use @username to notify another user.')|n}</span>
         </div>
-        <div class="mentions-container" id="mentions_container_{1}"></div>
-        <textarea id="text_{1}" name="text" class="comment-block-ta yui-ac-input"></textarea>
-      </div>
-      <div id="preview-container_{1}" class="clearfix" style="display:none">
-        <div class="comment-help">
-            ${_('Comment preview')}
+        <textarea name="text" class="form-control"></textarea>
+
+        <div id="status_block_container" class="status-block general-only hidden">
+                %if c.pull_request is None:
+                  ${_('Set changeset status')}:
+                %else:
+                  ${_('Vote for pull request status')}:
+                %endif
+                <span class="general-only cs-only">
+                </span>
+                <label class="radio-inline">
+                    <input type="radio" class="status_change_radio" name="changeset_status" id="changeset_status_unchanged" value="" checked="checked" />
+                    ${_('No change')}
+                </label>
+                %for status, lbl in c.changeset_statuses:
+                    <label class="radio-inline">
+                        <input type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
+                        ${lbl}<i class="icon-circle changeset-status-${status}"></i>
+                    </label>
+                %endfor
+
+                %if c.pull_request is not None and ( \
+                    h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionLevel('admin')(c.repo_name) \
+                    or c.pull_request.owner_id == request.authuser.user_id):
+                <div>
+                  ${_('Finish pull request')}:
+                  <label class="checkbox-inline">
+                    <input id="save_close" type="checkbox" name="save_close" class="status_change_checkbox">
+                    ${_("Close")}
+                  </label>
+                  <label class="checkbox-inline">
+                    <input id="save_delete" type="checkbox" name="save_delete" value="delete" class="status_change_checkbox">
+                    ${_("Delete")}
+                  </label>
+                </div>
+                %endif
         </div>
-        <div id="preview-box_{1}" class="preview-box"></div>
+
       </div>
       <div class="comment-button">
-        <div class="submitting-overlay">${_('Submitting ...')}</div>
-        <input type="hidden" name="f_path" value="{0}">
-        <input type="hidden" name="line" value="{1}">
-        ${h.submit('save', _('Comment'), class_='btn btn-small save-inline-form')}
-        ${h.reset('hide-inline-form', _('Cancel'), class_='btn btn-small hide-inline-form')}
-        <div id="preview-btn_{1}" class="preview-btn btn btn-small">${_('Preview')}</div>
-        <div id="edit-btn_{1}" class="edit-btn btn btn-small" style="display:none">${_('Edit')}</div>
+        ${h.submit('save', _('Comment'), class_='btn btn-default btn-sm save-inline-form')}
+        ${h.reset('hide-inline-form', _('Cancel'), class_='btn btn-default btn-sm hide-inline-form')}
       </div>
     ${h.end_form()}
   %else:
@@ -91,7 +138,7 @@
           </div>
       </div>
       <div class="comment-button">
-      ${h.reset('hide-inline-form', _('Hide'), class_='btn btn-small hide-inline-form')}
+      ${h.reset('hide-inline-form', _('Hide'), class_='btn btn-default btn-sm hide-inline-form')}
       </div>
       ${h.end_form()}
   %endif
@@ -113,7 +160,8 @@
 
 ## generate inline comments and the main ones
 <%def name="generate_comments()">
-<div class="comments">
+## original location of comments ... but the ones outside diff context remains here
+<div class="comments inline-comments">
   %for f_path, lines in c.inline_comments:
     %for line_no, comments in lines.iteritems():
       <div class="comments-list-chunk" data-f_path="${f_path}" data-line_no="${line_no}" data-target-id="${h.safeid(h.safe_unicode(f_path))}_${line_no}">
@@ -124,126 +172,40 @@
     %endfor
   %endfor
 
-  <div class="comments-number">
-    ${comment_count(c.inline_cnt, len(c.comments))}
-  </div>
-
-      <div class="comments-list-general">
+      <div class="comments-list-chunk" data-f_path="" data-line_no="" data-target-id="general-comments">
         %for co in c.comments:
             ${comment_block(co)}
         %endfor
       </div>
 </div>
+<div class="comments-number">
+    ${comment_count(c.inline_cnt, len(c.comments))}
+</div>
 </%def>
 
 ## MAIN COMMENT FORM
-<%def name="comments(post_url, cur_status, is_pr=False, change_status=True)">
-
-<div class="comments">
-    %if c.authuser.username != 'default':
-    <div class="comment-form ac">
-      ${h.form(post_url, id="main_form")}
-        <div id="edit-container" class="clearfix">
-            <div class="comment-help">
-                ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
-                  '<span style="color:#577632" class="tooltip" title="%s">@mention</span>' %
-                  _('Use @username inside this text to notify another user.')))|n}
-            </div>
-            <div class="mentions-container" id="mentions_container"></div>
-            ${h.textarea('text', class_="comment-block-ta")}
-            %if change_status:
-              <div id="status_block_container" class="status-block">
-                %if is_pr:
-                  ${_('Vote for pull request status')}:
-                %else:
-                  ${_('Set changeset status')}:
-                %endif
-                <input type="radio" class="status_change_radio" name="changeset_status" id="changeset_status_unchanged" value="" checked="checked" />
-                <label for="changeset_status_unchanged">
-                  ${_('No change')}
-                </label>
-                %for status,lbl in c.changeset_statuses:
-                    <span>
-                        <input type="radio" class="status_change_radio" name="changeset_status" id="${status}" value="${status}">
-                        <label for="${status}"><i class="icon-circle changeset-status-${status}" /></i>${lbl}</label>
-                    </span>
-                %endfor
-
-                %if is_pr and ( \
-                    h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) \
-                    or c.pull_request.owner.user_id == c.authuser.user_id):
-                  <input id="save_close" type="checkbox" name="save_close">
-                  <label id="save_close_label" for="save_close">${_("Close")}</label>
-                %endif
-              </div>
-            %endif
-        </div>
-
-        <div id="preview-container" class="clearfix" style="display:none">
-            <div class="comment-help">
-                ${_('Comment preview')}
-            </div>
-            <div id="preview-box" class="preview-box"></div>
-        </div>
-
-        <div class="comment-button">
-            ${h.submit('save', _('Comment'), class_="btn")}
-            <div id="preview-btn" class="preview-btn btn">${_('Preview')}</div>
-            <div id="edit-btn" class="edit-btn btn" style="display:none">${_('Edit')}</div>
-        </div>
-      ${h.end_form()}
-    </div>
-    %endif
+<%def name="comments(change_status=True)">
+<div class="inline-comments inline-comments-general
+            ${'show-general-status' if change_status else ''}">
+  <div id="comments-general-comments" class="">
+  ## comment_div for general comments
+  </div>
 </div>
 
 <script>
 
 $(document).ready(function () {
-   MentionsAutoComplete($('#text'), $('#mentions_container'), _USERS_AC_DATA, _GROUPS_AC_DATA);
 
    $(window).on('beforeunload', function(){
-      if($('.comment-inline-form textarea[name=text]').size() ||
-         $('textarea#text').val()) {
+      var $textareas = $('.comment-inline-form textarea[name=text]');
+      if($textareas.size() > 1 ||
+         $textareas.val()) {
          // this message will not be displayed on all browsers
          // (e.g. some versions of Firefox), but the user will still be warned
          return 'There are uncommitted comments.';
       }
    });
 
-   $('form#main_form').submit(function(){
-      // if no open inline forms, disable the beforeunload check - it would
-      // fail in the check for the textarea we are about to submit
-      if(!$('.form-open').size()){
-          $(window).off('beforeunload');
-      }
-   });
-
-   $('#preview-btn').click(function(){
-       var _text = $('#text').val();
-       if(!_text){
-           return;
-       }
-       var post_data = {'text': _text};
-       $('#preview-box').addClass('unloaded');
-       $('#preview-box').html(_TM['Loading ...']);
-       $('#edit-container').hide();
-       $('#edit-btn').show();
-       $('#preview-container').show();
-       $('#preview-btn').hide();
-
-       var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
-       ajaxPOST(url,post_data,function(html){
-           $('#preview-box').html(html);
-           $('#preview-box').removeClass('unloaded');
-       });
-   });
-   $('#edit-btn').click(function(){
-       $('#edit-container').show();
-       $('#edit-btn').hide();
-       $('#preview-container').hide();
-       $('#preview-btn').show();
-   });
-
 });
 </script>
 </%def>
--- a/kallithea/templates/changeset/changeset_range.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changeset/changeset_range.html	Sun Mar 31 21:28:56 2019 +0200
@@ -7,9 +7,9 @@
 
 <%def name="breadcrumbs_links()">
     ${_('Changesets')} -
-    ${h.link_to(h.show_id(c.cs_ranges[0]),h.url('changeset_home',repo_name=c.repo_name,revision=c.cs_ranges[0].raw_id))}</td>
+    ${h.link_to(h.show_id(c.cs_ranges[0]),h.url('changeset_home',repo_name=c.repo_name,revision=c.cs_ranges[0].raw_id))}
     <i class="icon-right"></i>
-    ${h.link_to(h.show_id(c.cs_ranges[-1]),h.url('changeset_home',repo_name=c.repo_name,revision=c.cs_ranges[-1].raw_id))}</td>
+    ${h.link_to(h.show_id(c.cs_ranges[-1]),h.url('changeset_home',repo_name=c.repo_name,revision=c.cs_ranges[-1].raw_id))}
 </%def>
 
 <%block name="header_menu">
@@ -18,96 +18,90 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <div class="table">
-        <div id="body" class="diffblock">
-            <div class="code-header">
-                <div>
-                    ${h.show_id(c.cs_ranges[0])}
-                    <i class="icon-right"></i>
-                    ${h.show_id(c.cs_ranges[-1])}
-                    <a style="font-weight: bold" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='rev',org_ref_name=getattr(c.cs_ranges[0].parents[0] if c.cs_ranges[0].parents else h.EmptyChangeset(),'raw_id'),other_ref_type='rev',other_ref_name=c.cs_ranges[-1].raw_id)}" class="btn btn-small"><i class="icon-git-compare"></i> Compare Revisions</a>
-                </div>
-            </div>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='rev',org_ref_name=getattr(c.cs_ranges[0].parents[0] if c.cs_ranges[0].parents else h.EmptyChangeset(),'raw_id'),other_ref_type='rev',other_ref_name=c.cs_ranges[-1].raw_id)}" class="btn btn-default btn-sm"><i class="icon-git-compare"></i>Compare Revisions</a>
         </div>
-        <div id="changeset_compare_view_content">
-            <div class="container">
-            <table class="compare_view_commits noborder">
-            %for cnt,cs in enumerate(c.cs_ranges):
-                <tr>
-                <td><div class="gravatar">${h.gravatar(h.email_or_none(cs.author), size=14)}</div></td>
-                <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</td>
-                <td><div class="author">${h.person(cs.author)}</div></td>
-                <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
-                <td>
-                  %if c.statuses:
-                    <div title="${_('Changeset status')}" class="changeset-status-ico"><i class="icon-circle changeset-status-${c.statuses[cnt]}"></i></div>
-                  %endif
-                </td>
-                <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
-                </tr>
-            %endfor
-            </table>
-            </div>
-            <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
-            <div class="cs_files">
-                %for cs in c.cs_ranges:
-                    <div class="cur_cs">${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</div>
-                    %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[cs.raw_id].iteritems():
-                        <div class="cs_${change}">
-                            <div class="node">
-                                <i class="icon-diff-${change}"></i>
-                                ${h.link_to(h.safe_unicode(path),h.url.current(anchor=FID))}
+    </div>
+    <div class="panel-body">
+        <div>
+                <table class="table compare_view_commits">
+                %for cnt,cs in enumerate(c.cs_ranges):
+                  <tr>
+                    %if c.visual.use_gravatar:
+                    <td>${h.gravatar_div(h.email_or_none(cs.author), size=14)}</td>
+                    %endif
+                    <td>${h.link_to(h.short_id(cs.raw_id),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</td>
+                    <td class="author">${h.person(cs.author)}</td>
+                    <td><span data-toggle="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
+                    <td>
+                      %if c.statuses:
+                        <i class="icon-circle changeset-status-${c.statuses[cnt]}" title="${_('Changeset status: %s') % h.changeset_status_lbl(c.statuses[cnt])}"></i>
+                      %endif
+                    </td>
+                    <td><div class="message">${h.urlify_text(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
+                  </tr>
+                %endfor
+                </table>
+                <h4>${_('Files affected')}</h4>
+                <div class="cs_files">
+                    %for cs in c.cs_ranges:
+                        <h6>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</h6>
+                        <% a_rev, cs_rev, file_diff_data = c.changes[cs.raw_id] %>
+                        %for fid, url_fid, op, a_path, path, diff, stats in file_diff_data:
+                            <div class="cs_${op} clearfix">
+                                <span class="node">
+                                    <i class="icon-diff-${op}"></i>
+                                    ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                                </span>
+                                <div class="changes">${h.fancy_file_stats(stats)}</div>
                             </div>
-                            <div class="changes">${h.fancy_file_stats(stats)}</div>
-                        </div>
+                        %endfor
                     %endfor
-                %endfor
-            </div>
+                </div>
         </div>
-
     </div>
     <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
     %for cs in c.cs_ranges:
-          ##${comment.comment_inline_form(cs)}
+        <div class="panel-body">
           ## diff block
           <div class="h3">
-          <a class="tooltip" title="${cs.message}" href="${h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id)}">${h.show_id(cs)}</a>
-             <div class="gravatar">
-               ${h.gravatar(h.email_or_none(cs.author), size=20)}
-             </div>
-             <div class="right">
-              <span class="logtags">
+          ${h.gravatar_div(h.email_or_none(cs.author), size=20)}
+          <a data-toggle="tooltip" title="${cs.message}" href="${h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id)}">${h.show_id(cs)}</a>
+            <div class="right">
+              <span>
                 %if len(cs.parents)>1:
-                <span class="merge">${_('Merge')}</span>
+                <span class="label label-merge">${_('Merge')}</span>
                 %endif
                 %if h.is_hg(c.db_repo_scm_instance):
                   %for book in cs.bookmarks:
-                  <span class="booktag" title="${_('Bookmark %s') % book}">
+                  <span class="label label-bookmark" title="${_('Bookmark %s') % book}">
                      ${h.link_to(book,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
                   </span>
                   %endfor
                 %endif
                 %for tag in cs.tags:
-                    <span class="tagtag" title="${_('Tag %s') % tag}">
+                    <span class="label label-tag" title="${_('Tag %s') % tag}">
                     ${h.link_to(tag,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</span>
                 %endfor
-                %if cs.branch:
-                <span class="branchtag" title="${_('Branch %s') % cs.branch}">
-                   ${h.link_to(cs.branch,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
-                </span>
-                %endif
+                %for branch in cs.branches:
+                  <span class="label label-branch" title="${_('Branch %s') % branch}">
+                    ${h.link_to(branch,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
+                  </span>
+                %endfor
               </span>
             </div>
-           </div>
-          ${diff_block.diff_block(c.changes[cs.raw_id])}
-
+          </div>
+          <% a_rev, cs_rev, file_diff_data = c.changes[cs.raw_id] %>
+          ${diff_block.diff_block(c.repo_name, 'rev', a_rev, a_rev,
+                                  c.repo_name, 'rev', cs_rev, cs_rev, file_diff_data)}
+        </div>
     %endfor
 </div>
 </%def>
--- a/kallithea/templates/changeset/diff_block.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/changeset/diff_block.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,129 +1,98 @@
 ## -*- coding: utf-8 -*-
-##usage:
-## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
-## ${diff_block.diff_block(change)}
-##
-<%def name="diff_block(change)">
+
+<%def name="diff_block(a_repo_name, a_ref_type, a_ref_name, a_rev,
+                       cs_repo_name, cs_ref_name, cs_ref_type, cs_rev,
+                       file_diff_data)">
 <div class="diff-collapse">
-    <span target="${'diff-container-%s' % (id(change))}" class="diff-collapse-button">&uarr; ${_('Collapse Diff')} &uarr;</span>
+    <button data-target="${'diff-container-%s' % (id(file_diff_data))}" class="diff-collapse-button btn btn-link btn-sm">&uarr; ${_('Collapse Diff')} &uarr;</button>
 </div>
-<div class="diff-container" id="${'diff-container-%s' % (id(change))}">
-%for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
-    <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
-    <div id="${FID}" class="diffblock  margined comm">
-        <div class="code-header">
-            <div class="changeset_header">
-                <div class="changeset_file">
-                    ${h.link_to_if(change!='D',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name,
-                    revision=cs2,f_path=h.safe_unicode(path)))}
+%for id_fid, url_fid, op, a_filename, cs_filename, diff, stats in file_diff_data:
+    ${diff_block_diffblock(id_fid, url_fid, op, diff,
+        a_repo_name, a_rev, a_ref_type, a_ref_name, a_filename,
+        cs_repo_name, cs_rev, cs_ref_type, cs_ref_name, cs_filename,
+        'diff-container-%s' % id(file_diff_data))}
+%endfor
+</%def>
+
+<%def name="diff_block_diffblock(id_fid, url_fid, op, diff,
+    a_repo_name, a_rev, a_ref_type, a_ref_name, a_filename,
+    cs_repo_name, cs_rev, cs_ref_type, cs_ref_name, cs_filename, cls)"
+>
+    <div id="${id_fid}_target"></div>
+    <div id="${id_fid}" class="panel panel-default ${cls}">
+        <div class="panel-heading clearfix">
+                <div class="pull-left">
+                    ${h.safe_unicode(cs_filename)}
                 </div>
-                <div class="diff-actions">
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${_('Show full diff for this file')}">
-                      <i class="icon-file-code"></i>
-                  </a>
-                  <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${_('Show full side-by-side diff for this file')}">
-                      <i class="icon-docs"></i>
-                  </a>
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${_('Raw diff')}">
-                      <i class="icon-diff"></i>
-                  </a>
-                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${_('Download diff')}">
-                      <i class="icon-floppy"></i>
-                  </a>
-                  ${c.ignorews_url(request.GET, h.FID(cs2,path))}
-                  ${c.context_url(request.GET, h.FID(cs2,path))}
+                <div class="pull-left diff-actions">
+                  <span>
+                    %if op == 'A':
+                      <span class="no-file" data-toggle="tooltip" title="${_('No file before')}">
+                        <i class="icon-minus-circled"></i></span>
+                    %else:
+                      <a href="${h.url('files_home', repo_name=a_repo_name, f_path=a_filename, revision=a_rev)}" data-toggle="tooltip" title="${_('File before')}">
+                        <i class="icon-doc"></i></a>
+                    %endif
+
+                    %if op == 'A':
+                      <span class="arrow" data-toggle="tooltip" title="${_('Added')}">&#10142;</span>
+                    %elif op == 'M':
+                      <span class="arrow" data-toggle="tooltip" title="${_('Modified')}">&#10142;</span>
+                    %elif op == 'D':
+                      <span class="arrow" data-toggle="tooltip" title="${_('Deleted')}">&#10142;</span>
+                    %elif op == 'R':
+                      <span class="arrow" data-toggle="tooltip" title="${_('Renamed')}">&#10142;</span>
+                    %elif op is None:
+                      <span class="arrow" data-toggle="tooltip" title="${_('No change')}">&#10142;</span>
+                    %else:
+                      <span class="arrow" data-toggle="tooltip" title="${_('Unknown operation: %r') % op}">&#10142;</span>
+                    %endif
+
+                    %if op == 'D':
+                      <span class="no-file" data-toggle="tooltip" title="${_('No file after')}">
+                        <i class="icon-minus-circled"></i></span>
+                    %else:
+                      <a href="${h.url('files_home', repo_name=cs_repo_name, f_path=cs_filename, revision=cs_rev)}" data-toggle="tooltip" title="${_('File after')}">
+                        <i class="icon-doc"></i></a>
+                    %endif
+                  </span>
+
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full diff for this file')}">
+                      <i class="icon-file-code"></i></a>
+                  <a href="${h.url('files_diff_2way_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='diff',fulldiff=1)}" data-toggle="tooltip" title="${_('Show full side-by-side diff for this file')}">
+                      <i class="icon-docs"></i></a>
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='raw')}" data-toggle="tooltip" title="${_('Raw diff')}">
+                      <i class="icon-diff"></i></a>
+                  <a href="${h.url('files_diff_home',repo_name=cs_repo_name,f_path=h.safe_unicode(cs_filename),diff2=cs_rev,diff1=a_rev,diff='download')}" data-toggle="tooltip" title="${_('Download diff')}">
+                      <i class="icon-floppy"></i></a>
+                  ${c.ignorews_url(request.GET, url_fid)}
+                  ${c.context_url(request.GET, url_fid)}
                 </div>
-                <span style="float:right;margin-top:-3px">
-                  <label>
-                  ${_('Show inline comments')}
-                  ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))}
-                  </label>
-                </span>
-            </div>
+                <div class="pull-right">
+                    ${_('Show inline comments')}
+                    ${h.checkbox('checkbox-show-inline-' + id_fid, checked="checked",class_="show-inline-comments",**{'data-id_for':id_fid})}
+                </div>
         </div>
-        <div class="code-body full_f_path" data-f_path="${h.safe_unicode(path)}">
+        <div class="no-padding panel-body" data-f_path="${h.safe_unicode(cs_filename)}">
             ${diff|n}
-            %if path.rsplit('.')[-1] in ['png', 'gif', 'jpg', 'bmp']:
+            %if op and cs_filename.rsplit('.')[-1] in ['png', 'gif', 'jpg', 'bmp']:
               <div class="btn btn-image-diff-show">Show images</div>
-              %if change =='M':
-                <div id="${FID}_image-diff" class="btn btn-image-diff-swap" style="display:none">Press to swap images</div>
+              %if op == 'M':
+                <div id="${id_fid}_image-diff" class="btn btn-image-diff-swap" style="display:none">Press to swap images</div>
               %endif
-              <div style="font-size: 0">
-                %if change == 'M':
-                  <img id="${FID}_image-diff-img-a" class="img-diff img-diff-swapable" style="display:none"
-                      realsrc="${h.url('files_raw_home',repo_name=c.repo_name,revision=cs1,f_path=path)}" />
+              <div>
+                %if op in 'DM':
+                  <img id="${id_fid}_image-diff-img-a" class="img-diff img-diff-swapable" style="display:none"
+                      realsrc="${h.url('files_raw_home',repo_name=a_repo_name,revision=a_rev,f_path=a_filename)}" />
                 %endif
-                %if change in 'AM':
-                  <img id="${FID}_image-diff-img-b" class="img-diff img-diff-swapable" style="display:none"
-                      realsrc="${h.url('files_raw_home',repo_name=c.repo_name,revision=cs2,f_path=path)}" />
+                %if op in 'AM':
+                  <img id="${id_fid}_image-diff-img-b" class="img-diff img-diff-swapable" style="display:none"
+                      realsrc="${h.url('files_raw_home',repo_name=cs_repo_name,revision=cs_rev,f_path=cs_filename)}" />
                 %endif
               </div>
             %endif
         </div>
     </div>
-%endfor
-</div>
-</%def>
-
-<%def name="diff_block_simple(change)">
-
-  %for op,filenode_path,diff in change:
-    <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div>
-    <div id="${h.FID('',filenode_path)}" class="diffblock  margined comm">
-      <div class="code-header">
-          <div class="changeset_header">
-              <div class="changeset_file">
-                  ${h.safe_unicode(filenode_path)} |
-                  ## TODO: link to ancestor and head of other instead of exactly other
-                  %if op == 'A':
-                    ${_('Added')}
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a>
-                  %elif op == 'M':
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a>
-                    <i class="icon-right"></i>
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a>
-                  %elif op == 'D':
-                    ${_('Deleted')}
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a>
-                  %elif op == 'R':
-                    ${_('Renamed')}
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.a_repo.repo_name, f_path=filenode_path, revision=c.a_rev)}">${h.short_id(c.a_ref_name) if c.a_ref_type=='rev' else c.a_ref_name}</a>
-                    <i class="icon-right"></i>
-                    <a class="spantag" href="${h.url('files_home', repo_name=c.cs_repo.repo_name, f_path=filenode_path, revision=c.cs_rev)}">${h.short_id(c.cs_ref_name) if c.cs_ref_type=='rev' else c.cs_ref_name}</a>
-                  %else:
-                    ${op}???
-                  %endif
-              </div>
-              <div class="diff-actions">
-                <a href="${h.url('files_diff_2way_home',repo_name=c.cs_repo.repo_name,f_path=h.safe_unicode(filenode_path),diff1=c.a_rev,diff2=c.cs_rev,diff='diff',fulldiff=1)}" class="tooltip" title="${_('Show full side-by-side diff for this file')}">
-                  <i class="icon-docs"></i>
-                </a>
-                ${c.ignorews_url(request.GET)}
-                ${c.context_url(request.GET)}
-              </div>
-          </div>
-      </div>
-        <div class="code-body full_f_path" data-f_path="${h.safe_unicode(filenode_path)}">
-            ${diff|n}
-            %if filenode_path.rsplit('.')[-1] in ['png', 'gif', 'jpg', 'bmp']:
-              <div class="btn btn-image-diff-show">Show images</div>
-              %if op == 'M':
-                <div id="${h.FID('',filenode_path)}_image-diff" class="btn btn-image-diff-swap" style="display:none">Press to swap images</div>
-              %endif
-              <div style="font-size: 0">
-                %if op == 'M':
-                  <img id="${h.FID('',filenode_path)}_image-diff-img-a" class="img-diff img-diff-swapable" style="display:none"
-                      realsrc="${h.url('files_raw_home',repo_name=c.a_repo.repo_name,revision=c.a_rev,f_path=filenode_path) if op in 'DM' else ''}" />
-                %endif
-                %if op in 'AM':
-                  <img id="${h.FID('',filenode_path)}_image-diff-img-b" class="img-diff img-diff-swapable" style="display:none"
-                      realsrc="${h.url('files_raw_home',repo_name=c.cs_repo.repo_name,revision=c.cs_rev,f_path=filenode_path) if op in 'AM' else ''}" />
-                %endif
-              </div>
-            %endif
-        </div>
-    </div>
-  %endfor
 </%def>
 
 <%def name="diff_block_js()">
@@ -149,6 +118,32 @@
     };
     $('.btn-image-diff-swap').mouseup(reset);
     $('.btn-image-diff-swap').mouseleave(reset);
+
+    $('.diff-collapse-button').click(function(e) {
+        $('.diff_block').toggleClass('hidden');
+        var $button = $(e.currentTarget);
+        var $target = $('.' + $button.data('target'));
+        if($target.hasClass('hidden')){
+            $target.removeClass('hidden');
+            $button.html("&uarr; {0} &uarr;".format(_TM['Collapse Diff']));
+        }
+        else if(!$target.hasClass('hidden')){
+            $target.addClass('hidden');
+            $button.html("&darr; {0} &darr;".format(_TM['Expand Diff']));
+        }
+    });
+    $('.show-inline-comments').change(function(e){
+        var target = e.currentTarget;
+        if(target == null){
+            target = this;
+        }
+        var boxid = $(target).data('id_for');
+        if(target.checked){
+            $('#{0} .inline-comments'.format(boxid)).show();
+        }else{
+            $('#{0} .inline-comments'.format(boxid)).hide();
+        }
+    });
 });
 </script>
 </%def>
--- a/kallithea/templates/compare/compare_cs.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/compare/compare_cs.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,77 +1,41 @@
-## Changesets table !
-<div class="container">
+## Changesets table with graph
+<%namespace name="changelog_table" file="/changelog/changelog_table.html"/>
+<div>
   %if not c.cs_ranges:
-    <span class="empty_data">${_('No changesets')}</span>
+    <span class="text-muted">${_('No changesets')}</span>
   %else:
 
-    %if c.ancestor:
-    <div class="ancestor">${_('Ancestor')}:
-      ${h.link_to(h.short_id(c.ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
-    </div>
+    %if c.ancestors:
+      <div class="ancestor">
+        %if len(c.ancestors) > 1:
+        <div class="text-danger">
+          ${_('Criss cross merge situation with multiple merge ancestors detected!')}
+        </div>
+        <div>
+          ${_('Please merge the target branch to your branch before creating a pull request.')}
+        </div>
+        %endif
+        <div>
+          ${_('Merge Ancestor')}:
+          %for ancestor in c.ancestors:
+            ${h.link_to(h.short_id(ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=ancestor), class_="changeset_hash")}
+          %endfor
+        </div>
+      </div>
     %endif
 
     <div id="graph_nodes">
-        <canvas id="graph_canvas" style="width:0"></canvas>
+        <canvas id="graph_canvas"></canvas>
     </div>
 
-    <div id="graph_content_pr" style="margin-left: 100px;">
-
-    <table class="compare_view_commits noborder">
-    %for cnt, cs in enumerate(reversed(c.cs_ranges)):
-        <tr id="chg_${cnt+1}">
-        <td style="width:50px">
-          %if cs.raw_id in c.statuses:
-            <div title="${_('Changeset status: %s') % c.statuses[cs.raw_id][1]}" class="changeset-status-ico">
-                <i class="icon-circle changeset-status-${c.statuses[cs.raw_id][0]}"></i>
-            </div>
-          %endif
-          %if c.cs_comments.get(cs.raw_id):
-              <div class="comments-container">
-                  <div class="comments-cnt" title="${_('Changeset has comments')}">
-                      <a href="${c.cs_comments[cs.raw_id][0].url()}">
-                          ${len(c.cs_comments[cs.raw_id])}
-                          <i class="icon-comment"></i>
-                      </a>
-                  </div>
-              </div>
-          %endif
-        </td>
-        <td class="changeset-logical-index">
-          <%
-              num_cs = len(c.cs_ranges)
-              index = num_cs - cnt
-              if index == 1:
-                  title = _('First (oldest) changeset in this list')
-              elif index == num_cs:
-                  title = _('Last (most recent) changeset in this list')
-              else:
-                  title = _('Position in this list of changesets')
-          %>
-          <span class="tooltip" title="${title}">
-            ${index}
-          </span>
-        </td>
-        <td style="width: 140px"><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
-        <td><div class="gravatar" commit_id="${cs.raw_id}">${h.gravatar(h.email_or_none(cs.author), size=14)}</div></td>
-        <td><div class="author">${h.person(cs.author)}</div></td>
-        <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</td>
-        <td>
-        %if cs.branch:
-        <span class="branchtag">${h.link_to(cs.branch,h.url('changelog_home',repo_name=c.cs_repo.repo_name,branch=cs.branch))}</span>
-        %endif
-        </td>
-        <td class="expand_commit" commit_id="${cs.raw_id}" title="${_('Expand commit message')}">
-            <i class="icon-align-left" style="color:#999"></i>
-        </td>
-        <td><div id="C-${cs.raw_id}" class="message">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
-        </tr>
-    %endfor
-    </table>
-
+    <div id="graph_content_pr">
+      ${changelog_table.changelog(c.cs_repo.repo_name, list(reversed(c.cs_ranges)), c.cs_statuses, c.cs_comments,
+                                  show_index=True,
+                                  resize_js='graph.render(jsdata)')}
     </div>
 
-    %if c.as_form:
-      <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+    %if c.is_ajax_preview:
+      <h5>
       ## links should perhaps use ('rev', c.a_rev) instead ...
       ${h.link_to(_('Show merge diff'),
         h.url('compare_url',
@@ -81,19 +45,11 @@
           other_ref_type=c.cs_ref_type, other_ref_name=c.cs_ref_name,
           merge='1')
         )}
-      </div>
-      <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
-        ${_('Common ancestor')}:
-        %if c.ancestor:
-        ${h.link_to(h.short_id(c.ancestor),h.url('changeset_home',repo_name=c.repo_name,revision=c.ancestor))}
-        %else:
-        ${_('No common ancestor found - repositories are unrelated')}
-        %endif
-      </div>
+      </h5>
     %endif
     %if c.cs_ranges_org is not None:
       ## TODO: list actual changesets?
-      <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+      <div>
         ${h.link_to_ref(c.cs_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev)}
         ${_('is')}
         <a href="${c.swap_url}">${_('%s changesets') % (len(c.cs_ranges_org))}</a>
@@ -104,31 +60,26 @@
   %endif
 </div>
 
-%if c.as_form:
-<div id="jsdata" style="display:none">${c.jsdata|n}</div>
+%if c.is_ajax_preview:
+<div id="jsdata" style="display:none">${h.js(c.jsdata)}</div>
 %else:
 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
 %endif
 
 <script type="text/javascript">
+    var jsdata = ${h.js(c.jsdata)};
+    var graph = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
 
     $(document).ready(function(){
-%if not c.as_form:
-        var jsdata = ${c.jsdata|n};
-        var r = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
-        r.render(jsdata,100);
-%endif
+        graph.render(jsdata);
 
         $('.expand_commit').click(function(e){
-            var cid = $(this).attr('commit_id');
-            $('#C-'+cid).toggleClass('expanded');
-            r.render(jsdata,100);
+            $(this).next('.mid').find('.message').toggleClass('expanded');
+            graph.render(jsdata);
         });
-
-        $('.gravatar').click(function(e){
-            var cid = $(this).attr('commit_id');
-            $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
-        });
+    });
+    $(window).resize(function(){
+        graph.render(jsdata);
     });
 
 </script>
--- a/kallithea/templates/compare/compare_diff.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/compare/compare_diff.html	Sun Mar 31 21:28:56 2019 +0200
@@ -19,72 +19,78 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <div class="table">
-        <div id="body" class="diffblock">
-            <div class="code-header">
-                <div>
-                    ${h.hidden('compare_org')} <i class="icon-right"></i> ${h.hidden('compare_other')}
+    <div class="panel-body">
+      <div class="panel panel-default">
+        <div id="body" class="panel-heading">
+            <div class="compare-revision-selector">
+                ## divs are "inline-block" and cannot have whitespace between them.
+                <span>
+                    ${h.hidden('compare_org')}
+                </span><span>
+                    <i class="icon-right"></i>
+                </span><span>
+                    ${h.hidden('compare_other')}
+                </span><span>
                     %if not c.compare_home:
-                        <a class="btn btn-small" href="${c.swap_url}"><i class="icon-arrows-cw"></i> ${_('Swap')}</a>
+                        <a class="btn btn-default btn-sm" href="${c.swap_url}"><i class="icon-arrows-cw"></i>${_('Swap')}</a>
                     %endif
-                    <div id="compare_revs" class="btn btn-small"><i class="icon-git-compare"></i> ${_('Compare Revisions')}</div>
-                </div>
+                    <button type="button" id="compare_revs" class="btn btn-default btn-sm"><i class="icon-git-compare"></i>${_('Compare Revisions')}</button>
+                </span>
             </div>
         </div>
 
     %if c.compare_home:
-        <div id="changeset_compare_view_content">
-         <div style="color:#999;font-size: 18px">${_('Compare revisions, branches, bookmarks, or tags.')}</div>
+        <div id="changeset_compare_view_content" class="panel-body">
+         <h4 class="text-muted">${_('Compare revisions, branches, bookmarks, or tags.')}</h4>
         </div>
     %else:
-        <div id="changeset_compare_view_content">
+        <div id="changeset_compare_view_content" class="panel-body">
                 ##CS
-                <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</div>
+                <h5>${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}</h5>
                 <%include file="compare_cs.html" />
 
                 ## FILES
-                <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
-
+                <h5>
                 % if c.limited_diff:
-                    ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
+                    ${ungettext('%s file changed', '%s files changed', len(c.file_diff_data)) % len(c.file_diff_data)}:
                 % else:
-                    ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
+                    ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.file_diff_data)) % (len(c.file_diff_data),c.lines_added,c.lines_deleted)}:
                 %endif
 
                 ${c.ignorews_url(request.GET)}
                 ${c.context_url(request.GET)}
-
-                </div>
+                </h5>
                 <div class="cs_files">
-                  %if not c.files:
-                     <span class="empty_data">${_('No files')}</span>
+                  %if not c.file_diff_data:
+                     <span class="text-muted">${_('No files')}</span>
                   %endif
-                  %for fid, change, f, stat in c.files:
-                     <div class="cs_${change}">
-                         <div class="node">
-                             <i class="icon-diff-${change}"></i>
-                             ${h.link_to(h.safe_unicode(f), '#' + fid)}
-                         </div>
-                         <div class="changes">${h.fancy_file_stats(stat)}</div>
-                     </div>
+                  %for fid, url_fid, op, a_path, path, diff, stats in c.file_diff_data:
+                    <div class="cs_${op} clearfix">
+                      <span class="node">
+                          <i class="icon-diff-${op}"></i>
+                          ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                      </span>
+                      <div class="changes">${h.fancy_file_stats(stats)}</div>
+                    </div>
                   %endfor
+                  %if c.limited_diff:
+                    <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
+                  %endif
                 </div>
-                % if c.limited_diff:
-                  <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h5>
-                % endif
-         </div>
+        </div>
+      </div>
+    %endif
 
+    %if not c.compare_home:
         ## diff block
         <%namespace name="diff_block" file="/changeset/diff_block.html"/>
         ${diff_block.diff_block_js()}
-        %for fid, change, f, stat in c.files:
-          ${diff_block.diff_block_simple([c.changes[fid]])}
-        %endfor
+        ${diff_block.diff_block(c.a_repo.repo_name, c.a_ref_type, c.a_ref_name, c.a_rev,
+                                c.cs_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev, c.file_diff_data)}
         % if c.limited_diff:
           <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff')}</a></h4>
         % endif
@@ -97,27 +103,32 @@
    $(document).ready(function(){
     var cache = {};
 
-    function make_revision_dropdown(css_selector, placeholder, repo_name, cache_key) {
+    function make_revision_dropdown(css_selector, repo_name, ref_name, cache_key) {
       $(css_selector).select2({
-        placeholder: placeholder,
+        placeholder: '{0}@{1}'.format(repo_name, ref_name || ${h.jshtml(_('Select changeset'))}),
         formatSelection: function(obj){
             return '{0}@{1}'.format(repo_name, obj.text).html_escape();
         },
         dropdownAutoWidth: true,
+        maxResults: 50,
         query: function(query){
           var key = cache_key;
           var cached = cache[key] ;
           if(cached) {
             var data = {results: []};
+            var queryLower = query.term.toLowerCase();
             //filter results
             $.each(cached.results, function(){
                 var section = this.text;
                 var children = [];
                 $.each(this.children, function(){
-                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
+                    if(children.length < 50 ?
+                       ((queryLower.length == 0) || (this.text.toLowerCase().indexOf(queryLower) >= 0)) :
+                       ((queryLower.length != 0) && (this.text.toLowerCase().indexOf(queryLower) == 0))) {
                         children.push(this);
                     }
                 });
+                children = branchSort(children, undefined, query)
                 data.results.push({'text': section, 'children': children});
             });
             //push the typed in changeset
@@ -140,8 +151,8 @@
     });
     }
 
-    make_revision_dropdown("#compare_org",   ${h.js('%s@%s' % (c.a_repo.repo_name, c.a_ref_name))},   "${c.a_repo.repo_name}",  'cache');
-    make_revision_dropdown("#compare_other", ${h.js('%s@%s' % (c.cs_repo.repo_name, c.cs_ref_name))}, "${c.cs_repo.repo_name}", 'cache2');
+    make_revision_dropdown("#compare_org",   ${h.js(c.a_repo.repo_name)},  ${h.js(c.a_ref_name)},  'cache');
+    make_revision_dropdown("#compare_other", ${h.js(c.cs_repo.repo_name)}, ${h.js(c.cs_ref_name)}, 'cache2');
 
     var values_changed = function() {
         var values = $('#compare_org').select2('data') && $('#compare_other').select2('data');
@@ -163,7 +174,7 @@
             return;
         }
 
-        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.cs_repo.repo_name)}";
+        var compare_url = ${h.js(h.url('compare_url',repo_name=c.repo_name,org_ref_type='__other_ref_type__',org_ref_name='__org__',other_ref_type='__org_ref_type__',other_ref_name='__other__', other_repo=c.cs_repo.repo_name))};
         var u = compare_url.replace('__other_ref_type__',org.type)
                            .replace('__org__',org.text)
                            .replace('__org_ref_type__',other.type)
--- a/kallithea/templates/data_table/_dt_elements.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/data_table/_dt_elements.html	Sun Mar 31 21:28:56 2019 +0200
@@ -4,55 +4,7 @@
 
 <%namespace name="base" file="/base/base.html"/>
 
-<%def name="quick_menu(repo_name)">
-  <ul class="menu_items hidden">
-  ##<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
-
-    <li style="border-top:1px solid #577632; margin-left: 21px; padding-left: -99px;"></li>
-    <li>
-       <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
-       <span class="icon">
-           <i class="icon-doc-text-inv"></i>
-       </span>
-       <span>${_('Summary')}</span>
-       </a>
-    </li>
-    <li>
-       <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
-       <span class="icon">
-           <i class="icon-clock"></i>
-       </span>
-       <span>${_('Changelog')}</span>
-       </a>
-    </li>
-    <li>
-       <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
-       <span class="icon">
-           <i class="icon-docs"></i>
-       </span>
-       <span>${_('Files')}</span>
-       </a>
-    </li>
-    <li>
-       <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
-       <span class="icon">
-           <i class="icon-fork"></i>
-       </span>
-       <span>${_('Fork')}</span>
-       </a>
-    </li>
-    <li>
-       <a title="${_('Settings')}" href="${h.url('edit_repo',repo_name=repo_name)}">
-       <span class="icon">
-           <i class="icon-gear"></i>
-       </span>
-       <span>${_('Settings')}</span>
-       </a>
-    </li>
-  </ul>
-</%def>
-
-<%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
+<%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False)">
     <%
     def get_name(name,short_name=short_name):
       if short_name:
@@ -61,22 +13,15 @@
         return name
     %>
   <div class="dt_repo ${'dt_repo_pending' if rstate == 'repo_state_pending' else ''}">
-    ##NAME
-    <a href="${h.url('edit_repo' if admin else 'summary_home', repo_name=name)}">
-
-    ##TYPE OF REPO
-    ${base.repotag(rtype)}
-
-    ##PRIVATE/PUBLIC
+    ${base.repolabel(rtype)}
+    <a href="${h.url('summary_home', repo_name=name)}">
+        ${get_name(name)}
+    </a>
     %if private and c.visual.show_private_icon:
-      <i class="icon-keyhole-circled" title="${_('Private repository')}"></i>
+      <i class="icon-lock" title="${_('Private repository')}"></i>
     %elif not private and c.visual.show_public_icon:
       <i class="icon-globe" title="${_('Public repository')}"></i>
-    %else:
-      <span style="margin: 0px 8px 0px 8px"></span>
     %endif
-    <span class="dt_repo_name">${get_name(name)}</span>
-    </a>
     %if fork_of:
       <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-fork"></i></a>
     %endif
@@ -87,109 +32,96 @@
 </%def>
 
 <%def name="last_change(last_change)">
-  <span class="tooltip" date="${last_change}" title="${h.fmt_date(last_change)}">${h.age(last_change)}</span>
+  <span data-toggle="tooltip" title="${h.fmt_date(last_change)}" date="${last_change}">${h.age(last_change)}</span>
 </%def>
 
 <%def name="revision(name,rev,tip,author,last_msg)">
-  <div>
   %if rev >= 0:
-      <a title="${'%s:\n\n%s' % (h.escape(author), h.escape(last_msg))}" class="tooltip revision-link safe-html-title" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
+      <a data-toggle="popover" title="${author | entity}" data-content="${last_msg | entity}" class="changeset_hash" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
   %else:
       ${_('No changesets yet')}
   %endif
-  </div>
 </%def>
 
 <%def name="rss(name)">
-  %if c.authuser.username != 'default':
-    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
+  %if request.authuser.username != 'default':
+    <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
   %else:
     <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
   %endif
 </%def>
 
 <%def name="atom(name)">
-  %if c.authuser.username != 'default':
-    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
+  %if request.authuser.username != 'default':
+    <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
   %else:
     <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
   %endif
 </%def>
 
-<%def name="repo_actions(repo_name, super_user=True)">
-  <div>
-    <div style="float:left; margin-right:5px;" class="grid_edit">
-      <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
-        <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('Edit'),class_="action_button")}
+<%def name="repo_actions(repo_name)">
+      <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}" class="btn btn-default btn-xs">
+        <i class="icon-pencil"></i>${_('Edit')}
       </a>
-    </div>
-    <div style="float:left" class="grid_delete">
-      ${h.form(h.url('delete_repo', repo_name=repo_name), method='delete')}
-        <i class="icon-minus-circled" style="color:#FF4444"></i>
-        ${h.submit('remove_%s' % repo_name,_('Delete'),class_="action_button",
-        onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
+      ${h.form(h.url('delete_repo', repo_name=repo_name))}
+        <button name="${'remove_%s' % repo_name}" class="btn btn-default btn-xs"
+            onclick="return confirm('${_('Confirm to delete this repository: %s') % repo_name}');">
+          <i class="icon-trashcan"></i>${_('Delete')}
+        </button>
       ${h.end_form()}
-    </div>
-  </div>
 </%def>
 
 <%def name="repo_state(repo_state)">
-  <div>
-    %if repo_state == 'repo_state_pending':
-        <div class="btn btn-mini btn-info disabled">${_('Creating')}</div>
-    %elif repo_state == 'repo_state_created':
-        <div class="btn btn-mini btn-success disabled">${_('Created')}</div>
+    %if repo_state == u'repo_state_pending':
+        <div class="label label-info">${_('Creating')}</div>
+    %elif repo_state == u'repo_state_created':
+        <div class="label label-success">${_('Created')}</div>
     %else:
-        <div class="btn btn-mini btn-danger disabled" title="${repo_state}">invalid</div>
+        <div class="label label-danger" title="${repo_state}">invalid</div>
     %endif
-  </div>
 </%def>
 
 <%def name="user_actions(user_id, username)">
- <div style="float:left" class="grid_edit">
-   <a href="${h.url('edit_user',id=user_id)}" title="${_('Edit')}">
-     <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('Edit'),class_="action_button")}
+   <a href="${h.url('edit_user',id=user_id)}" title="${_('Edit')}" class="btn btn-default btn-xs">
+     <i class="icon-pencil"></i>${_('Edit')}
    </a>
- </div>
- <div style="float:left" class="grid_delete">
-  ${h.form(h.url('delete_user', id=user_id),method='delete')}
-    <i class="icon-minus-circled" style="color:#FF4444"></i>
-    ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="action_button",
-    onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
+  ${h.form(h.url('delete_user', id=user_id))}
+    <button id="${'remove_user_%s' % user_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
+        onclick="return confirm('${_('Confirm to delete this user: %s') % username}');">
+      <i class="icon-trashcan"></i>${_('Delete')}
+    </button>
   ${h.end_form()}
- </div>
 </%def>
 
 <%def name="user_group_actions(user_group_id, user_group_name)">
- <div style="float:left" class="grid_edit">
-    <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
-    <i class="icon-pencil"></i>
-     ${h.submit('edit_%s' % user_group_name,_('Edit'),class_="action_button", id_="submit_user_group_edit")}
+    <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}" class="btn btn-default btn-xs">
+      <i class="icon-pencil"></i>${_('Edit')}
     </a>
- </div>
- <div style="float:left" class="grid_delete">
-    ${h.form(h.url('users_group', id=user_group_id),method='delete')}
-      <i class="icon-minus-circled" style="color:#FF4444"></i>
-      ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="action_button",
-      onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
+    ${h.form(h.url('delete_users_group', id=user_group_id))}
+      <button id="${'remove_group_%s' % user_group_id}" name="${'remove_user_%s' % repo_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
+          onclick="return confirm('${_('Confirm to delete this user group: %s') % user_group_name}');">
+        <i class="icon-trashcan"></i>${_('Delete')}
+      </button>
     ${h.end_form()}
- </div>
+</%def>
+
+<%def name="group_name_html(group_name,name)">
+  <div class="dt_repo">
+    <i class="icon-folder"></i>
+    <a href="${h.url('repos_group_home',group_name=group_name)}">${name}</a>
+  </div>
 </%def>
 
 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
- <div style="float:left" class="grid_edit">
-    <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
-    <i class="icon-pencil"></i>
-     ${h.submit('edit_%s' % repo_group_name, _('Edit'),class_="action_button")}
+    <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}" class="btn btn-default btn-xs">
+      <i class="icon-pencil"></i>${_('Edit')}
     </a>
- </div>
- <div style="float:left" class="grid_delete">
-    ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
-        <i class="icon-minus-circled" style="color:#FF4444"></i>
-        ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="action_button",
-        onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
+    ${h.form(h.url('delete_repo_group', group_name=repo_group_name))}
+      <button id="${'remove_%s' % repo_group_name}" name="${'remove_%s' % repo_group_name}" class="btn btn-default btn-xs" title="${_('Delete')}"
+          onclick="return confirm('${ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)}')">
+        <i class="icon-trashcan"></i>${_('Delete')}
+      </button>
     ${h.end_form()}
- </div>
 </%def>
 
 <%def name="user_name(user_id, username)">
@@ -197,21 +129,15 @@
 </%def>
 
 <%def name="repo_group_name(repo_group_name, children_groups)">
-  <div style="white-space: nowrap">
+  <div class="text-nowrap">
   <a href="${h.url('repos_group_home',group_name=repo_group_name)}">
-    <i class="icon-folder" title="${_('Repository group')}"></i> ${h.literal(' &raquo; '.join(children_groups))}</a>
+    <i class="icon-folder" title="${_('Repository group')}"></i>${h.literal(' &raquo; '.join(children_groups))}</a>
   </div>
 </%def>
 
 <%def name="user_group_name(user_group_id, user_group_name)">
-  <div style="white-space: nowrap">
+  <div class="text-nowrap">
   <a href="${h.url('edit_users_group', id=user_group_id)}">
-    <i class="icon-users" title="${_('User group')}"></i> ${user_group_name}</a>
+    <i class="icon-users" title="${_('User group')}"></i>${user_group_name}</a>
   </div>
 </%def>
-
-<%def name="toggle_follow(repo_id)">
-  <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}"
-        onclick="toggleFollowingRepo(this, ${repo_id})">
-  </span>
-</%def>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/button.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,28 @@
+<%page args="url,title='',padding_top=True,padding_bottom=True" />\
+##<!-- button -->
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        %if padding_top:
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        %endif
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px ${color_button};padding:11px 20px 11px 20px">
+                <a href="${url}" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="${sans_style};font-weight:700;font-size:15px;line-height:14px;color:${color_button};white-space:nowrap;vertical-align:middle">${_(title)}</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+        %if padding_bottom:
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        %endif
+    </table>
+</center>
+##<!-- /button -->
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/button.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,3 @@
+<%page args="url,title" />\
+
+${title|n,unicode}: ${url}
--- a/kallithea/templates/email_templates/changeset_comment.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/changeset_comment.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,20 +1,39 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-%if is_mention:
-<p>${_('Comment from %s on %s changeset %s mentioned you') % (cs_comment_user, cs_target_repo, h.short_id(raw_id))}:</p>
-%else:
-<p>${_('Comment from %s on %s changeset %s') % (cs_comment_user, cs_target_repo, h.short_id(raw_id))}:</p>
-%endif
-<p>${body}</p>
-
-%if status_change:
-    <p>${_('The changeset status was changed to')}: <b>${status_change}</b></p>
-%endif
-
-<p>${_('URL')}: <a href="${cs_comment_url}">${cs_comment_url}</a></p>
-
-<p>${_('Changeset')}: ${h.short_id(raw_id)}</p>
-<p>${_('Description')}:<br/>
-${h.shorter(message, 256)}
-</p>
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<% title = _('Mention in Comment on Changeset "%s"') % h.shorter(message, 200, firstline=True) if is_mention else _('Comment on Changeset "%s"') % h.shorter(message, 200, firstline=True) %>\
+<%include file="header.html" args="title=title,link=cs_comment_url"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<%include file="comment.html" args="text=body,author=cs_comment_user,status=status_change"/>\
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                ${_('Changeset on')}
+                <a style="${link_text_style}"
+                   href="${cs_target_repo}">${cs_target_repo}</a>
+                ${_('branch')}
+                <span style="${data_style}">${branch}</span>:
+            </div>
+            <div>
+                "<a style="${link_style}"
+                   href="${cs_url}">${h.shorter(message, 60, firstline=True)}</a>"
+                ${_('by')}
+                <span style="${data_style}">${cs_author.full_name_and_username}</span>.
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<%include file="button.html" args="url=cs_comment_url,title=_('View Comment'),padding_bottom=False"/>\
+        </td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/changeset_comment.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/changeset_comment.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,19 +1,16 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
+<%block name="header">\
+<% title = _('Mention in Comment on Changeset "%s"') % h.shorter(message, 200, firstline=True) if is_mention else _('Comment on Changeset "%s"') % h.shorter(message, 200, firstline=True) %>\
+<%include file="header.txt" args="title=title,link=cs_comment_url"/>\
+</%block>\
 
-%if is_mention:
-${_('Comment from %s on %s changeset %s mentioned you') % (cs_comment_user, cs_target_repo, h.short_id(raw_id))|n,unicode}:
-%else:
-${_('Comment from %s on %s changeset %s') % (cs_comment_user, cs_target_repo, h.short_id(raw_id))|n,unicode}:
-%endif
-${body|n,unicode}
+<%include file="comment.txt" args="text=body,author=cs_comment_user,status=status_change"/>\
 
-%if status_change:
-${_('The changeset status was changed to')|n,unicode}: ${status_change|n,unicode}
-%endif
+${_('Changeset on')|n,unicode} \
+${cs_target_repo|n,unicode} \
+${_('branch')|n,unicode} \
+${branch|n,unicode}:
+"${h.shorter(message, 60, firstline=True)|n,unicode}" \
+${_('by')|n,unicode} \
+${cs_author.full_name_and_username|n,unicode}.
 
-${_('URL')|n,unicode}: ${cs_comment_url|n,unicode}
-
-${_('Changeset')|n,unicode}: ${h.short_id(raw_id)|n,unicode}
-${_('Description')|n,unicode}:
-${h.shorter(message, 256)|n,unicode}
+<%include file="button.txt" args="url=cs_comment_url,title=_('View Comment')"/>\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/comment.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,57 @@
+<%page args="author,text,status,close=False" />\
+\
+##<!-- comment ->
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="${color_background_grey}" style="border:1px solid ${color_border};border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="${emph_style}">${author}</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid ${color_border}"></td>
+    </tr>
+    %if status or close:
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                %if status:
+                    <div style="font-weight:600">
+                        ${_('Status change:')}
+                        ${status}
+                    </div>
+                %endif
+                %if close:
+                    <div style="font-weight:600">
+                        ${_('The pull request has been closed.')}
+                    </div>
+                %endif
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid ${color_border}"></td>
+        </tr>
+    %endif
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="${comment_style}">${text}</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+##<!-- /comment ->
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/comment.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,15 @@
+<%page args="author,text,status,close=False" />\
+${author|n,unicode}:
+
+%if status:
+${_('Status change:')|n,unicode} \
+${status|n,unicode}
+
+%endif
+%if close:
+${_('The pull request has been closed.')|n,unicode}
+
+%endif
+${text|n,unicode}
+
+## Trailing empty line
--- a/kallithea/templates/email_templates/default.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/default.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,11 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-${body}
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<%include file="header.html" args="title=_('Message'),link=None"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td style="${comment_style}">${body}</td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/default.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/default.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
-
-${body|n,unicode}
+<%block name="header">\
+</%block>\
+\
+${body|n,unicode}\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/header.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,25 @@
+<%page args="title,link" />\
+\
+##<!-- header -->
+<table bgcolor="${color_background_grey}" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid ${color_border}">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="${sans_style};font-size:19px;line-height:24px">
+        %if link is not None:
+            <a style="text-decoration:none;${emph_style}" href="${link}"
+               target="_blank">${title}</a>
+        %else:
+            <span style="${emph_style}">${title}</span>
+        %endif
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+##<!-- /header -->
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/templates/email_templates/header.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,8 @@
+<%page args="title,link" />\
+%if link is not None:
+${link}
+
+%endif
+${title|n,unicode}
+
+## Trailing empty line
--- a/kallithea/templates/email_templates/main.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/main.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,42 @@
-${self.body()}
-
-<br/>
-<br/>
--- <br/>
-${_("This is an automatic notification. Don't reply to this mail.")}
+<!doctype html>
+<html lang="en">
+<head>
+    <title></title>
+    <meta name="viewport" content="width=device-width">
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;${default_style}">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;${sans_style};border:1px solid ${color_border}">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<%block name="header"/>\
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+                        ##<!-- body -->
+${self.body()}\
+                        ##<!-- /body -->
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+</body>
+</html>
--- a/kallithea/templates/email_templates/main.txt	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-${self.body()|n,unicode}
-
--- 
-${_("This is an automatic notification. Don't reply to this mail.")|n,unicode}
--- a/kallithea/templates/email_templates/password_reset.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/password_reset.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,12 +1,47 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-<h4>${_('Hello %s') % user}</h4>
-
-<p>${_('We have received a request to reset the password for your account.')}</p>
-<p>${_('To set a new password, click the following link')}:</p>
-<p><a href="${reset_url}">${reset_url}</a></p>
-
-<p>${_("Should you not be able to use the link above, please type the following code into the password reset form")}: <code>${reset_token}</code></p>
-
-<p>${_("If it weren't you who requested the password reset, just disregard this message.")}</p>
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<%include file="header.html" args="title=_('Password Reset Request'),link=None"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%" style="table-layout:fixed;word-wrap:break-word;">
+    <tr>
+        <td>${_('Hello %s') % user},</td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+            ${_('We have received a request to reset the password for your account.')}
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+            %if reset_token is None:
+                <div>${_('This account is however managed outside this system and the password cannot be changed here.')}</div>
+            %else:
+                <div>
+                    ${_('To set a new password, click the following link')}:
+                    <br/>
+                    <a style="${link_style}" href="${reset_url}"
+                        target="_blank">${reset_url}</a>
+                    <br/>
+                    ${_("Should you not be able to use the link above, please type the following code into the password reset form")}:
+                    <code>${reset_token}</code>
+                </div>
+            %endif
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+            ${_("If it weren't you who requested the password reset, just disregard this message.")}
+        </td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/password_reset.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/password_reset.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,20 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
+<%block name="header">\
+<%include file="header.txt" args="title=_('Password Reset Request'),link=None"/>\
+</%block>\
+\
+${_('Hello %s') % user|n,unicode},
 
-${_('Hello %s') % user|n,unicode}
+${_('We have received a request to reset the password for your account.')|n,unicode}
 
-${_('We have received a request to reset the password for your account..')|n,unicode}
+%if reset_token is None:
+${_('This account is however managed outside this system and the password cannot be changed here.')|n,unicode}
+%else:
 ${_('To set a new password, click the following link')|n,unicode}:
 
 ${reset_url|n,unicode}
 
-${_("Should you not be able to use the link above, please type the following code into the password reset form")|n,unicode}: ${reset_token|n,unicode}
+${_("Should you not be able to use the link above, please type the following code into the password reset form")|n,unicode}:
+${reset_token|n,unicode}
+%endif
 
 ${_("If it weren't you who requested the password reset, just disregard this message.")|n,unicode}
--- a/kallithea/templates/email_templates/pull_request.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/pull_request.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,22 +1,88 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-%if is_mention:
-<p>${_('%s mentioned you on %s pull request "%s"') % (pr_user_created, repo_name, pr_title)}</p>
-%else:
-<p>${_('%s requested your review of %s pull request "%s"') % (pr_user_created, repo_name, pr_title)}</p>
-%endif
-
-<p>${_('URL')}: <a href="${pr_url}">${pr_url}</a></p>
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<% title = _('Mention on Pull Request %s "%s" by %s') % (pr_nice_id, pr_title, pr_user_created) if is_mention else _('Added as Reviewer of Pull Request %s "%s" by %s') % (pr_nice_id, pr_title, pr_user_created) %>\
+<%include file="header.html" args="title=title,link=pr_url"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+            <div>
+                ${_('Pull request')}
+                <a style="${link_style}"
+                   href="${pr_url}">${pr_nice_id} "${pr_title}"</a>
+                ${_('by')}
+                <span style="${data_style}">${pr_owner.full_name_and_username}</span>.
+            </div>
+            <div>
+                ${_('from')}
+                <a style="${link_text_style}"
+                   href="${pr_source_repo}">${pr_source_repo}</a>
+                ${_('branch')}
+                <span style="${data_style}">${pr_source_branch}</span>
+                <br/>
+                ${_('to')}
+                <a style="${link_text_style}"
+                   href="${pr_target_repo}">${pr_target_repo}</a>
+                ${_('branch')}
+                <span style="${data_style}">${pr_target_branch}</span>
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <div>
+                ${_('Description')}:
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="${color_background_grey}" style="border:1px solid ${color_border};border-radius:4px">
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td width="20px" style="width:20px"></td>
+                    <td>
+                        <div style="${comment_style}">${body}</div>
+                    </td>
+                    <td width="20px" style="width:20px"></td>
+                </tr>
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+    <tr><td height="15px" style="height:15px"></td></tr>
+    <tr>
+        <td>
+            <div>${_('Changesets')}:</div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
 
-<p>${_('Description')}:</p>
-<p style="white-space: pre-wrap;">${body}</p>
-
-<p>${_('Changesets')}:</p>
-<p style="white-space: pre-wrap;">
-%for r,r_msg in pr_revisions:
-<i>${h.short_id(r)}</i>:
-${h.shorter(r_msg, 256)}
-
-%endfor
-</p>
+    <tr>
+        <td style="${sans_style}">
+            <ul style="color:${color_link};padding-left:15px;margin:0">
+                %for revision, desc in pr_revisions:
+                    <li style="mso-special-format:bullet">
+                        <a style="${link_style}"
+                           href="${h.canonical_url('changeset_home', repo_name=org_repo_name, revision=revision)}">
+                            ${h.shorter(desc, 80, firstline=True)}
+                        </a>
+                    </li>
+                %endfor
+            </ul>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<%include file="button.html" args="url=pr_url,title=_('View Pull Request'),padding_bottom=False"/>\
+        </td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/pull_request.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/pull_request.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,20 +1,32 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
+<%block name="header">\
+<% title = _('Mention on Pull Request %s "%s" by %s') % (pr_nice_id, pr_title, pr_user_created) if is_mention else _('Added as Reviewer of Pull Request %s "%s" by %s') % (pr_nice_id, pr_title, pr_user_created) %>\
+<%include file="header.txt" args="title=title,link=pr_url"/>\
+</%block>\
 
-%if is_mention:
-${_('%s mentioned you on %s pull request "%s"') % (pr_user_created, repo_name, pr_title)|n,unicode}
-%else:
-${_('%s requested your review of %s pull request "%s"') % (pr_user_created, repo_name, pr_title)|n,unicode}
-%endif
+${_('Pull request')|n,unicode} \
+${pr_nice_id|n,unicode} \
+"${pr_title|n,unicode}" \
+${_('by')|n,unicode} \
+${pr_owner.full_name_and_username|n,unicode}
+${_('from')} \
+${pr_source_repo|n,unicode} \
+${_('branch')|n,unicode} \
+${pr_source_branch|n,unicode}
+${_('to')|n,unicode} \
+${pr_target_repo|n,unicode} \
+${_('branch')|n,unicode} \
+${pr_target_branch|n,unicode}
 
-${_('URL')|n,unicode}: ${pr_url|n,unicode}
 
 ${_('Description')|n,unicode}:
+
 ${body|n,unicode}
 
+
 ${_('Changesets')|n,unicode}:
-%for r,r_msg in pr_revisions:
-${h.short_id(r)|n,unicode}:
-${h.shorter(r_msg, 256)|n,unicode}
 
+%for revision, desc in pr_revisions:
+${h.shorter(desc, 80, firstline=True)|n,unicode}
 %endfor
+
+<%include file="button.txt" args="url=pr_url,title='View Pull Request'"/>\
--- a/kallithea/templates/email_templates/pull_request_comment.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/pull_request_comment.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,15 +1,46 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-<p>${_('Comment from %s on %s pull request "%s"') % (pr_comment_user, repo_name, pr_title)}:</p>
-<p>${body}</p>
-
-%if status_change:
-    %if closing_pr:
-       <p>${_('The comment closed the pull request with status')}: <b>${status_change}</b></p>
-    %else:
-       <p>${_('The comment was made with status')}: <b>${status_change}</b></p>
-    %endif
-%endif
-
-<p>${_('URL')}: <a href="${pr_comment_url}">${pr_comment_url}</a></p>
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<% title = _('Mention in Comment on Pull Request %s "%s"') % (pr_nice_id, pr_title) if is_mention else _('Pull Request %s "%s" Closed') % (pr_nice_id, pr_title) if closing_pr else _('Comment on Pull Request %s "%s"') % (pr_nice_id, pr_title) %>\
+<%include file="header.html" args="title=title,link=pr_comment_url"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<%include file="comment.html" args="text=body,author=pr_comment_user,status=status_change,close=closing_pr"/>\
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                ${_('Pull request')}
+                <a style="${link_style}"
+                   href="${pr_url}">${pr_nice_id} "${pr_title}"</a>
+                ${_('by')}
+                <span style="${data_style}">${pr_owner.full_name_and_username}</span>.
+            </div>
+            <div>
+                ${_('from')}
+                <a style="${link_text_style}"
+                   href="${pr_source_repo}">${pr_source_repo}</a>
+                ${_('branch')}
+                <span style="${data_style}">${pr_source_branch}</span>
+                <br/>
+                ${_('to')}
+                <a style="${link_text_style}"
+                   href="${pr_target_repo}">${pr_target_repo}</a>
+                ${_('branch')}
+                <span style="${data_style}">${pr_target_branch}</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<%include file="button.html" args="url=pr_comment_url,title=_('View Comment'),padding_bottom=False"/>\
+        </td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/pull_request_comment.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/pull_request_comment.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,15 +1,22 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
+<%block name="header">\
+<% title = _('Mention in Comment on Pull Request %s "%s"') % (pr_nice_id, pr_title) if is_mention else _('Pull Request %s "%s" Closed') % (pr_nice_id, pr_title) if closing_pr else _('Comment on Pull Request %s "%s"') % (pr_nice_id, pr_title) %>\
+<%include file="header.txt" args="title=title,link=pr_comment_url"/>\
+</%block>\
 
-${_('Comment from %s on %s pull request "%s"') % (pr_comment_user, repo_name, pr_title)|n,unicode}:
-${body|n,unicode}
+<%include file="comment.txt" args="text=body,author=pr_comment_user,status=status_change,close=closing_pr"/>\
 
-%if status_change:
-    %if closing_pr:
-${_('The comment closed the pull request with status')|n,unicode}: ${status_change|n,unicode}
-    %else:
-${_('The comment was made with status')|n,unicode}: ${status_change|n,unicode}
-    %endif
-%endif
+${_('Pull request')|n,unicode} \
+${pr_nice_id|n,unicode} \
+"${pr_title|n,unicode}" \
+${_('by')|n,unicode} \
+${pr_owner.full_name_and_username|n,unicode}
+${_('from')} \
+${pr_source_repo|n,unicode} \
+${_('branch')|n,unicode} \
+${pr_source_branch|n,unicode}
+${_('to')|n,unicode} \
+${pr_target_repo|n,unicode} \
+${_('branch')|n,unicode} \
+${pr_target_branch|n,unicode}
 
-${_('URL')|n,unicode}: ${pr_comment_url|n,unicode}
+<%include file="button.txt" args="url=pr_comment_url,title=_('View Comment')"/>\
--- a/kallithea/templates/email_templates/registration.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/registration.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,44 @@
 ## -*- coding: utf-8 -*-
-<%inherit file="main.html"/>
-
-${body}
-
-${_('View this user here')}: <a href="${registered_user_url}">${registered_user_url}</a>
+<%inherit file="main.html"/>\
+\
+<%block name="header">\
+<%include file="header.html" args="title=_('New User Registration'),link=registered_user_url"/>\
+</%block>\
+\
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+            ${_('Username')}:
+        </td>
+        <td style="${data_style}">
+            ${new_username}
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="2"></td>
+    </tr>
+    <tr>
+        <td>
+            ${_('Full Name')}:
+        </td>
+        <td style="${data_style}">
+            ${new_full_name}
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="2"></td>
+    </tr>
+    <tr>
+        <td>
+            ${_('Email')}:
+        </td>
+        <td style="${data_style}">
+            ${new_email}
+        </td>
+    </tr>
+    <tr>
+        <td colspan="2">
+<%include file="button.html" args="url=registered_user_url,title=_('View User Profile'),padding_bottom=False"/>\
+        </td>
+    </tr>
+</table>
--- a/kallithea/templates/email_templates/registration.txt	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/email_templates/registration.txt	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,11 @@
-## -*- coding: utf-8 -*-
-<%inherit file="main.txt"/>
+<%block name="header">\
+<%include file="header.txt" args="title=_('New User Registration'),link=registered_user_url"/>\
+</%block>\
+
+${_('Username')|n,unicode}: ${new_username|n,unicode}
 
-${body|n,unicode}
+${_('Full Name')|n,unicode}: ${new_full_name|n,unicode}
 
-${_('View this user here')|n,unicode}: ${registered_user_url|n,unicode}
+${_('Email')|n,unicode}: ${new_email|n,unicode}
+
+<%include file="button.txt" args="url=registered_user_url,title='View User Profile'"/>\
--- a/kallithea/templates/errors/error_document.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/errors/error_document.html	Sun Mar 31 21:28:56 2019 +0200
@@ -11,7 +11,6 @@
 
         <!-- stylesheets -->
         <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
-        <link rel="stylesheet" type="text/css" href="${h.url('/css/contextbar.css')}" media="screen"/>
         <style type="text/css">
          #main_div{
            border: 0px solid #000;
@@ -32,16 +31,14 @@
     </head>
     <body>
         <%include file="/base/flash_msg.html"/>
-        <div id="login">
-            <div class="table">
+        <div>
                 <div id="main_div">
-                    <div style="font-size:2.0em;margin: 10px">${c.site_name}</div>
+                    <h2>${c.site_name}</h2>
                     <h1 class="error_message">${c.error_message}</h1>
 
                     <p>${c.error_explanation}</p>
 
                 </div>
-            </div>
         </div>
     </body>
 
--- a/kallithea/templates/files/diff_2way.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/diff_2way.html	Sun Mar 31 21:28:56 2019 +0200
@@ -25,56 +25,55 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div class="diff-container" style="overflow-x: hidden">
-        <div class="diffblock comm" style="margin:3px; padding:1px">
-            <div class="code-header">
-                <div class="changeset_header">
-                    <div class="changeset_file">
+    <div class="no-padding panel-body overflow-x-visible">
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                    <div class="pull-left">
                         ${h.link_to(h.safe_unicode(c.node1.path),h.url('files_home',repo_name=c.repo_name,
                         revision=c.cs2.raw_id,f_path=h.safe_unicode(c.node1.path)))}
                     </div>
-                    <div class="diff-actions">
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${_('Show full diff for this file')}">
-                          <i class="icon-file-code"></i>
-                      </a>
-                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}" class="tooltip" title="${_('Show full side-by-side diff for this file')}">
-                          <i class="icon-docs"></i>
-                      </a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}" class="tooltip" title="${_('Raw diff')}">
-                          <i class="icon-diff"></i>
-                      </a>
-                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}" class="tooltip" title="${_('Download diff')}">
-                          <i class="icon-floppy"></i>
-                      </a>
+                    <div class="pull-left diff-actions">
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
+                         data-toggle="tooltip"
+                         title="${_('Show full diff for this file')}">
+                          <i class="icon-file-code"></i></a>
+                      <a href="${h.url('files_diff_2way_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='diff',fulldiff=1)}"
+                         data-toggle="tooltip"
+                         title="${_('Show full side-by-side diff for this file')}">
+                          <i class="icon-docs"></i></a>
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='raw')}"
+                         data-toggle="tooltip"
+                         title="${_('Raw diff')}"><i class="icon-diff"></i></a>
+                      <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),diff2=c.cs2.raw_id,diff1=c.cs1.raw_id,diff='download')}"
+                         data-toggle="tooltip"
+                         title="${_('Download diff')}"><i class="icon-floppy"></i></a>
                       ${h.checkbox('ignorews', label=_('Ignore whitespace'))}
                       ${h.checkbox('edit_mode', label=_('Edit'))}
                     </div>
-                </div>
             </div>
-            <div id="compare"></div>
+            <div id="compare" class="overflow-x-visible"></div>
         </div>
     </div>
 
 <script>
-var orig1_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id)}';
-var orig2_url = '${h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id)}';
+var orig1_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node1.path),revision=c.cs1.raw_id))};
+var orig2_url = ${h.jshtml(h.url('files_raw_home',repo_name=c.repo_name,f_path=h.safe_unicode(c.node2.path),revision=c.cs2.raw_id))};
 
 $(document).ready(function () {
     $('#compare').mergely({
         width: 'auto',
-        height: '600',
+        height: $(window).height() - $('#compare').offset().top - $('#footer').height() - 35,
         fgcolor: {a:'#ddffdd',c:'#cccccc',d:'#ffdddd'},
         bgcolor: '#fff',
         viewport: true,
         cmsettings: {mode: 'text/plain', readOnly: true, lineWrapping: false, lineNumbers: true},
         lhs: function(setValue) {
-            if("${c.node1.is_binary}" == "True"){
+            if (${h.js(c.node1.is_binary)}) {
                 setValue('Binary file');
             }
             else{
@@ -83,7 +82,7 @@
 
         },
         rhs: function(setValue) {
-            if("${c.node2.is_binary}" == "True"){
+            if (${h.js(c.node2.is_binary)}) {
                 setValue('Binary file');
             }
             else{
@@ -91,6 +90,9 @@
             }
         }
     });
+    // recompute width - avoids overflow and horizontal scrollbar
+    $('#compare').mergely('resize');
+
     $('#ignorews').change(function(e){
         var val = e.currentTarget.checked;
         $('#compare').mergely('options', {ignorews: val});
--- a/kallithea/templates/files/file_diff.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/file_diff.html	Sun Mar 31 21:28:56 2019 +0200
@@ -14,16 +14,16 @@
 
 <%def name="main()">
 ${self.repo_context_bar('changelog')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <div>
+    <div class="panel-body">
     ## diff block
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
-    ${diff_block.diff_block(c.changes)}
+    ${diff_block.diff_block(c.repo_name, 'rev', c.a_rev, c.a_rev,
+                            c.repo_name, 'rev', c.cs_rev, c.cs_rev, c.file_diff_data)}
     </div>
 </div>
 </%def>
--- a/kallithea/templates/files/files.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files.html	Sun Mar 31 21:28:56 2019 +0200
@@ -20,17 +20,16 @@
 
 <%def name="main()">
 ${self.repo_context_bar('files', c.revision)}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-            <li style="color:white">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
               ${_("Branch filter:")} ${h.select('branch_selector',c.changeset.raw_id,c.revision_options)}
-            </li>
-        </ul>
+        </div>
     </div>
-    <div class="table">
+    <div class="panel-body">
         <div id="files_data">
             <%include file='files_ypjax.html'/>
         </div>
@@ -41,29 +40,27 @@
 var CACHE = {};
 var CACHE_EXPIRE = 5*60*1000; //cache for 5*60s
 //used to construct links from the search list
-var url_base = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
+var url_base = ${h.js(h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__'))};
 //send the nodelist request to this url
-var node_list_url = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
-// send the node history requst to this url
-var node_history_url = '${h.url("files_history_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
+var node_list_url = ${h.js(h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__'))};
 
 ## new pyroutes URLs
-pyroutes.register('files_nodelist_home', "${h.url('files_nodelist_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
-pyroutes.register('files_history_home', "${h.url('files_history_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
-pyroutes.register('files_authors_home', "${h.url('files_authors_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s')}", ['revision', 'f_path']);
+pyroutes.register('files_nodelist_home', ${h.js(h.url('files_nodelist_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
+pyroutes.register('files_history_home', ${h.js(h.url('files_history_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
+pyroutes.register('files_authors_home', ${h.js(h.url('files_authors_home', repo_name=c.repo_name,revision='%(revision)s',f_path='%(f_path)s'))}, ['revision', 'f_path']);
 
 var ypjax_links = function(){
     $('.ypjax-link').click(function(e){
 
         //don't do ypjax on middle click
-        if(e.which == 2 || !History.enabled){
+        if (e.which == 2) {
             return true;
         }
 
         var el = e.currentTarget;
         var url = el.href;
 
-        var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
+        var _base_url = ${h.jshtml(h.url("files_home",repo_name=c.repo_name,revision='',f_path=''))};
         _base_url = _base_url.replace('//','/');
 
         //extract rev and the f_path from url.
@@ -77,15 +74,16 @@
         var f_path = parts2.join('/');
 
         //page title - make this consistent with title mako block above
-        var title = "${_('%s Files') % c.repo_name}" + " \u00B7 " + (f_path || '/') + " \u00B7 " + "${c.site_name}";
+        var title = ${h.jshtml(_('%s Files') % c.repo_name)} + " \u00B7 " + (f_path || '/') + " \u00B7 " + ${h.jshtml(c.site_name)};
 
         var _node_list_url = node_list_url.replace('__REV__',rev).replace('__FPATH__', f_path);
         var _url_base = url_base.replace('__REV__',rev);
 
         // Change our States and save some data for handling events
-        var data = {url:url,title:title, url_base:_url_base,
-                    node_list_url:_node_list_url, rev:rev, f_path:f_path};
-        History.pushState(data, title, url);
+        var state = {url:url, title:title, url_base:_url_base,
+                     node_list_url:_node_list_url, rev:rev, f_path:f_path};
+        window.history.pushState(state, null, url);
+        load_state(state);
 
         //now we're sure that we can do ypjax things
         e.preventDefault();
@@ -93,20 +91,39 @@
     });
 }
 
-// callbacks needed to process the ypjax filebrowser
-var callbacks = function(State){
+var load_state = function(state) {
+    var $files_data = $('#files_data');
+    var cache_key = state.url;
+    var _cache_obj = CACHE[cache_key];
+    var _cur_time = new Date().getTime();
+    if (_cache_obj !== undefined && _cache_obj[0] > _cur_time) {
+        $files_data.html(_cache_obj[1]);
+        $files_data.css('opacity', '1.0');
+        post_load_state(state);
+    } else {
+        asynchtml(State.url, $files_data, function() {
+                  post_load_state(state);
+                  var expire_on = new Date().getTime() + CACHE_EXPIRE;
+                  CACHE[cache_key] = [expire_on, $files_data.html()];
+            });
+    }
+}
+
+var post_load_state = function(state) {
     ypjax_links();
     tooltip_activate();
 
-    if(State !== undefined){
-        //inistially loaded stuff
-        var _f_path = State.data.f_path;
-        var _rev = State.data.rev;
+    if(state !== undefined) {
+        document.title = state.title;
 
-        fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
+        //initially loaded stuff
+        var _f_path = state.f_path;
+        var _rev = state.rev;
+
+        fileBrowserListeners(state.node_list_url, state.url_base);
         // Inform Google Analytics of the change
         if ( typeof window.pageTracker !== 'undefined' ) {
-            window.pageTracker._trackPageview(State.url);
+            window.pageTracker._trackPageview(state.url);
         }
     }
 
@@ -149,22 +166,30 @@
     $("#diff1").select2({
         placeholder: _TM['Select changeset'],
         dropdownAutoWidth: true,
+        maxResults: 50,
         query: function(query){
           var key = 'cache';
           var cached = cache[key] ;
           if(cached) {
             var data = {results: []};
+            var queryLower = query.term.toLowerCase();
             //filter results
             $.each(cached.results, function(){
                 var section = this.text;
                 var children = [];
                 $.each(this.children, function(){
-                    if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
-                        children.push({'id': this.id, 'text': this.text});
+                    if(children.length < 50 ?
+                       ((queryLower.length == 0) || (this.text.toLowerCase().indexOf(queryLower) >= 0)) :
+                       ((queryLower.length != 0) && (this.text.toLowerCase().indexOf(queryLower) == 0))) {
+                        children.push(this);
                     }
                 });
+                children = branchSort(children, undefined, query)
                 data.results.push({'text': section, 'children': children});
             });
+            //push the typed in changeset
+            data.results.push({'text':_TM['Specify changeset'],
+                               'children': [{'id': query.term, 'text': query.term, 'type': 'rev'}]});
             query.callback(data);
           }else{
               $.ajax({
@@ -174,7 +199,7 @@
                 type: 'GET',
                 success: function(data) {
                   cache[key] = data;
-                  query.callback({results: data.results});
+                  query.callback(data);
                 }
               });
           }
@@ -194,56 +219,32 @@
 
 $(document).ready(function(){
     ypjax_links();
-    var $files_data = $('#files_data');
-    //Bind to StateChange Event
-    History.Adapter.bind(window,'statechange',function(){
-        var State = History.getState();
-        cache_key = State.url;
-        //check if we have this request in cache maybe ?
-        var _cache_obj = CACHE[cache_key];
-        var _cur_time = new Date().getTime();
-        // get from cache if it's there and not yet expired !
-        if(_cache_obj !== undefined && _cache_obj[0] > _cur_time){
-            $files_data.html(_cache_obj[1]);
-            $files_data.css('opacity','1.0');
-            //callbacks after ypjax call
-            callbacks(State);
-        }
-        else{
-            asynchtml(State.url, $files_data, function(){
-                    callbacks(State);
-                    var expire_on = new Date().getTime() + CACHE_EXPIRE;
-                    CACHE[cache_key] = [expire_on, $files_data.html()];
-                });
-        }
+
+    // Process history navigation event and load its state
+    window.addEventListener('popstate', function(e){
+        if (e.state)
+            load_state(e.state);
     });
 
     // init the search filter
-    var _State = {
-       url: "${h.url.current()}",
-       data: {
-         node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
-         url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
-         rev:"${c.changeset.raw_id}",
-         f_path: "${h.safe_unicode(c.file.path)}"
-       }
-    }
-    fileBrowserListeners(_State.url, _State.data.node_list_url, _State.data.url_base);
+    var _node_list_url = node_list_url.replace('__REV__', ${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(h.safe_unicode(c.file.path))});
+    var _url_base = url_base.replace('__REV__', ${h.js(c.changeset.raw_id)});
+    fileBrowserListeners(_node_list_url, _url_base);
 
     // change branch filter
     $("#branch_selector").select2({
         dropdownAutoWidth: true,
-        minimumInputLength: 1,
+        maxResults: 50,
         sortResults: branchSort
         });
 
     $("#branch_selector").change(function(e){
         var selected = e.currentTarget.options[e.currentTarget.selectedIndex].value;
-        if(selected && selected != "${c.changeset.raw_id}"){
-            window.location = pyroutes.url('files_home', {'repo_name': "${h.safe_unicode(c.repo_name)}", 'revision': selected, 'f_path': "${h.safe_unicode(c.file.path)}"});
-            $("#body.browserblock").hide();
+        if(selected && selected != ${h.js(c.changeset.raw_id)}){
+            window.location = pyroutes.url('files_home', {'repo_name': ${h.js(h.safe_unicode(c.repo_name))}, 'revision': selected, 'f_path': ${h.js(h.safe_unicode(c.file.path))}});
+            $("#body").hide();
         } else {
-            $("#branch_selector").val("${c.changeset.raw_id}");
+            $("#branch_selector").val(${h.js(c.changeset.raw_id)});
         }
     });
 
--- a/kallithea/templates/files/files_add.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_add.html	Sun Mar 31 21:28:56 2019 +0200
@@ -23,93 +23,101 @@
 
 <%def name="main()">
 ${self.repo_context_bar('files')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-            <li>
-              <span><a href="#">${_('Branch')}: ${c.cs.branch}</a></span>
-            </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="#">${_('Branch')}: ${c.cs.branch}</a>
+        </div>
     </div>
-    <div class="table" id="edit">
+    <div class="panel-body" id="edit">
         <div id="files_data">
-          ${h.form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")}
-          <h3 class="files_location">
-            ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.f_path)} /
-              <span id="filename_container" class="file reviewer_ac">
-                  <input class="input-small" type="text" value="" size="30" name="filename" id="filename" placeholder="${_('Enter filename...')}">
-                  <input type="hidden" value="${c.f_path}" size="30" name="location" id="location">
-                  ${_('or')} <div class="btn btn-small" id="upload_file_enable">${_('Upload File')}</div>
+            ${h.form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-inline")}
+            <h3 class="files_location">
+              ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.f_path)} /
+              <span id="filename_container" class="file">
+                  <input class="form-control input-small" type="text" value="" size="30" name="filename" id="filename" placeholder="${_('Enter filename...')}">
+                  <input type="hidden" value="${c.f_path}" name="location" id="location">
+                  ${_('or')} <button type="button" class="btn btn-default btn-sm" id="upload_file_enable">${_('Upload File')}</button>
+              </span>
+              <span id="upload_file_container" style="display:none">
+                  <input class="form-control" type="file" size="20" name="upload_file" id="upload_file">
+                  ${_('or')} <button type="button" class="btn btn-default btn-sm" id="file_enable">${_('Create New File')}</button>
               </span>
-              <span id="upload_file_container" class="reviewer_ac" style="display:none">
-                  <input type="file"  size="20" name="upload_file" id="upload_file">
-                  ${_('or')} <div class="btn btn-small" id="file_enable">${_('Create New File')}</div>
-              </span>
-          </h3>
-            <div id="body" class="codeblock">
-            <div class="code-header" id="set_mode_header">
-                <label class="commit" for="set_mode">${_('New file mode')}</label>
-                <select id="set_mode" name="set_mode"/>
+            </h3>
+            <div id="body" class="panel panel-default">
+              <div class="panel-heading clearfix">
+                  <div class="pull-left">
+                    <label>${_('New file type')}
+                        <select class="form-control" id="mimetype" name="mimetype"></select>
+                    </label>
+                  </div>
+              </div>
+              <div class="panel-body no-padding">
+                <textarea id="editor" name="content" style="display:none"></textarea>
+              </div>
             </div>
-                <div id="editor_container">
-                    <pre id="editor_pre"></pre>
-                    <textarea id="editor" name="content" style="display:none"></textarea>
-                </div>
-                <div style="padding: 10px;color:#666666">${_('Commit Message')}</div>
-                <textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
-            </div>
-            <div style="text-align: left;padding-top: 5px">
-            ${h.submit('commit',_('Commit Changes'),class_="btn btn-small btn-success")}
-            ${h.reset('reset',_('Reset'),class_="btn btn-small")}
+            <div>
+              <div>
+                  <div>${_('Commit Message')}</div>
+                  <textarea class="form-control" name="message" placeholder="${c.default_message}"></textarea>
+              </div>
+              <div class="buttons">
+                ${h.submit('commit',_('Commit Changes'),class_="btn btn-success")}
+                ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+              </div>
             </div>
             ${h.end_form()}
             <script type="text/javascript">
-            var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path)}";
-            var myCodeMirror = initCodeMirror('editor', "${request.script_name}", reset_url);
+                $(document).ready(function(){
+                    var reset_url = ${h.jshtml(h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))};
+                    var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, reset_url);
 
-            //inject new modes, based on codeMirrors modeInfo object
-            $('#set_mode').each(function(){
-                var modes_select = this;
-                var index = 1;
-                for(var i=0;i<CodeMirror.modeInfo.length;i++){
-                    var m = CodeMirror.modeInfo[i];
-                    var opt = new Option(m.name, m.mime);
-                    $(opt).attr('mode', m.mode);
-                    if (m.mime == 'text/plain') {
-                        // default plain text
-                        $(opt).prop('selected', true);
-                        modes_select.options[0] = opt;
-                    } else {
-                        modes_select.options[index++] = opt;
-                    }
-                }
-            });
-            $('#set_mode').change(function(e){
-                var selected = e.currentTarget;
-                var node = selected.options[selected.selectedIndex];
-                var detected_mode = CodeMirror.findModeByMIME(node.value);
-                setCodeMirrorMode(myCodeMirror, detected_mode);
+                    //inject new modes, based on codeMirrors modeInfo object
+                    var $mimetype_select = $('#mimetype');
+                    $mimetype_select.each(function(){
+                        var modes_select = this;
+                        var index = 1;
+                        for(var i=0;i<CodeMirror.modeInfo.length;i++){
+                            var m = CodeMirror.modeInfo[i];
+                            var opt = new Option(m.name, m.mime);
+                            $(opt).attr('mode', m.mode);
+                            if (m.mime == 'text/plain') {
+                                // default plain text
+                                $(opt).prop('selected', true);
+                                modes_select.options[0] = opt;
+                            } else {
+                                modes_select.options[index++] = opt;
+                            }
+                        }
+                    });
+                    var $filename_input = $('#filename');
+                    $mimetype_select.change(function(e){
+                        var selected = e.currentTarget;
+                        var node = selected.options[selected.selectedIndex];
+                        var detected_mode = CodeMirror.findModeByMIME(node.value);
+                        setCodeMirrorMode(myCodeMirror, detected_mode);
 
-                var filenameInput = $('#filename');
-                var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
-                var file_data = CodeMirror.getFilenameAndExt(filenameInput.val());
-                var filename = file_data['filename'] || 'filename1';
-                filenameInput.val(filename + '.' + proposed_ext);
-            });
+                        var proposed_ext = CodeMirror.findExtensionByMode(detected_mode);
+                        var file_data = CodeMirror.getFilenameAndExt($filename_input.val());
+                        var filename = file_data['filename'] || 'filename1';
+                        $filename_input.val(filename + '.' + proposed_ext);
+                    });
 
-            // on type the new filename set mode
-            $('#filename').keyup(function(e){
-                var file_data = CodeMirror.getFilenameAndExt(this.value);
-                if(file_data['ext'] != null){
-                    var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
-                    if (detected_mode){
-                        setCodeMirrorMode(myCodeMirror, detected_mode);
-                        $('#set_mode').val(detected_mode.mime);
-                    }
-                }
-            });
+                    // on type the new filename set mode
+                    $filename_input.keyup(function(e){
+                        var file_data = CodeMirror.getFilenameAndExt(this.value);
+                        if(file_data['ext'] != null){
+                            var detected_mode = CodeMirror.findModeByExtension(file_data['ext']) || CodeMirror.findModeByMIME('text/plain');
+                            if (detected_mode){
+                                setCodeMirrorMode(myCodeMirror, detected_mode);
+                                $mimetype_select.val(detected_mode.mime);
+                            }
+                        }
+                    });
+                });
             </script>
         </div>
     </div>
--- a/kallithea/templates/files/files_browser.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_browser.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,3 +1,5 @@
+<%namespace name="base" file="/base/base.html"/>
+
 <%def name="file_class(node)">
     %if node.is_file():
         <%return "browser-file" %>
@@ -24,26 +26,18 @@
     %>
     <%return h.literal('<i class="%s"></i><span>%s</span>' % (c, h.escape(node.name)))%>
 </%def>
-<div id="body" class="browserblock">
-    <div class="browser-header">
-        <div class="browser-nav">
-            ${h.form(h.url.current())}
-            <div class="info_box">
-              <div class="info_box_elem rev">${_('Revision')}</div>
-              <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_prev}" title="${_('Previous revision')}"><i class="icon-left-open"></i></a></div>
-              <div class="info_box_elem">${h.text('at_rev',value=c.changeset.revision,size=5)}</div>
-              <div class="info_box_elem"><a class="btn btn-mini ypjax-link" href="${c.url_next}" title="${_('Next revision')}"><i class="icon-right-open"></i></a></div>
-            </div>
-            ${h.end_form()}
+<div id="body" class="panel panel-default">
+    <div class="panel-heading clearfix">
+        ${base.parent_child_navigation()}
+    </div>
+
+    <div class="panel-body">
+        ${h.form(h.url.current())}
+        <div id="search_activate_id" class="search_activate">
+            <a class="btn btn-default btn-xs" id="filter_activate" href="#">${_('Search File List')}</a>
         </div>
-        <div class="browser-branch">
-           ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
-           <label>${_('Follow current branch')}</label>
-        </div>
-        <div id="search_activate_id" class="search_activate">
-           <a class="btn btn-mini" id="filter_activate" href="#">${_('Search File List')}</a>
-        </div>
-        <div class="browser-search">
+        ${h.end_form()}
+        <div class="browser-search form-inline">
             <div>
                 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
                 <div id="node_filter_box" style="display:none">
@@ -51,10 +45,7 @@
                 </div>
             </div>
         </div>
-    </div>
-
-    <div class="browser-body">
-        <table class="code-browser">
+        <table class="table code-browser">
             <thead>
                 <tr>
                     <th>${_('Name')}</th>
@@ -81,7 +72,7 @@
             %for cnt,node in enumerate(c.file):
                 <tr class="parity${cnt%2}">
                      <td>
-                         ${h.link_to(file_name(node),file_url(node,c),class_=file_class(node)+" ypjax-link")}
+                         ${h.link_to(file_name(node),file_url(node,c),class_=file_class(node)+(" ypjax-link" if not node.is_submodule() else ""), target_="_blank" if node.is_submodule() else None)}
                      </td>
                      <td>
                      %if node.is_file():
@@ -90,12 +81,12 @@
                      </td>
                      <td>
                          %if node.is_file():
-                             <a title="${node.last_changeset.message}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=node.last_changeset.raw_id)}" class="tooltip revision-link">${h.show_id(node.last_changeset)}</a>
+                             <a data-toggle="tooltip" title="${node.last_changeset.message}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=node.last_changeset.raw_id)}" class="changeset_hash">${h.show_id(node.last_changeset)}</a>
                          %endif
                      </td>
                      <td>
                          %if node.is_file():
-                             <span class="tooltip" title="${h.fmt_date(node.last_changeset.date)}">
+                             <span data-toggle="tooltip" title="${h.fmt_date(node.last_changeset.date)}">
                             ${h.age(node.last_changeset.date)}</span>
                          %endif
                      </td>
@@ -118,7 +109,7 @@
 <script>
     $(document).ready(function(){
         // init node filter if we pass GET param ?search=1
-        var search_GET = "${request.GET.get('search','')}";
+        var search_GET = ${h.js(request.GET.get('search',''))};
         if(search_GET == "1"){
             $("#filter_activate").click();
         }
--- a/kallithea/templates/files/files_delete.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_delete.html	Sun Mar 31 21:28:56 2019 +0200
@@ -14,34 +14,31 @@
 
 <%def name="main()">
 ${self.repo_context_bar('files')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-            <li>
-              <span><a href="#">${_('Branch')}: ${c.cs.branch}</a></span>
-            </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="#">${_('Branch')}: ${c.cs.branch}</a>
+        </div>
     </div>
-    <div class="table" id="edit">
+    <div class="panel-body" id="edit">
         <div id="files_data">
-            ${h.form(h.url.current(),method='post',class_="form-horizontal")}
+            ${h.form(h.url.current(),method='post')}
             <h3 class="files_location">
                 ${_('Delete file')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.f_path)}
             </h3>
 
-            <div id="body" class="codeblock">
-                <div id="editor_container">
-                    <pre id="editor_pre"></pre>
-                    <textarea id="editor" name="content" style="display:none"></textarea>
+            <div id="body" class="panel panel-default">
+                <div class="panel-heading">${_('Commit Message')}</div>
+                <div class="panel-body">
+                    <textarea class="form-control" name="message" placeholder="${c.default_message}"></textarea>
                 </div>
-                <div style="padding: 10px;color:#666666">${_('Commit Message')}</div>
-                <textarea id="commit" name="message" style="height: 100px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
             </div>
-            <div style="text-align: left;padding-top: 5px">
-                ${h.submit('commit',_('Commit Changes'),class_="btn btn-small btn-success")}
-                ${h.reset('reset',_('Reset'),class_="btn btn-small")}
+            <div>
+                ${h.submit('commit',_('Commit Changes'),class_="btn btn-success btn-sm")}
+                ${h.reset('reset',_('Reset'),class_="btn btn-default btn-sm")}
             </div>
             ${h.end_form()}
         </div>
--- a/kallithea/templates/files/files_edit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_edit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -23,49 +23,54 @@
 
 <%def name="main()">
 ${self.repo_context_bar('files')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-            <li>
-              <span><a href="#">${_('Branch')}: ${c.cs.branch}</a></span>
-            </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            <a href="#">${_('Branch')}: ${c.cs.branch}</a>
+        </div>
     </div>
-    <div class="table" id="edit">
+    <div class="panel-body" id="edit">
         <div id="files_data">
-            <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
             ${h.form(h.url.current(),method='post',id='eform')}
-            <div id="body" class="codeblock">
-            <div class="code-header">
-                <div class="stats">
-                    <div class="left"><i class="icon-doc-inv"></i></div>
-                    <div class="left item">${h.link_to(h.show_id(c.file.changeset),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</div>
-                    <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
-                    <div class="left item last">${c.file.mimetype}</div>
-                    <div class="buttons">
-                      ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
-                      ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
-                      ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
-                      % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+            <h3 class="files_location">
+              ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.raw_id,c.file.path)}
+            </h3>
+            <div id="body" class="panel panel-default">
+              <div class="panel-heading clearfix form-inline form-group-sm">
+                    <span>
+                        <span><i class="icon-doc-inv"></i></span>
+                        <span>${h.link_to(h.show_id(c.file.changeset),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</span>
+                        <span>${h.format_byte_size(c.file.size,binary=True)}</span>
+                        <span>${c.file.mimetype}</span>
+                        <select class="form-control" id="mimetype" name="mimetype"></select>
+                    </span>
+                    <span class="pull-right buttons">
+                      ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
+                      ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
+                      ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
+                      % if h.HasRepoPermissionLevel('write')(c.repo_name):
                        % if not c.file.is_binary:
-                        ${h.link_to(_('Source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+                        ${h.link_to(_('Source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
                        % endif
                       % endif
-                    </div>
-                </div>
-                <label class="commit" for="set_mode">${_('Editing file')}: ${c.file.unicode_path}</label>
-                <select id="set_mode" name="set_mode"/>
-            </div>
-                <pre id="editor_pre"></pre>
+                    </span>
+              </div>
+              <div class="panel-body no-padding">
                 <textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
-                <div style="padding: 10px;color:#666666">${_('Commit Message')}</div>
-                <textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px" placeholder="${c.default_message}"></textarea>
+              </div>
             </div>
-            <div style="text-align: left;padding-top: 5px">
-            ${h.submit('commit',_('Commit Changes'),class_="btn btn-small btn-success")}
-            ${h.reset('reset',_('Reset'),class_="btn btn-small")}
+            <div>
+              <div class="form-group">
+                  <label>${_('Commit Message')}</label>
+                  <textarea class="form-control" id="commit" name="message" placeholder="${c.default_message}"></textarea>
+              </div>
+              <div class="form-group buttons">
+                ${h.submit('commit',_('Commit Changes'),class_="btn btn-success")}
+                ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+              </div>
             </div>
             ${h.end_form()}
         </div>
@@ -73,40 +78,41 @@
 </div>
 
 <script type="text/javascript">
-$(document).ready(function(){
-    var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
-    var myCodeMirror = initCodeMirror('editor', "${request.script_name}", reset_url);
+    $(document).ready(function(){
+        var reset_url = ${h.jshtml(h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path))};
+        var myCodeMirror = initCodeMirror('editor', ${h.jshtml(request.script_name)}, reset_url);
 
-   //inject new modes, based on codeMirrors modeInfo object
-    $('#set_mode').each(function(){
-        var modes_select = this;
-        var index = 1;
-        for(var i=0;i<CodeMirror.modeInfo.length;i++){
-            var m = CodeMirror.modeInfo[i];
-            var opt = new Option(m.name, m.mime);
-            $(opt).attr('mode', m.mode);
-            if (m.mime == 'text/plain') {
-                // default plain text
-                $(opt).prop('selected', true);
-                modes_select.options[0] = opt;
-            } else {
-                modes_select.options[index++] = opt;
+       //inject new modes, based on codeMirrors modeInfo object
+        var $mimetype_select = $('#mimetype');
+        $mimetype_select.each(function(){
+            var modes_select = this;
+            var index = 1;
+            for(var i=0;i<CodeMirror.modeInfo.length;i++){
+                var m = CodeMirror.modeInfo[i];
+                var opt = new Option(m.name, m.mime);
+                $(opt).attr('mode', m.mode);
+                if (m.mime == 'text/plain') {
+                    // default plain text
+                    $(opt).prop('selected', true);
+                    modes_select.options[0] = opt;
+                } else {
+                    modes_select.options[index++] = opt;
+                }
             }
+        });
+        // try to detect the mode based on the file we edit
+        var detected_mode = CodeMirror.findModeByExtension(${h.js(c.file.extension)});
+        if(detected_mode){
+            setCodeMirrorMode(myCodeMirror, detected_mode);
+            $($mimetype_select.find('option[value="'+detected_mode.mime+'"]')[0]).prop('selected', true);
         }
+
+        $mimetype_select.on('change', function(e){
+            var selected = e.currentTarget;
+            var node = selected.options[selected.selectedIndex];
+            var detected_mode = CodeMirror.findModeByMIME(node.value);
+            setCodeMirrorMode(myCodeMirror, detected_mode);
+        });
     });
-    // try to detect the mode based on the file we edit
-    var detected_mode = CodeMirror.findModeByExtension("${c.file.extension}");
-    if(detected_mode){
-        setCodeMirrorMode(myCodeMirror, detected_mode);
-        $($('#set_mode option[value="'+detected_mode.mime+'"]')[0]).prop('selected', true);
-    }
-
-    $('#set_mode').on('change', function(e){
-        var selected = e.currentTarget;
-        var node = selected.options[selected.selectedIndex];
-        var detected_mode = CodeMirror.findModeByMIME(node.value);
-        setCodeMirrorMode(myCodeMirror, detected_mode);
-    });
-});
 </script>
 </%def>
--- a/kallithea/templates/files/files_history_box.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_history_box.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,10 +1,8 @@
-<div class="file_author" style="clear:both">
-    <div class="item">${h.literal(ungettext(u'%s author',u'%s authors',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }</div>
+<div class="form-group">
+    <span>${h.literal(ungettext(u'%s author',u'%s authors',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }</span>
     %for email, user in c.authors:
-      <div class="contributor tooltip" style="float:left" title="${user}">
-        <div class="gravatar" style="margin:1px">
-          ${h.gravatar(email, size=20)}
-        </div>
-      </div>
+      <span data-toggle="tooltip" title="${user}">
+        ${h.gravatar_div(email, size=20)}
+      </span>
     %endfor
 </div>
--- a/kallithea/templates/files/files_source.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_source.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,77 +1,78 @@
-<div id="node_history" style="padding: 0px 0px 10px 0px">
-    <div>
-        <div style="float:left">
+<div id="node_history" class="clearfix">
+    <div class="form-group">
         ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
-        ${h.hidden('diff2',c.file_changeset.raw_id)}
+        ${h.hidden('diff2',c.changeset.raw_id)}
         ${h.hidden('diff1')}
-        ${h.submit('diff',_('Diff to Revision'),class_="btn btn-small")}
-        ${h.submit('show_rev',_('Show at Revision'),class_="btn btn-small")}
+        ${h.submit('diff',_('Diff to Revision'),class_="btn btn-default btn-sm")}
+        ${h.submit('show_rev',_('Show at Revision'),class_="btn btn-default btn-sm")}
         ${h.hidden('annotate', c.annotate)}
-        ${h.link_to(_('Show Full History'),h.url('changelog_file_home',repo_name=c.repo_name, revision=c.file_changeset.raw_id, f_path=c.f_path),class_="btn btn-small")}
-        ${h.link_to(_('Show Authors'),'#',class_="btn btn-small" ,id="show_authors")}
-
+        ${h.link_to(_('Show Full History'),h.url('changelog_file_home',repo_name=c.repo_name, revision=c.changeset.raw_id, f_path=c.f_path),class_="btn btn-default btn-sm")}
+        ${h.link_to(_('Show Authors'),'#',class_="btn btn-default btn-sm" ,id="show_authors")}
         ${h.end_form()}
-        </div>
-        <div id="file_authors" class="file_author" style="clear:both; display: none"></div>
     </div>
-    <div style="clear:both"></div>
+    <div id="file_authors" class="file_author" style="display: none"></div>
 </div>
 
-
-<div id="body" class="codeblock">
-    <div class="code-header">
-        <div class="stats">
-            <div class="left img"><i class="icon-doc-inv"></i></div>
-            <div class="left item"><pre class="tooltip" title="${h.fmt_date(c.file_changeset.date)}">${h.link_to(h.show_id(c.file_changeset),h.url('changeset_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id))}</pre></div>
-            <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
-            <div class="left item last"><pre>${c.file.mimetype}</pre></div>
-            <div class="buttons">
+<div id="body" class="panel panel-default">
+    <div class="panel-heading clearfix">
+        <span>
+            <span class="img"><i class="icon-doc-inv"></i></span>
+            <span data-toggle="tooltip" title="${h.fmt_date(c.changeset.date)}">${h.link_to(h.show_id(c.changeset),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
+            <span>${h.format_byte_size(c.file.size,binary=True)}</span>
+            <span>${c.file.mimetype}</span>
+        </span>
+        <span class="pull-right buttons">
               %if c.annotate:
-                ${h.link_to(_('Show Source'),    h.url('files_home',         repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+                ${h.link_to(_('Show Source'),    h.url('files_home',         repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
               %else:
-                ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
+                ${h.link_to(_('Show Annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
               %endif
-              ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
-              ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path),class_="btn btn-mini")}
-              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
-               %if c.on_branch_head and c.changeset.branch and not c.file.is_binary:
-                ${h.link_to(_('Edit on Branch:%s') % c.changeset.branch, h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini")}
-                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini btn-danger")}
-               %elif c.on_branch_head and c.changeset.branch and c.file.is_binary:
-                ${h.link_to(_('Edit'), '#', class_="btn btn-mini disabled tooltip", title=_('Editing binary files not allowed'))}
-                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.branch,f_path=c.f_path, anchor='edit'),class_="btn btn-mini btn-danger")}
+              ${h.link_to(_('Show as Raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
+              ${h.link_to(_('Download as Raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path),class_="btn btn-default btn-xs")}
+              %if h.HasRepoPermissionLevel('write')(c.repo_name):
+               %if c.on_branch_head and not c.file.is_binary:
+                ${h.link_to(_('Edit on Branch: %s') % c.changeset.branch, h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path, anchor='edit'),class_="btn btn-default btn-xs")}
+                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path, anchor='edit'),class_="btn btn-danger btn-xs")}
+               %elif c.on_branch_head and c.file.is_binary:
+                ${h.link_to(_('Edit'), '#', class_="btn btn-default btn-xs disabled", title=_('Editing binary files not allowed'),**{'data-toggle':'tooltip'})}
+                ${h.link_to(_('Delete'), h.url('files_delete_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path, anchor='edit'),class_="btn btn-danger btn-xs")}
                %else:
-                ${h.link_to(_('Edit'), '#', class_="btn btn-mini disabled tooltip", title=_('Editing files allowed only when on branch head revision'))}
-                ${h.link_to(_('Delete'), '#', class_="btn btn-mini btn-danger disabled tooltip", title=_('Deleting files allowed only when on branch head revision'))}
-               % endif
-              % endif
-            </div>
-        </div>
-        <div class="author">
-            <div class="gravatar">
-                ${h.gravatar(h.email_or_none(c.file_changeset.author), size=16)}
-            </div>
-            <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)}</div>
-        </div>
-        <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div>
+                ${h.link_to(_('Edit'), '#', class_="btn btn-default btn-xs disabled", title=_('Editing files allowed only when on branch head revision'),**{'data-toggle':'tooltip'})}
+                ${h.link_to(_('Delete'), '#', class_="btn btn-danger btn-xs disabled", title=_('Deleting files allowed only when on branch head revision'),**{'data-toggle':'tooltip'})}
+               %endif
+              %endif
+        </span>
     </div>
-    <div class="code-body">
+    <div class="panel-body">
+      <div class="author">
+            ${h.gravatar_div(h.email_or_none(c.changeset.author), size=16)}
+            <div title="${c.changeset.author}" class="user">${h.person(c.changeset.author)}</div>
+      </div>
+      <div class="formatted-fixed">${h.urlify_text(c.changeset.message,c.repo_name)}</div>
+    </div>
+    <div class="panel-body no-padding">
       %if c.file.is_browser_compatible_image():
-        <img src="${h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path)}" class="img-preview"/>
+        <img src="${h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}" class="img-preview"/>
       %elif c.file.is_binary:
-        <div style="padding:5px">
+        <div>
           ${_('Binary file (%s)') % c.file.mimetype}
         </div>
       %else:
-        %if c.file.size < c.cut_off_limit:
+        %if c.file.size < c.cut_off_limit or c.fulldiff:
             %if c.annotate:
               ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
             %else:
               ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
             %endif
         %else:
-            ${_('File is too big to display')} ${h.link_to(_('Show as raw'),
-            h.url('files_raw_home',repo_name=c.repo_name,revision=c.file_changeset.raw_id,f_path=c.f_path))}
+          <h4>
+            ${_('File is too big to display.')}
+            %if c.annotate:
+              ${h.link_to(_('Show full annotation anyway.'), h.url.current(fulldiff=1, **request.GET.mixed()))}
+            %else:
+              ${h.link_to(_('Show as raw.'), h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
+            %endif
+          </h4>
         %endif
       %endif
     </div>
@@ -79,16 +80,12 @@
 
 <script>
     $(document).ready(function(){
-        // fake html5 history state
-        var _State = {
-           url: "${h.url.current()}",
-           data: {
-             node_list_url: node_list_url.replace('__REV__',"${c.changeset.raw_id}").replace('__FPATH__', "${h.safe_unicode(c.file.path)}"),
-             url_base: url_base.replace('__REV__',"${c.changeset.raw_id}"),
-             rev:"${c.changeset.raw_id}",
-             f_path: "${h.safe_unicode(c.file.path)}"
-           }
+        var state = {
+             node_list_url: node_list_url.replace('__REV__',${h.js(c.changeset.raw_id)}).replace('__FPATH__', ${h.js(h.safe_unicode(c.file.path))}),
+             url_base: url_base.replace('__REV__',${h.js(c.changeset.raw_id)}),
+             rev: ${h.js(c.changeset.raw_id)},
+             f_path: ${h.js(h.safe_unicode(c.file.path))}
         }
-        callbacks(_State); // defined in files.html, main callbacks. Triggerd in pjax calls
+        window.history.pushState(state, null, ${h.js(h.url.current())});
     });
 </script>
--- a/kallithea/templates/files/files_ypjax.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/files/files_ypjax.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,10 +5,10 @@
         - ${_('annotation')}
         %endif
         %if c.file.is_dir():
-          % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
+          % if h.HasRepoPermissionLevel('write')(c.repo_name):
              / <span title="${_('Add New File')}">
                <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path, anchor='edit')}">
-                   <i class="icon-plus-circled" style="color:#5bb75b; font-size: 16px"></i></a>
+                   <i class="icon-plus-circled"></i></a>
                </span>
           % endif
         %endif
--- a/kallithea/templates/followers/followers.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/followers/followers.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,13 +15,11 @@
 
 <%def name="main()">
 ${self.repo_context_bar('followers')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    <div class="table">
+    <div class="panel-body">
         <div id="followers">
             <%include file='followers_data.html'/>
         </div>
@@ -34,7 +32,6 @@
         asynchtml(e.target.href, $followers, function(){
             show_more_event();
             tooltip_activate();
-            show_changeset_tooltip();
         });
         e.preventDefault();
     });
--- a/kallithea/templates/followers/followers_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/followers/followers_data.html	Sun Mar 31 21:28:56 2019 +0200
@@ -3,18 +3,13 @@
 % for f in c.followers_pager:
     <div>
         <div class="follower_user">
-            <div class="gravatar">
-              ${h.gravatar(f.user.email, size=24)}
-            </div>
-            <span style="font-size: 20px"> <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname})</span>
+            ${h.gravatar_div(f.user.email, size=24)}
+            <span> <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname})</span>
         </div>
-        <div style="clear:both;padding-top: 10px"></div>
         <div class="follower_date">${_('Started following -')}
-        <span class="tooltip" title="${f.follows_from}"> ${h.age(f.follows_from)}</span></div>
-        <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
+          <span data-toggle="tooltip" title="${f.follows_from}"> ${h.age(f.follows_from)}</span>
+        </div>
     </div>
 % endfor
 
-<div class="pagination-wh pagination-left">
-${c.followers_pager.pager('$link_previous ~2~ $link_next')}
-</div>
+${c.followers_pager.pager()}
--- a/kallithea/templates/forks/fork.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/forks/fork.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,87 +15,76 @@
 
 <%def name="main()">
 ${self.repo_context_bar('createfork')}
-<div class="box">
-    <!-- box / title -->
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        ${self.breadcrumbs()}
+    </div>
     ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
-    <div class="form">
-        <!-- fields -->
-        <div class="fields">
-
-            <div class="field">
-              <div class="label">
-                  <label for="repo_name">${_('Fork name')}:</label>
-              </div>
-              <div class="input">
-                  ${h.text('repo_name',class_="small")}
-                  ${h.hidden('repo_type',c.repo_info.repo_type)}
-                  ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
-              </div>
+    <div class="form panel-body settings">
+            <div class="form-group">
+                <label class="control-label" for="repo_name">${_('Fork name')}:</label>
+                <div>
+                    ${h.text('repo_name',class_='form-control')}
+                    ${h.hidden('repo_type',c.repo_info.repo_type)}
+                    ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
+                </div>
             </div>
 
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="description">${_('Description')}:</label>
-                </div>
-                <div class="textarea editor">
-                    ${h.textarea('description')}
+            <div class="form-group">
+                <label class="control-label" for="description">${_('Description')}:</label>
+                <div>
+                    ${h.textarea('description',class_='form-control')}
                     <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                 <div class="label">
-                     <label for="repo_group">${_('Repository group')}:</label>
-                 </div>
-                 <div class="input">
-                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
-                     <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
-                 </div>
+            <div class="form-group">
+                <label class="control-label" for="repo_group">${_('Repository group')}:</label>
+                <div>
+                    ${h.select('repo_group','',c.repo_groups,class_='form-control')}
+                    <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
+                </div>
             </div>
 
-             <div class="field">
-                <div class="label">
-                    <label for="landing_rev">${_('Landing revision')}:</label>
-                </div>
-                <div class="input">
-                    ${h.select('landing_rev','',c.landing_revs,class_="medium")}
+            <div class="form-group">
+                <label class="control-label" for="landing_rev">${_('Landing revision')}:</label>
+                <div>
+                    ${h.select('landing_rev','',c.landing_revs,class_='form-control')}
                     <span class="help-block">${_('Default revision for files page, downloads, whoosh, and readme.')}</span>
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="private">${_('Private')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="private">${_('Private')}:</label>
+                <div>
                     ${h.checkbox('private',value="True")}
                     <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
                 </div>
             </div>
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="private">${_('Copy permissions')}:</label>
-                </div>
-                <div class="checkboxes">
+
+            <div class="form-group">
+                <label class="control-label" for="copy_permissions">${_('Copy permissions')}:</label>
+                <div>
                     ${h.checkbox('copy_permissions',value="True", checked="checked")}
                     <span class="help-block">${_('Copy permissions from forked repository')}</span>
                 </div>
             </div>
+
             %if c.can_update:
-            <div class="field">
-                <div class="label label-checkbox">
-                    <label for="private">${_('Update after clone')}:</label>
-                </div>
-                <div class="checkboxes">
+            <div class="form-group">
+                <label class="control-label" for="update_after_clone">${_('Update after clone')}:</label>
+                <div>
                     ${h.checkbox('update_after_clone',value="True")}
                     <span class="help-block">${_('Checkout source after making a clone')}</span>
                 </div>
             </div>
             %endif
-            <div class="buttons">
-                ${h.submit('',_('Fork this Repository'),class_="btn")}
+
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('fork-submit',_('Fork this Repository'),class_="btn btn-default")}
+                </div>
             </div>
-        </div>
     </div>
     ${h.end_form()}
 </div>
--- a/kallithea/templates/forks/forks.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/forks/forks.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,13 +15,11 @@
 
 <%def name="main()">
 ${self.repo_context_bar('showforks')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    <div class="table">
+    <div class="panel-body">
         <div id="forks">
             <%include file='forks_data.html'/>
         </div>
@@ -34,7 +32,6 @@
           asynchtml(e.target.href, $forks, function(){
               show_more_event();
               tooltip_activate();
-              show_changeset_tooltip();
           });
           e.preventDefault();
       });
--- a/kallithea/templates/forks/forks_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/forks/forks_data.html	Sun Mar 31 21:28:56 2019 +0200
@@ -4,28 +4,22 @@
     % for f in c.forks_pager:
         <div>
             <div class="fork_user">
-                <div class="gravatar">
-                  ${h.gravatar(f.user.email, size=24)}
-                </div>
-                <span style="font-size: 20px">
-                 <b>${f.user.username}</b> (${f.user.name} ${f.user.lastname}) /
+                ${h.gravatar_div(f.owner.email, size=24)}
+                <span>
+                 <b>${f.owner.username}</b> (${f.owner.name} ${f.owner.lastname}) /
                   ${h.link_to(f.repo_name,h.url('summary_home',repo_name=f.repo_name))}
                 </span>
-                <div style="padding:5px 3px 3px 42px;">${f.description}</div>
+                <div>${f.description}</div>
             </div>
-            <div style="clear:both;padding-top: 10px"></div>
             <div class="follower_date">${_('Forked')} -
-                <span class="tooltip" title="${h.fmt_date(f.created_on)}"> ${h.age(f.created_on)}</span>
+                <span data-toggle="tooltip" title="${h.fmt_date(f.created_on)}"> ${h.age(f.created_on)}</span>
                 <a title="${_('Compare fork with %s') % c.repo_name}"
                    href="${h.url('compare_url',repo_name=c.repo_name, org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1],other_repo=f.repo_name,other_ref_type=c.db_repo.landing_rev[0],other_ref_name=c.db_repo.landing_rev[1], merge=1)}"
-                   class="btn btn-small"><i class="icon-git-compare"></i> ${_('Compare Fork')}</a>
+                   class="btn btn-default btn-sm"><i class="icon-git-compare"></i>${_('Compare Fork')}</a>
             </div>
-            <div style="border-bottom: 1px solid #DDD;margin:10px 0px 10px 0px"></div>
         </div>
     % endfor
-  <div class="pagination-wh pagination-left">
-  ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
-  </div>
+    ${c.forks_pager.pager()}
 % else:
     ${_('There are no forks yet')}
 % endif
--- a/kallithea/templates/index.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/index.html	Sun Mar 31 21:28:56 2019 +0200
@@ -2,12 +2,9 @@
 <%inherit file="base/base.html"/>
 
 <%block name="title">
-    ${_('Dashboard')}
+    ${_('Repositories')}
 </%block>
 
-<%def name="breadcrumbs()">
-</%def>
-
 <%block name="header_menu">
 ${self.menu('repositories')}
 </%block>
--- a/kallithea/templates/index_base.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/index_base.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,186 +1,70 @@
 <%page args="parent,group_name=''" />
-    <div class="box">
-        <!-- box / title -->
-        <div class="title">
-            <h5>
-            <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
-            </h5>
-            %if c.authuser.username != 'default':
-              <ul class="links">
-                <li>
+    <div class="panel panel-primary">
+        <div class="panel-heading clearfix">
+            <div class="pull-left panel-title">
+                %if c.group is not None:
+                    %for group in c.group.parents:
+                        ${h.link_to(group.name, url('repos_group_home', group_name=group.group_name))}
+                        &raquo;
+                    %endfor
+                    ${c.group.name}
+                %endif
+            </div>
+
+            %if request.authuser.username != 'default':
+              <div class="pull-right">
                 <%
                     gr_name = c.group.group_name if c.group else None
                     # create repositories with write permission on group is set to true
                     create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
-                    group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'can write into group index page')
-                    group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
+                    group_admin = h.HasRepoGroupPermissionLevel('admin')(gr_name, 'can write into group index page')
+                    group_write = h.HasRepoGroupPermissionLevel('write')(gr_name, 'can write into group index page')
                 %>
                 %if h.HasPermissionAny('hg.admin','hg.create.repository')() or (group_admin or (group_write and create_on_write)):
                   %if c.group:
-                        <a href="${h.url('new_repo',parent_group=c.group.group_id)}" class="btn btn-small"><i class="icon-plus"></i> ${_('Add Repository')}</a>
-                        %if h.HasPermissionAny('hg.admin')() or h.HasRepoGroupPermissionAny('group.admin')(c.group.group_name):
-                            <a href="${h.url('new_repos_group', parent_group=c.group.group_id)}" class="btn btn-small"><i class="icon-plus"></i> ${_('Add Repository Group')}</a>
+                        <a href="${h.url('new_repo',parent_group=c.group.group_id)}" class="btn btn-default btn-xs"><i class="icon-plus"></i>${_('Add Repository')}</a>
+                        %if h.HasPermissionAny('hg.admin')() or h.HasRepoGroupPermissionLevel('admin')(c.group.group_name):
+                            <a href="${h.url('new_repos_group', parent_group=c.group.group_id)}" class="btn btn-default btn-xs"><i class="icon-plus"></i>${_('Add Repository Group')}</a>
                         %endif
                   %else:
-                    <a href="${h.url('new_repo')}" class="btn btn-small"><i class="icon-plus"></i> ${_('Add Repository')}</a>
+                    <a href="${h.url('new_repo')}" class="btn btn-default btn-xs"><i class="icon-plus"></i>${_('Add Repository')}</a>
                     %if h.HasPermissionAny('hg.admin')():
-                        <a href="${h.url('new_repos_group')}" class="btn btn-small"><i class="icon-plus"></i> ${_('Add Repository Group')}</a>
+                        <a href="${h.url('new_repos_group')}" class="btn btn-default btn-xs"><i class="icon-plus"></i>${_('Add Repository Group')}</a>
                     %endif
                   %endif
                 %endif
-                %if c.group and h.HasRepoGroupPermissionAny('group.admin')(c.group.group_name):
-                    <a href="${h.url('edit_repo_group',group_name=c.group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small"><i class="icon-pencil"></i> ${_('Edit Repository Group')}</a>
+                %if c.group and h.HasRepoGroupPermissionLevel('admin')(c.group.group_name):
+                    <a href="${h.url('edit_repo_group',group_name=c.group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-default btn-xs"><i class="icon-pencil"></i>${_('Edit Repository Group')}</a>
                 %endif
-                </li>
-              </ul>
+              </div>
             %endif
         </div>
-        <!-- end box / title -->
-        <div class="table">
-           % if c.groups:
-            <div id='groups_list_wrap' class="yui-skin-sam">
-              <table id="groups_list">
-                  <thead>
-                      <tr>
-                          <th class="left"><a href="#">${_('Group Name')}</a></th>
-                          <th class="left"><a href="#">${_('Description')}</a></th>
-                          ##<th class="left"><a href="#">${_('Number of Repositories')}</a></th>
-                      </tr>
-                  </thead>
-
-                  ## REPO GROUPS
-                  % for gr in c.groups:
-                    <tr>
-                        <td>
-                            <div class="dt_repo">
-                              <a href="${url('repos_group_home',group_name=gr.group_name)}">
-                                <i class="icon-folder"></i>
-                                <span class="dt_repo_name">${gr.name}</span>
-                              </a>
-                            </div>
-                        </td>
-                        <td>${h.urlify_text(gr.group_description, stylize=c.visual.stylify_metatags)}</td>
-                        ## this is commented out since for multi nested repos can be HEAVY!
-                        ## in number of executed queries during traversing uncomment at will
-                        ##<td><b>${gr.repositories_recursive_count}</b></td>
-                    </tr>
-                  % endfor
-              </table>
-            </div>
-            <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div>
-            <div style="height: 20px"></div>
-            % endif
-            <div id="welcome" style="display:none;text-align:center">
-                <h1><a href="${h.url('home')}">${c.site_name} ${c.kallithea_version}</a></h1>
-            </div>
-            <%cnt=0%>
-            <%namespace name="dt" file="/data_table/_dt_elements.html"/>
-            <div class="yui-skin-sam" id="repos_list_wrap"></div>
-            <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
+        <div class="panel-body">
+            <table class="table" id="repos_list_wrap" width="100%"></table>
         </div>
     </div>
 
       <script>
-        var data = ${c.data|n};
-        var myDataSource = new YAHOO.util.DataSource(data);
-        myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-        myDataSource.responseSchema = {
-            resultsList: "records",
-            fields: [
-               {key:"menu"},
-               {key:"raw_name"},
-               {key:"name"},
-               {key:"desc"},
-               {key:"last_change"},
-               {key:"last_changeset"},
-               {key:"last_rev_raw"},
-               {key:"owner"},
-               {key:"atom"}
-            ]
-         };
-        myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-            // This is the filter function
-            var data     = res.results || [],
-                filtered = [],
-                i,l;
-
-            if (req) {
-                req = req.toLowerCase();
-                for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req, ${len(group_name)});
-                    if (pos != -1) {
-                        filtered.push(data[i]);
-                    }
-                }
-                res.results = filtered;
-            }
-            YUD.get('repo_count').innerHTML = res.results.length;
-            return res;
-        }
-
-        // main table sorting
-        var myColumnDefs = [
-            {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-            {key:"name",label:"${_('Name')}",sortable:true,
-                sortOptions: { sortFunction: nameSort }},
-            {key:"desc",label:"${_('Description')}",sortable:true},
-            {key:"last_change",label:"${_('Last Change')}",sortable:true,
-                sortOptions: { sortFunction: ageSort }},
-            {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-                sortOptions: { sortFunction: revisionSort }},
-            {key:"owner",label:"${_('Owner')}",sortable:true},
-            {key:"atom",label:"",sortable:false}
-        ];
-
-        var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-          sortedBy:{key:"name",dir:"asc"},
-          paginator: YUI_paginator(${c.visual.dashboard_items},['user-paginator']),
-
-          MSG_SORTASC:"${_('Click to sort ascending')}",
-          MSG_SORTDESC:"${_('Click to sort descending')}",
-          MSG_EMPTY:"${_('No repositories found.')}",
-          MSG_ERROR:"${_('Data error.')}",
-          MSG_LOADING:"${_('Loading...')}"
-        }
-        );
-        myDataTable.subscribe('postRenderEvent',function(oArgs) {
-            tooltip_activate();
-            quick_repo_menu();
-        });
-
-        var filterTimeout = null;
-
-        updateFilter = function () {
-            // Reset timeout
-            filterTimeout = null;
-
-            // Reset sort
-            var state = myDataTable.getState();
-            state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-            // Get filtered data
-            myDataSource.sendRequest(YUD.get('q_filter').value,{
-                success : myDataTable.onDataReturnInitializeTable,
-                failure : myDataTable.onDataReturnInitializeTable,
-                scope   : myDataTable,
-                argument: state
+        var data = ${h.js(c.data)},
+            $dataTable = $("#repos_list_wrap").DataTable({
+                data: data.records,
+                columns: [
+                    {data: "raw_name", visible: false, searchable: false},
+                    {title: ${h.jshtml(_('Repository'))}, data: "name", orderData: [0,], render: {
+                        filter: function(data, type, row, meta) {
+                            return row.just_name;
+                        }
+                    }},
+                    {data: "desc", title: ${h.jshtml(_('Description'))}, searchable: false},
+                    {data: "last_change_iso", defaultContent: '', visible: false, searchable: false},
+                    {data: "last_change", defaultContent: '', title: ${h.jshtml(_('Last Change'))}, orderData: [3,], searchable: false},
+                    {data: "last_rev_raw", defaultContent: '', visible: false, searchable: false},
+                    {data: "last_changeset", defaultContent: '', title: ${h.jshtml(_('Tip'))}, orderData: [5,], searchable: false},
+                    {data: "owner", defaultContent: '', title: ${h.jshtml(_('Owner'))}, searchable: false},
+                    {data: "atom", defaultContent: '', sortable: false}
+                ],
+                order: [[1, "asc"]],
+                dom: '<"dataTables_left"f><"dataTables_right"ip>t',
+                pageLength: 100
             });
-
-        };
-        $('#q_filter').click(function(){
-            if(!$('#q_filter').hasClass('loaded')){
-                //TODO: load here full list later to do search within groups
-                $('#q_filter').addClass('loaded');
-            }
-        });
-
-        $('#q_filter').keyup(function(){
-            clearTimeout(filterTimeout);
-            filterTimeout = setTimeout(updateFilter,600);
-        });
-
-        if($('#q_filter').val()) {
-            updateFilter();
-        }
       </script>
--- a/kallithea/templates/journal/journal.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/journal/journal.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,72 +1,43 @@
 ## -*- coding: utf-8 -*-
 <%inherit file="/base/base.html"/>
+
 <%block name="title">
     ${_('Journal')}
 </%block>
-<%def name="breadcrumbs()">
-    <h5>
-    <form id="filter_form">
-    <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
-    <span class="tooltip" title="${h.journal_filter_help()}">?</span>
-    <input type='submit' value="${_('Filter')}" class="btn btn-small" style="padding:0px 2px 0px 2px;margin:0px"/>
-    ${_('Journal')} - ${ungettext('%s Entry', '%s Entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
+
+<%def name="breadcrumbs_links()">
+    <form id="filter_form" class="pull-left form-inline input-group-sm">
+        <input class="form-control q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
+        <span data-toggle="popover" data-content="${h.journal_filter_help()}">?</span>
+        <input type='submit' value="${_('Filter')}" class="btn btn-default btn-xs"/>
+        ${_('Journal')} - ${ungettext('%s Entry', '%s Entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
     </form>
-    ${h.end_form()}
-    </h5>
 </%def>
+
 <%block name="header_menu">
     ${self.menu('journal')}
 </%block>
+
 <%block name="head_extra">
-  <link href="${h.url('journal_atom', api_key=c.authuser.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
-  <link href="${h.url('journal_rss', api_key=c.authuser.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
+  <link href="${h.url('journal_atom', api_key=request.authuser.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
+  <link href="${h.url('journal_rss', api_key=request.authuser.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
 </%block>
 
 <%def name="main()">
-    <div class="box box-left">
-        <!-- box / title -->
-        <div class="title">
-         ${self.breadcrumbs()}
-         <ul class="links icon-only-links">
-           <li>
-             <span><a id="refresh" href="${h.url('journal')}"><i class="icon-arrows-cw"></i></a></span>
-           </li>
-           <li>
-             <span><a href="${h.url('journal_atom', api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a></span>
-           </li>
-         </ul>
-        </div>
-        <div id="journal">
-            <%include file='journal_data.html'/>
+    <div class="panel panel-primary">
+        <div class="panel-heading clearfix">
+            <div class="pull-left">
+                ${self.breadcrumbs()}
+            </div>
+            <div class="pull-right panel-title">
+                <a href="${h.url('my_account_watched')}"><i class="icon-eye"></i>${_('Watched Repositories')}</a>
+                <a href="${h.url('my_account_repos')}"><i class="icon-database"></i>${_('My Repositories')}</a>
+                <a id="refresh" href="${h.url('journal')}"><i class="icon-arrows-cw"></i></a>
+                <a href="${h.url('journal_atom', api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
+            </div>
         </div>
-    </div>
-    <div class="box box-right">
-        <!-- box / title -->
-
-        <div class="title">
-            <h5>
-            <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value="" style="display: none"/>
-            <input class="q_filter_box" id="q_filter_watched" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value="" style="display: none"/>
-            </h5>
-            <ul class="links nav nav-tabs">
-                <li class="active" id="show_watched_li">
-                    <a id="show_watched" href="#watched"><i class="icon-eye"></i> ${_('Watched Repositories')}</a>
-                </li>
-                <li id="show_my_li">
-                    <a id="show_my" href="#my"><i class="icon-database"></i> ${_('My Repositories')}</a>
-               </li>
-            </ul>
-        </div>
-
-        <!-- end box / title -->
-        <div id="my_container" style="display:none">
-            <div class="table-grid table yui-skin-sam" id="repos_list_wrap"></div>
-            <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
-        </div>
-
-        <div id="watched_container">
-            <div class="table-grid table yui-skin-sam" id="watched_repos_list_wrap"></div>
-            <div id="watched-user-paginator" style="padding: 0px 0px 0px 20px"></div>
+        <div id="journal" class="panel-body">
+            <%include file='journal_data.html'/>
         </div>
     </div>
 
@@ -87,253 +58,18 @@
     $('#filter_form').submit(function(e){
         e.preventDefault();
         var val = $('#j_filter').val();
-        window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
+        window.location = ${h.js(url.current(filter='__FILTER__'))}.replace('__FILTER__',val);
     });
     fix_j_filter_width($('#j_filter').val().length);
 
     $('#refresh').click(function(e){
-        asynchtml("${h.url.current(filter=c.search_term)}", $("#journal"), function(){
+        asynchtml(${h.js(h.url.current(filter=c.search_term))}, $("#journal"), function(){
             show_more_event();
             tooltip_activate();
-            show_changeset_tooltip();
             });
         e.preventDefault();
     });
 
-    var show_my = function(e){
-        $('#watched_container').hide();
-        $('#my_container').show();
-        $('#q_filter').show();
-        $('#q_filter_watched').hide();
-
-        $('#show_my_li').addClass('active');
-        $('#show_watched_li').removeClass('active');
-        if(!$('#show_my').hasClass('loaded')){
-            table_renderer(${c.data |n});
-            $('#show_my').addClass('loaded');
-        }
-    };
-    $('#show_my').click(function(){
-        show_my();
-    });
-    var show_watched = function(){
-        $('#my_container').hide();
-        $('#watched_container').show();
-        $('#q_filter_watched').show();
-        $('#q_filter').hide();
-
-        $('#show_watched_li').addClass('active');
-        $('#show_my_li').removeClass('active');
-        if(!$('#show_watched').hasClass('loaded')){
-            watched_renderer(${c.watched_data |n});
-            $('#show_watched').addClass('loaded');
-        }
-    };
-    $('#show_watched').click(function(){
-        show_watched();
-    });
-    //init watched
-    show_watched();
-
-    var tabs = {
-        'watched': show_watched,
-        'my': show_my
-    }
-    var url = location.href.split('#');
-    if (url[1]) {
-        //We have a hash
-        var tabHash = url[1];
-        var func = tabs[tabHash]
-        if (func){
-            func();
-        }
-    }
-    function watched_renderer(data){
-        var myDataSource = new YAHOO.util.DataSource(data);
-        myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-        myDataSource.responseSchema = {
-            resultsList: "records",
-            fields: [
-               {key:"menu"},
-               {key:"raw_name"},
-               {key:"name"},
-               {key:"last_changeset"},
-               {key:"last_rev_raw"},
-               {key:"action"}
-            ]
-         };
-        myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-            // This is the filter function
-            var data     = res.results || [],
-                filtered = [],
-                i,l;
-
-            if (req) {
-                req = req.toLowerCase();
-                for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req);
-                    if (pos != -1) {
-                        filtered.push(data[i]);
-                    }
-                }
-                res.results = filtered;
-            }
-            return res;
-        }
-        // main table sorting
-        var myColumnDefs = [
-            {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-            {key:"name",label:"${_('Name')}",sortable:true,
-                sortOptions: { sortFunction: nameSort }},
-            {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-                sortOptions: { sortFunction: revisionSort }},
-            {key:"action",label:"${_('Action')}",sortable:false}
-        ];
-
-        var myDataTable = new YAHOO.widget.DataTable("watched_repos_list_wrap", myColumnDefs, myDataSource,{
-          sortedBy:{key:"name",dir:"asc"},
-          paginator: YUI_paginator(25, ['watched-user-paginator']),
-
-          MSG_SORTASC:"${_('Click to sort ascending')}",
-          MSG_SORTDESC:"${_('Click to sort descending')}",
-          MSG_EMPTY:"${_('No records found.')}",
-          MSG_ERROR:"${_('Data error.')}",
-          MSG_LOADING:"${_('Loading...')}"
-        }
-        );
-        myDataTable.subscribe('postRenderEvent',function(oArgs) {
-            tooltip_activate();
-            quick_repo_menu();
-        });
-
-        var filterTimeout = null;
-
-        updateFilter  = function () {
-            // Reset timeout
-            filterTimeout = null;
-
-            // Reset sort
-            var state = myDataTable.getState();
-            state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-            // Get filtered data
-            myDataSource.sendRequest(YUD.get('q_filter_watched').value,{
-                success : myDataTable.onDataReturnInitializeTable,
-                failure : myDataTable.onDataReturnInitializeTable,
-                scope   : myDataTable,
-                argument: state
-            });
-
-        };
-        $('#q_filter_watched').click(function(){
-            if(!$('#q_filter_watched').hasClass('loaded')) {
-                //TODO: load here full list later to do search within groups
-                $('#q_filter_watched').css('loaded');
-            }
-        });
-
-        $('#q_filter_watched').keyup(function(){
-            clearTimeout(filterTimeout);
-            filterTimeout = setTimeout(updateFilter,600);
-        });
-      }
-
-    function table_renderer(data){
-        var myDataSource = new YAHOO.util.DataSource(data);
-        myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
-
-        myDataSource.responseSchema = {
-            resultsList: "records",
-            fields: [
-               {key:"menu"},
-               {key:"raw_name"},
-               {key:"name"},
-               {key:"last_changeset"},
-               {key:"last_rev_raw"},
-               {key:"action"}
-            ]
-         };
-        myDataSource.doBeforeCallback = function(req,raw,res,cb) {
-            // This is the filter function
-            var data     = res.results || [],
-                filtered = [],
-                i,l;
-
-            if (req) {
-                req = req.toLowerCase();
-                for (i = 0; i<data.length; i++) {
-                    var pos = data[i].raw_name.toLowerCase().indexOf(req);
-                    if (pos != -1) {
-                        filtered.push(data[i]);
-                    }
-                }
-                res.results = filtered;
-            }
-            return res;
-        }
-        // main table sorting
-        var myColumnDefs = [
-            {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
-            {key:"name",label:"${_('Name')}",sortable:true,
-                sortOptions: { sortFunction: nameSort }},
-            {key:"last_changeset",label:"${_('Tip')}",sortable:true,
-                sortOptions: { sortFunction: revisionSort }},
-            {key:"action",label:"${_('Action')}",sortable:false}
-        ];
-
-        var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
-          sortedBy:{key:"name",dir:"asc"},
-          paginator: YUI_paginator(25, ['user-paginator']),
-
-          MSG_SORTASC:"${_('Click to sort ascending')}",
-          MSG_SORTDESC:"${_('Click to sort descending')}",
-          MSG_EMPTY:"${_('No records found.')}",
-          MSG_ERROR:"${_('Data error.')}",
-          MSG_LOADING:"${_('Loading...')}"
-        }
-        );
-        myDataTable.subscribe('postRenderEvent',function(oArgs) {
-            tooltip_activate();
-            quick_repo_menu();
-        });
-
-        var filterTimeout = null;
-
-        updateFilter = function () {
-            // Reset timeout
-            filterTimeout = null;
-
-            // Reset sort
-            var state = myDataTable.getState();
-            state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
-
-            // Get filtered data
-            myDataSource.sendRequest(YUD.get('q_filter').value,{
-                success : myDataTable.onDataReturnInitializeTable,
-                failure : myDataTable.onDataReturnInitializeTable,
-                scope   : myDataTable,
-                argument: state
-            });
-
-        };
-        $('#q_filter').click(function(){
-            if(!$('#q_filter').hasClass('loaded')){
-                //TODO: load here full list later to do search within groups
-                $('#q_filter').addClass('loaded');
-            }
-        });
-
-        $('#q_filter').keyup(function(){
-            clearTimeout(filterTimeout);
-            filterTimeout = setTimeout(updateFilter,600);
-        });
-
-        if($('#q_filter').val()) {
-            updateFilter();
-        }
-    }
-
 </script>
 
 <script type="text/javascript">
@@ -343,7 +79,6 @@
             asynchtml(e.target.href, $journal, function(){
                 show_more_event();
                 tooltip_activate();
-                show_changeset_tooltip();
             });
             e.preventDefault();
         });
--- a/kallithea/templates/journal/journal_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/journal/journal_data.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,45 +1,44 @@
 ## -*- coding: utf-8 -*-
 
-%if c.journal_day_aggreagate:
-    %for day,items in c.journal_day_aggreagate:
-     <div class="journal_day">${day}</div>
+%if c.journal_day_aggregate:
+    %for day,items in c.journal_day_aggregate:
+        <h4>${day}</h4>
         % for user,entries in items:
-            <div class="journal_container">
-                <div class="gravatar">
-                    ${h.gravatar(user.email if user else 'anonymous@kallithea-scm.org', size=24)}
-                </div>
+            <div class="container-fluid">
+                ${h.gravatar_div(user.email if user else 'anonymous@kallithea-scm.org', size=24)}
                 %if user:
-                    <div class="journal_user">${user.name} ${user.lastname}</div>
+                    <span class="journal_user">${user.name} ${user.lastname}</span>
                 %else:
-                    <div class="journal_user deleted">${entries[0].username}</div>
+                    <span class="journal_user deleted">${entries[0].username}</span>
                 %endif
-                <div class="journal_action_container">
                 % for entry in entries:
-                    <div class="journal_icon"> ${h.action_parser(entry)[2]()}</div>
-                    <div class="journal_action">${h.action_parser(entry)[0]()}</div>
-                    <div class="journal_repo">
-                        <span class="journal_repo_name">
-                        %if entry.repository is not None:
-                          ${h.link_to(entry.repository.repo_name,
-                                      h.url('summary_home',repo_name=entry.repository.repo_name))}
-                        %else:
-                          ${entry.repository_name}
-                        %endif
-                        </span>
+                  <div class="clearfix">
+                    <div class="pull-left">
+                      <div class="journal_icon"> ${h.action_parser(entry)[2]()}</div>
                     </div>
-                    <div class="journal_action_params">${h.literal(h.action_parser(entry)[1]())}</div>
-                    <div class="date"><span class="tooltip" title="${h.fmt_date(entry.action_date)}">${h.age(entry.action_date)}</span></div>
+                    <div class="pull-left">
+                      <div class="journal_action">
+                          ${h.action_parser(entry)[0]()}
+                          <span class="journal_repo_name">
+                          %if entry.repository is not None:
+                            ${h.link_to(entry.repository.repo_name,
+                                        h.url('summary_home',repo_name=entry.repository.repo_name))}
+                          %else:
+                            ${entry.repository_name}
+                          %endif
+                          </span>
+                      </div>
+                      <div class="journal_action_params">${h.literal(h.action_parser(entry)[1]())}</div>
+                      <div class="date"><span data-toggle="tooltip" title="${h.fmt_date(entry.action_date)}">${h.age(entry.action_date)}</span></div>
+                    </div>
+                  </div>
                 %endfor
-                </div>
             </div>
         %endfor
     %endfor
-
-  <div class="pagination-wh pagination-left" style="padding: 0px 0px 0px 10px;">
-  ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
-  </div>
+    ${c.journal_pager.pager()}
 %else:
-  <div style="padding:5px 0px 10px 10px;">
+  <div>
       ${_('No entries yet')}
   </div>
 %endif
--- a/kallithea/templates/journal/public_journal.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/journal/public_journal.html	Sun Mar 31 21:28:56 2019 +0200
@@ -3,9 +3,6 @@
 <%block name="title">
     ${_('Public Journal')}
 </%block>
-<%def name="breadcrumbs()">
-    ${c.site_name}
-</%def>
 <%block name="header_menu">
     ${self.menu('journal')}
 </%block>
@@ -15,20 +12,17 @@
 </%block>
 <%def name="main()">
 
-<div class="box">
-  <!-- box / title -->
-  <div class="title">
-    <h5>${_('Public Journal')}</h5>
-    <ul class="links icon-only-links">
-      <li>
-        <span>
-          <a href="${h.url('public_journal_atom')}"><i class="icon-rss-squared"></i></a>
-        </span>
-      </li>
-    </ul>
+<div class="panel panel-primary">
+  <div class="panel-heading clearfix">
+    <div class="pull-left">
+      ${_('Public Journal')}
+    </div>
+    <div class="pull-right panel-title">
+        <a href="${h.url('public_journal_atom')}"><i class="icon-rss-squared"></i></a>
+    </div>
   </div>
 
-  <div id="journal">
+  <div id="journal" class="panel-body">
     <%include file='journal_data.html'/>
   </div>
 </div>
--- a/kallithea/templates/login.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/login.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,62 +5,63 @@
     ${_('Log In')}
 </%block>
 
-<div id="login" class="panel panel-default">
-    <%include file="/base/flash_msg.html"/>
-    <!-- login -->
-    <div class="panel-heading title withlogo">
+<%include file="/base/flash_msg.html"/>
+<div class="container">
+<div class="row">
+<div class="centered-column">
+<div id="login" class="panel panel-primary">
+    <div class="panel-heading">
+      <h5>
+        <i class="icon-lock"></i>
         %if c.site_name:
-            <h5>${_('Log In to %s') % c.site_name}</h5>
+            ${_('Log In to %s') % c.site_name}
         %else:
-            <h5>${_('Log In')}</h5>
+            ${_('Log In')}
         %endif
+      </h5>
     </div>
-    <div class="panel-body inner">
+    <div class="panel-body">
         ${h.form(url('login_home', came_from=c.came_from))}
         <div class="form">
-            <i class="icon-lock"></i>
-            <!-- fields -->
 
-            <div class="form-horizontal">
                 <div class="form-group">
-                    <label class="control-label col-sm-5" for="username">${_('Username')}:</label>
-                    <div class="input col-sm-7">
-                        ${h.text('username',class_='form-control focus large')}
+                    <label class="control-label" for="username">${_('Username')}:</label>
+                    <div>
+                        ${h.text('username',class_='form-control')}
                     </div>
-
                 </div>
                 <div class="form-group">
-                    <label class="control-label col-sm-5" for="password">${_('Password')}:</label>
-                    <div class="input col-sm-7">
-                        ${h.password('password',class_='form-control focus large')}
+                    <label class="control-label" for="password">${_('Password')}:</label>
+                    <div>
+                        ${h.password('password',class_='form-control')}
                     </div>
-
                 </div>
                 <div class="form-group">
-                    <div class="col-sm-offset-5 col-sm-7">
+                    <div>
                         <div class="checkbox">
-                            <label for="remember">
+                            <label>
                                 <input type="checkbox" id="remember" name="remember"/>
-                                ${_('Remember me')}
+                                ${_('Stay logged in after browser restart')}
                             </label>
                         </div>
                     </div>
                 </div>
-            </div>
-            <!-- end fields -->
-            <!-- links -->
-            <div class="links">
-                ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
-                %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
-                  /
-                 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
-                %endif
-                <span class="buttons">
-                    ${h.submit('sign_in',_('Sign In'),class_="btn btn-default")}
-                </span>
+
+            <div class="form-group">
+                <div>
+                    ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
+                    %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
+                        /
+                        ${h.link_to(_("Don't have an account ?"),h.url('register'))}
+                    %endif
+                </div>
             </div>
 
-            <!-- end links -->
+            <div class="form-group">
+                <div class="buttons">
+                    ${h.submit('sign_in',_('Sign In'),class_="btn btn-default")}
+                </div>
+            </div>
         </div>
         ${h.end_form()}
         <script type="text/javascript">
@@ -69,5 +70,7 @@
         });
         </script>
     </div>
-    <!-- end login -->
+</div>
 </div>
+</div>
+</div>
--- a/kallithea/templates/password_reset.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/password_reset.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,65 +5,61 @@
     ${_('Password Reset')}
 </%block>
 
-<div id="register">
-    <%include file="/base/flash_msg.html"/>
-    <div class="title withlogo">
+<%block name="js_extra">
+    %if c.captcha_active:
+        <script type="text/javascript" src="https://www.google.com/recaptcha/api.js"></script>
+    %endif
+</%block>
+
+<%include file="/base/flash_msg.html"/>
+<div class="container">
+<div class="row">
+<div class="centered-column">
+<div id="register" class="panel panel-primary">
+    <div class="panel-heading">
         %if c.site_name:
             <h5>${_('Reset Your Password to %s') % c.site_name}</h5>
         %else:
             <h5>${_('Reset Your Password')}</h5>
         %endif
     </div>
-    <div class="inner">
+    <div class="panel-body">
         ${h.form(url('password_reset'))}
         <div class="form">
-            <!-- fields -->
-            <div class="fields">
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="email">${_('Email Address')}:</label>
+                <div class="form-group">
+                    <label class="control-label" for="email">${_('Email Address')}:</label>
+                    <div>
+                        ${h.text('email', class_='form-control')}
                     </div>
-                    <div class="input">
-                        ${h.text('email')}
-                    </div>
-                 </div>
+                </div>
 
                 %if c.captcha_active:
-                <div class="field">
-                    <div class="label">
-                        <label for="email">${_('Captcha')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.hidden('recaptcha_field')}
-                        <div id="recaptcha"></div>
+                <div class="form-group">
+                    <label class="control-label" for="recaptcha_field">${_('Captcha')}:</label>
+                    <div>
+                        <div id="recaptcha_field" class="g-recaptcha" data-sitekey="${c.captcha_public_key}"></div>
                     </div>
                 </div>
                 %endif
 
-                <div class="buttons">
-                    <div class="nohighlight">
-                      ${h.submit('send',_('Send Password Reset Email'),class_="btn")}
-                          <div class="activation_msg">${_('A password reset link will be sent to the specified email address if it is registered in the system.')}</div>
+                <div class="form-group">
+                    <div class="buttons">
+                        ${h.submit('send',_('Send Password Reset Email'),class_="btn btn-default")}
                     </div>
                 </div>
-            </div>
+
+                <div class="alert alert-info">
+                    ${_('A password reset link will be sent to the specified email address if it is registered in the system.')}
+                </div>
         </div>
         ${h.end_form()}
-        %if c.captcha_active:
-        <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
-        %endif
         <script type="text/javascript">
          $(document).ready(function(){
             $('#email').focus();
-            %if c.captcha_active:
-            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
-                {
-                  theme: "white"
-                }
-            );
-            %endif
          });
         </script>
     </div>
-   </div>
+</div>
+</div>
+</div>
+</div>
--- a/kallithea/templates/password_reset_confirmation.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/password_reset_confirmation.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,59 +5,56 @@
     ${_('Reset Your Password')}
 </%block>
 
-<div id="register">
-    <%include file="/base/flash_msg.html"/>
-    <div class="title withlogo">
+<%include file="/base/flash_msg.html"/>
+
+<div class="container">
+<div class="row">
+<div class="centered-column">
+<div id="register" class="panel panel-primary">
+    <div class="panel-heading">
         %if c.site_name:
             <h5>${_('Reset Your Password to %s') % c.site_name}</h5>
         %else:
             <h5>${_('Reset Your Password')}</h5>
         %endif
     </div>
-    <div class="inner">
+    <div class="panel-body">
         ${h.form(h.url('reset_password_confirmation'), method='post')}
         <p>${_('You are about to set a new password for the email address %s.') % c.email}</p>
         <p>${_('Note that you must use the same browser session for this as the one used to request the password reset.')}</p>
-        <div style="display: none;">
-            ${h.hidden('email')}
-            ${h.hidden('timestamp')}
-        </div>
+        ${h.hidden('email')}
+        ${h.hidden('timestamp')}
         <div class="form">
-            <!-- fields -->
-            <div class="fields">
-                 <div class="field">
-                    <div class="label">
-                        <label for="token">${_('Code you received in the email')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('token', class_='focus')}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="password">${_('New Password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password',class_='focus')}
-                    </div>
-                 </div>
-
-                 <div class="field">
-                    <div class="label">
-                        <label for="password_confirm">${_('Confirm New Password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password_confirm',class_='focus')}
-                    </div>
-                 </div>
-                <div class="buttons">
-                    <div class="nohighlight">
-                      ${h.submit('send',_('Confirm'),class_="btn")}
+                <div class="form-group">
+                    <label class="control-label" for="token">${_('Code you received in the email')}:</label>
+                    <div>
+                        ${h.text('token', class_='form-control')}
                     </div>
                 </div>
-            </div>
+
+                <div class="form-group">
+                    <label class="control-label" for="password">${_('New Password')}:</label>
+                    <div>
+                        ${h.password('password',class_='form-control')}
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="control-label" for="password_confirm">${_('Confirm New Password')}:</label>
+                    <div>
+                        ${h.password('password_confirm',class_='form-control')}
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <div class="buttons">
+                        ${h.submit('send',_('Confirm'),class_="btn btn-default")}
+                    </div>
+                </div>
         </div>
         ${h.end_form()}
     </div>
    </div>
+</div>
+</div>
+</div>
--- a/kallithea/templates/pullrequests/pullrequest.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/pullrequests/pullrequest.html	Sun Mar 31 21:28:56 2019 +0200
@@ -14,93 +14,77 @@
 
 <%def name="main()">
 ${self.repo_context_bar('showpullrequest')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
     ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
-    <div class="form">
-        <!-- fields -->
-
-        <div class="fields" style="float:left;width:50%;padding-right:30px;">
-
-             <div class="field">
-                <div class="label">
-                    <label for="pullrequest_title">${_('Title')}:</label>
-                </div>
-                <div class="input">
-                    ${h.text('pullrequest_title',class_="large",placeholder=_('Summarize the changes - or leave empty'))}
-                </div>
-             </div>
-
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="pullrequest_desc">${_('Description')}:</label>
-                </div>
-                <div class="textarea text-area editor">
-                    ${h.textarea('pullrequest_desc',size=30,placeholder=_('Write a short description on this pull request'))}
+    <div class="form panel-body">
+        <div class="settings clearfix">
+            <div class="form-group">
+                <label class="control-label" for="pullrequest_title">${_('Title')}:</label>
+                <div>
+                    ${h.text('pullrequest_title',class_='form-control',placeholder=_('Summarize the changes - or leave empty'))}
                 </div>
             </div>
 
-            <div class="field">
-                <div class="label label-textarea">
-                    <label for="pullrequest_desc">${_('Changeset flow')}:</label>
+            <div class="form-group">
+                <label class="control-label" for="pullrequest_desc">${_('Description')}:</label>
+                <div>
+                    ${h.textarea('pullrequest_desc',class_='form-control',placeholder=_('Write a short description on this pull request'))}
                 </div>
-                <div class="input">
+            </div>
+
+            <div class="form-group">
+                <label class="control-label">${_('Changeset flow')}:</label>
+                <div class="clearfix">
                     ##ORG
                     <div>
                         <div>
-                            <div style="padding:5px 3px 3px 3px;">
+                            <div>
                             <b>${_('Origin repository')}:</b> <span id="org_repo_desc">${c.db_repo.description.split('\n')[0]}</span>
                             </div>
                             <div>
                             ${h.select('org_repo','',c.cs_repos,class_='refs')}:${h.select('org_ref',c.default_cs_ref,c.cs_refs,class_='refs')}
                             </div>
-                            <div style="padding:5px 3px 3px 3px;">
+                            <div>
                             <b>${_('Revision')}:</b> <span id="org_rev_span">-</span>
                             </div>
                         </div>
                     </div>
 
                     ##OTHER, most Probably the PARENT OF THIS FORK
-                    <div style="border-top: 1px solid #EEE; margin: 5px 0px 0px 0px">
+                    <div>
                         <div>
                             ## filled with JS
-                            <div style="padding:5px 3px 3px 3px;">
+                            <div>
                             <b>${_('Destination repository')}:</b> <span id="other_repo_desc">${c.a_repo.description.split('\n')[0]}</span>
                             </div>
                             <div>
                             ${h.select('other_repo',c.a_repo.repo_name,c.a_repos,class_='refs')}:${h.select('other_ref',c.default_a_ref,c.a_refs,class_='refs')}
                             </div>
-                            <div style="padding:5px 3px 3px 3px;">
+                            <div>
                             <b>${_('Revision')}:</b> <span id="other_rev_span">-</span>
                             </div>
                         </div>
                     </div>
-                    <div style="clear:both"></div>
                 </div>
             </div>
 
-            <div class="field">
+            <div class="form-group">
                 <div class="buttons">
-                    ${h.submit('save',_('Create Pull Request'),class_="btn")}
-                    ${h.reset('reset',_('Reset'),class_="btn")}
-               </div>
+                    ${h.submit('save',_('Create Pull Request'),class_="btn btn-default")}
+                    ${h.reset('reset',_('Reset'),class_="btn btn-default")}
+                </div>
             </div>
-
         </div>
 
-        <div style="clear:both;padding: 0 0 30px 0;"></div>
-
-        <h4>${_('Changesets')}</h4>
-        <div style="float:left;padding:0px 30px 30px 30px">
+        <div>
+           <h4>${_('Changesets')}</h4>
            ## overview pulled by ajax
-           <div style="float:left" id="pull_request_overview"></div>
+           <div id="pull_request_overview"></div>
         </div>
-        <div style="clear:both;"></div>
-
     </div>
 
     ${h.end_form()}
@@ -109,7 +93,7 @@
 
 <script type="text/javascript" src="${h.url('/js/graph.js', ver=c.kallithea_version)}"></script>
 <script type="text/javascript">
-  pyroutes.register('pullrequest_repo_info', "${url('pullrequest_repo_info',repo_name='%(repo_name)s')}", ['repo_name']);
+  pyroutes.register('pullrequest_repo_info', ${h.js(url('pullrequest_repo_info',repo_name='%(repo_name)s'))}, ['repo_name']);
 
   var pendingajax = undefined;
   var otherrepoChanged = function(){
@@ -134,13 +118,13 @@
                 var length = options.length;
                 for(var j = 0; j < length; j++)
                 {
-                  $optgroup.append($('<option/>').text(options[j][1]).val(options[j][0]));
+                  $optgroup.append($('<'+'option/>').text(options[j][1]).val(options[j][0]));
                 }
                 $other_ref.append($optgroup);
               }
               $other_ref.val(data.selected_ref);
 
-              // re-populate the select2 thingie
+              // re-populate the select2 thingy
               $("#other_ref").select2({
                   dropdownAutoWidth: true
               });
@@ -152,16 +136,16 @@
 
   var loadPreview = function(){
       //url template
-      var url = "${h.url('compare_url',
+      var url = ${h.js(h.url('compare_url',
                          repo_name='__other_repo__',
                          org_ref_type='rev',
                          org_ref_name='__other_ref_name__',
                          other_repo='__org_repo__',
                          other_ref_type='rev',
                          other_ref_name='__org_ref_name__',
-                         as_form=True,
+                         is_ajax_preview=True,
                          merge=True,
-                         )}";
+                         ))};
       var org_repo = $('#pull_request_form #org_repo').val();
       var org_ref = $('#pull_request_form #org_ref').val().split(':');
       ## TODO: make nice link like link_to_ref() do
@@ -188,9 +172,6 @@
       }
       pendingajax = asynchtml(url, $('#pull_request_overview'), function(o){
           pendingajax = undefined;
-          var jsdata = eval('('+YUD.get('jsdata').innerHTML+')'); // TODO: just get json
-          var r = new BranchRenderer('graph_canvas', 'graph_content_pr', 'chg_');
-          r.render(jsdata,100);
       });
   }
 
@@ -202,6 +183,7 @@
 
       $("#org_ref").select2({
           dropdownAutoWidth: true,
+          maxResults: 50,
           sortResults: branchSort
       });
       $("#org_ref").on("change", function(e){
@@ -217,6 +199,7 @@
 
       $("#other_ref").select2({
           dropdownAutoWidth: true,
+          maxResults: 50,
           sortResults: branchSort
       });
       $("#other_ref").on("change", function(e){
--- a/kallithea/templates/pullrequests/pullrequest_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_data.html	Sun Mar 31 21:28:56 2019 +0200
@@ -3,36 +3,36 @@
 <%def name="pullrequest_overview(pullrequests)">
 
 %if not len(pullrequests):
-    <div class="normal-indent empty_data">${_('No entries')}</div>
+    <div class="text-muted">${_('No entries')}</div>
     <% return %>
 %endif
 
-<div class="table">
-  <table>
+<div>
+  <table class="table">
     <thead>
       <tr>
-        <th class="left">${_('Vote')}</th>
-        <th class="left">${_('Title')}</th>
-        <th class="left">${_('Owner')}</th>
-        <th class="left">${_('Age')}</th>
-        <th class="left">${_('From')}</th>
-        <th class="left">${_('To')}</th>
-        <th class="right" style="padding-right:5px">${_('Delete')}</th>
+        <th>${_('Vote')}</th>
+        <th>${_('Title')}</th>
+        <th>${_('Owner')}</th>
+        <th>${_('Age')}</th>
+        <th>${_('From')}</th>
+        <th>${_('To')}</th>
+        <th>${_('Delete')}</th>
       </tr>
     </thead>
 % for pr in pullrequests:
     <tr class="${'pr-closed' if pr.is_closed() else ''}">
-      <td width="80px">
-        <% status = pr.user_review_status(c.authuser.user_id) %>
+      <td>
+        <% status = pr.user_review_status(request.authuser.user_id) %>
         %if status:
-          <i class="icon-circle changeset-status-${status}" title="${_("You voted: %s") % status}"></i>
+          <i class="icon-circle changeset-status-${status}" title="${_('You voted: %s') % h.changeset_status_lbl(status)}"></i>
         %else:
-          <i class="icon-circle changeset-status-not_reviewed" title="${_("You didn't vote")}"></i>
+          <i class="icon-circle changeset-status-not_reviewed" title="${_('You didn\'t vote')}"></i>
         %endif
       </td>
       <td>
         <a href="${pr.url()}">
-        ${pr.title or _("(no title)")}
+        ${h.urlify_text(pr.title or _("(no title)"), pr.org_repo.repo_name, pr.url())}
         %if pr.is_closed():
           <span class="pr-closed-tag">${_('Closed')}</span>
         %endif
@@ -42,7 +42,7 @@
         ${pr.owner.full_name_and_username}
       </td>
       <td>
-        <span class="tooltip" title="${h.fmt_date(pr.created_on)}">
+        <span data-toggle="tooltip" title="${h.fmt_date(pr.created_on)}">
           ${h.age(pr.created_on)}
         </span>
       </td>
@@ -58,10 +58,10 @@
           ${pr.other_repo.repo_name}#${other_ref_name}
         </a>
       </td>
-      <td style="text-align:right">
-        %if pr.owner.user_id == c.authuser.user_id:
-          ${h.form(url('pullrequest_delete', repo_name=pr.other_repo.repo_name, pull_request_id=pr.pull_request_id),method='delete', style="display:inline-block")}
-          <button class="action_button"
+      <td>
+        %if pr.owner_id == request.authuser.user_id:
+          ${h.form(url('pullrequest_delete', repo_name=pr.other_repo.repo_name, pull_request_id=pr.pull_request_id))}
+          <button type="submit" class="btn btn-link btn-xs"
                   id="remove_${pr.pull_request_id}"
                   name="remove_${pr.pull_request_id}"
                   title="${_('Delete Pull Request')}"
@@ -69,7 +69,7 @@
                       && ((${len(pr.comments)} == 0) ||
                           confirm('${_('Confirm again to delete this pull request with %s comments') % len(pr.comments)}'))
                       ">
-            <i class="icon-minus-circled"></i>
+            <i class="icon-trashcan"></i>
           </button>
           ${h.end_form()}
         %endif
@@ -80,11 +80,7 @@
 </div>
 
 %if hasattr(pullrequests, 'pager'):
-<div class="notification-paginator">
-  <div class="pagination-wh pagination-left">
-  ${pullrequests.pager('$link_previous ~2~ $link_next', **request.GET.mixed())}
-  </div>
-</div>
+    ${pullrequests.pager(**request.GET.mixed())}
 %endif
 
 </%def>
--- a/kallithea/templates/pullrequests/pullrequest_show.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_show.html	Sun Mar 31 21:28:56 2019 +0200
@@ -15,98 +15,71 @@
 </%block>
 
 <%def name="main()">
-<% editable = not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or c.pull_request.owner.user_id == c.authuser.user_id) %>
+<% editable = not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionLevel('admin')(c.repo_name) or c.pull_request.owner_id == request.authuser.user_id) %>
 ${self.repo_context_bar('showpullrequest')}
-<div class="box">
-  <!-- box / title -->
-  <div class="title">
+<div class="panel panel-primary">
+  <div class="panel-heading clearfix">
     ${self.breadcrumbs()}
   </div>
 
-  ${h.form(url('pullrequest_post', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), method='post', id='pull_request_form')}
-    <div class="form pr-box" style="float: left">
+  ${h.form(url('pullrequest_post', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), method='post', id='pull_request_form',class_='panel-body')}
+    <div class="form pr-box pull-left">
       <div class="pr-details-title ${'closed' if c.pull_request.is_closed() else ''}">
-          ${_('Title')}: ${c.pull_request.title}
+        <h3>
+          ${_('Title')}: ${h.urlify_text(c.pull_request.title, c.pull_request.org_repo.repo_name)}
           %if c.pull_request.is_closed():
               (${_('Closed')})
           %endif
+        </h3>
       </div>
-      <div id="pr-summary" class="fields">
+      <div id="pr-summary">
 
-        <div class="field pr-not-edit" style="min-height:37px">
-          <div class="label-summary">
-            <label>${_('Description')}:</label>
+        <div class="pr-not-edit form-group">
+            <label>${_('Description')}:
             %if editable:
-            <div style="margin: 5px">
-              <a class="btn btn-small" onclick="$('.pr-do-edit').show();$('.pr-not-edit').hide()">${_("Edit")}</a>
+            <div id="pr-edit-btn">
+              <a class="btn btn-default btn-xs" onclick="$('.pr-do-edit').show();$('.pr-not-edit').hide()">${_("Edit")}</a>
             </div>
             %endif
-          </div>
-          <div class="input">
-            <div style="white-space:pre-wrap; line-height: 14px">${h.urlify_commit(c.pull_request.description, c.pull_request.org_repo.repo_name)}</div>
-          </div>
+            </label>
+            <div>
+              <div class="formatted-fixed">${h.urlify_text(c.pull_request.description, c.pull_request.org_repo.repo_name)}</div>
+            </div>
         </div>
 
         %if editable:
-        <div class="pr-do-edit" style="display:none">
-          <div class="field">
-              <div class="label-summary">
-                  <label for="pullrequest_title">${_('Title')}:</label>
-              </div>
-              <div class="input">
-                  ${h.text('pullrequest_title',class_="large",value=c.pull_request.title,placeholder=_('Summarize the changes'))}
+        <div class="pr-do-edit form-group" style="display:none">
+              <label for="pullrequest_title">${_('Title')}:</label>
+              <div>
+                  ${h.text('pullrequest_title',class_='form-control',value=c.pull_request.title,placeholder=_('Summarize the changes'))}
               </div>
-          </div>
+        </div>
 
-          <div class="field">
-              <div class="label-summary label-textarea">
-                  <label for="pullrequest_desc">${_('Description')}:</label>
+        <div class="pr-do-edit form-group" style="display:none">
+              <label for="pullrequest_desc">${_('Description')}:</label>
+              <div>
+                  ${h.textarea('pullrequest_desc',content=c.pull_request.description,placeholder=_('Write a short description on this pull request'),class_='form-control')}
               </div>
-              <div class="textarea text-area editor">
-                  ${h.textarea('pullrequest_desc',size=30,content=c.pull_request.description,placeholder=_('Write a short description on this pull request'))}
-              </div>
-          </div>
         </div>
         %endif
 
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Reviewer voting result')}:</label>
-          </div>
-          <div class="input">
-            <div class="changeset-status-container" style="float:none;clear:both">
+        <div class="form-group">
+          <label>${_('Voting Result')}:</label>
+          <div>
             %if c.current_voting_result:
-              <span class="changeset-status-ico" style="padding:0px 4px 0px 0px">
-                  <i class="icon-circle changeset-status-${c.current_voting_result}" title="${_('Pull request status calculated from votes')}"></i></span>
-              <span class="changeset-status-lbl tooltip" title="${_('Pull request status calculated from votes')}">
+              <i class="icon-circle changeset-status-${c.current_voting_result}" title="${_('Pull request status calculated from votes')}"></i>
+              <span class="changeset-status-lbl" data-toggle="tooltip" title="${_('Pull request status calculated from votes')}">
                 %if c.pull_request.is_closed():
                     ${_('Closed')},
                 %endif
                 ${h.changeset_status_lbl(c.current_voting_result)}
               </span>
             %endif
-            </div>
           </div>
         </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Still not reviewed by')}:</label>
-          </div>
-          <div class="input">
-            % if len(c.pull_request_pending_reviewers) > 0:
-                <div class="tooltip" title="${', '.join([x.username for x in c.pull_request_pending_reviewers])}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
-            % elif len(c.pull_request_reviewers) > 0:
-                <div>${_('Pull request was reviewed by all reviewers')}</div>
-            %else:
-                <div>${_('There are no reviewers')}</div>
-            %endif
-          </div>
-        </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Origin')}:</label>
-          </div>
-          <div class="input">
+        <div class="form-group">
+          <label>${_('Origin')}:</label>
+          <div>
             <div>
               ${h.link_to_ref(c.pull_request.org_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev)}
               %if c.cs_ref_type != 'branch':
@@ -115,11 +88,9 @@
             </div>
           </div>
         </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Target')}:</label>
-          </div>
-          <div class="input">
+        <div class="form-group">
+          <label>${_('Target')}:</label>
+          <div>
             %if c.is_range:
               ${_("This is just a range of changesets and doesn't have a target or a real merge ancestor.")}
             %else:
@@ -128,237 +99,236 @@
             %endif
           </div>
         </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Pull changes')}:</label>
-          </div>
-          <div class="input">
+        <div class="form-group">
+          <label>${_('Pull changes')}:</label>
+          <div>
+            %if c.cs_ranges:
               <div>
                ## TODO: use cs_ranges[-1] or org_ref_parts[1] in both cases?
                %if h.is_hg(c.pull_request.org_repo):
-                 <span style="font-family: monospace">hg pull ${c.pull_request.org_repo.clone_url()} -r ${h.short_id(c.cs_ranges[-1].raw_id)}</span>
+                 <span>hg pull ${c.pull_request.org_repo.clone_url()} -r ${h.short_id(c.cs_ranges[-1].raw_id)}</span>
                %elif h.is_git(c.pull_request.org_repo):
-                 <span style="font-family: monospace">git pull ${c.pull_request.org_repo.clone_url()} ${c.pull_request.org_ref_parts[1]}</span>
+                 <span>git pull ${c.pull_request.org_repo.clone_url()} ${c.pull_request.org_ref_parts[1]}</span>
                %endif
               </div>
+            %endif
           </div>
         </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Created on')}:</label>
-          </div>
-          <div class="input">
+        <div class="form-group">
+          <label>${_('Created on')}:</label>
+          <div>
               <div>${h.fmt_date(c.pull_request.created_on)}</div>
           </div>
         </div>
-        <div class="field">
-          <div class="label-summary">
-              <label>${_('Owner')}:</label>
-          </div>
-          <div class="input pr-not-edit">
-                  <div class="gravatar">
-                    ${h.gravatar(c.pull_request.owner.email, size=20)}
-                  </div>
+        <div class="form-group">
+          <label>${_('Owner')}:</label>
+          <div class="pr-not-edit">
+                  ${h.gravatar_div(c.pull_request.owner.email, size=20)}
                   <span>${c.pull_request.owner.full_name_and_username}</span><br/>
                   <span><a href="mailto:${c.pull_request.owner.email}">${c.pull_request.owner.email}</a></span><br/>
           </div>
-          <div class="input pr-do-edit ac" style="display:none">
-               ${h.text('owner', class_="large", value=c.pull_request.owner.username, placeholder=_('Username'))}
-               <div id="owner_completion_container"></div>
+          <div class="pr-do-edit" style="display:none">
+               ${h.text('owner', class_='form-control', value=c.pull_request.owner.username, placeholder=_('Type name of user'))}
           </div>
         </div>
 
-        <div class="field">
-            <div class="label-summary">
-              <label>${_('Update')}:</label>
-            </div>
-            <div class="input">
-              <div class="msg-div">${c.update_msg}</div>
-              %if c.avail_revs:
-              <div id="updaterevs" style="max-height:200px; overflow-y:auto; overflow-x:hidden; margin-bottom: 10px; padding: 1px 0">
-                <div style="height:0">
-                  <canvas id="avail_graph_canvas" style="width:0"></canvas>
+        <div class="form-group">
+          <label>${_('Next iteration')}:</label>
+            <div>
+              <p>${c.update_msg}</p>
+              %if c.avail_cs:
+              <div id="updaterevs" class="clearfix">
+                <div id="updaterevs-graph">
+                  <canvas id="avail_graph_canvas"></canvas>
                 </div>
-                <table id="updaterevs-table" class="noborder" style="padding-left:50px">
+                <table class="table" id="updaterevs-table">
                   %for cnt, cs in enumerate(c.avail_cs):
-                    <tr id="chg_available_${cnt+1}">
-                      %if cs.revision == c.cs_ranges[-1].revision:
+                    <tr id="chg_available_${cnt+1}" class="${'mergerow' if len(cs.parents) > 1 and not (editable and cs.revision in c.avail_revs) else ''}">
+                      %if c.cs_ranges and cs.revision == c.cs_ranges[-1].revision:
+                        %if editable:
                         <td>
-                          %if editable:
-                            ${h.radio(name='updaterev', value='')}
-                          %endif
+                            ${h.radio(name='updaterev', value='', checked=True)}
+                        </td>
+                        %endif
+                        <td colspan="4"><span>${_("Current revision - no change")}</span></td>
+                      %else:
+                        %if editable:
+                        <td>
+                          ${h.radio(name='updaterev', value=cs.raw_id, style=None if cs.revision in c.avail_revs else 'visibility: hidden')}
                         </td>
-                        <td colspan="2">${_("Current revision - no change")}</td>
-                      %else:
+                        %endif
+                        <td><span data-toggle="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
+                        <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id), class_='changeset_hash')}</td>
                         <td>
-                          %if editable and cs.revision in c.avail_revs:
-                            ${h.radio(name='updaterev', value=cs.raw_id)}
-                          %endif
+                          <div class="pull-right">
+                            %for tag in cs.tags:
+                              <span class="label label-tag" title="${_('Tag %s') % tag}">
+                                ${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
+                              </span>
+                            %endfor
+                          </div>
+                          <div class="message">${h.urlify_text(cs.message, c.repo_name)}</div>
                         </td>
-                        <td>${h.link_to(h.show_id(cs),h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</td>
-                        <td><div class="message" style="white-space:normal; height:1.1em; max-width: 500px; padding:0">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
                       %endif
                     </tr>
                   %endfor
                 </table>
               </div>
+              <div class="alert alert-info">${_("Pull request iterations do not change content once created. Select a revision to create a new iteration.")}</div>
               %endif
-              <div class="msg-div">${c.update_msg_other}</div>
+              %if c.update_msg_other:
+                <div class="alert alert-info">${c.update_msg_other}</div>
+              %endif
             </div>
         </div>
+        %if editable:
+        <div class="form-group">
+          <div class="buttons">
+            ${h.submit('pr-form-save',_('Save Changes'),class_="btn btn-default btn-sm")}
+            ${h.submit('pr-form-clone',_('Create New Iteration with Changes'),class_="btn btn-default btn-sm",disabled='disabled')}
+            ${h.reset('pr-form-reset',_('Cancel Changes'),class_="btn btn-default btn-sm")}
+          </div>
+        </div>
+        %endif
       </div>
     </div>
     ## REVIEWERS
-    <div style="float:left; border-left:1px dashed #eee">
-        <div class="pr-details-title">${_('Pull Request Reviewers')}</div>
-        <div id="reviewers" style="padding:0px 0px 5px 10px">
+    <div class="pr-reviewers-box pull-left">
+        <h4 class="pr-details-title">${_('Reviewers')}</h4>
+        <div id="reviewers">
           ## members goes here !
           <div>
-            <ul id="review_members" class="group_members">
+            %for member,status in c.pull_request_reviewers:
+              <input type="hidden" value="${member.user_id}" name="org_review_members" />
+            %endfor
+            <ul id="review_members" class="list-unstyled">
             %for member,status in c.pull_request_reviewers:
               ## WARNING: the HTML below is duplicate with
               ## kallithea/public/js/base.js
               ## If you change something here it should be reflected in the template too.
               <li id="reviewer_${member.user_id}">
-                <div class="reviewers_member">
-                    <div class="reviewer_status tooltip" title="${h.changeset_status_lbl(status.status if status else 'not_reviewed')}">
-                      <i class="icon-circle changeset-status-${status.status if status else 'not_reviewed'}"></i>
-                    </div>
-                  <div class="reviewer_gravatar gravatar">
-                    ${h.gravatar(member.email, size=14)}
-                  </div>
-                  <div style="float:left;">
+                <span class="reviewers_member">
+                  <input type="hidden" value="${member.user_id}" name="review_members" />
+                  <span class="reviewer_status" data-toggle="tooltip" title="${h.changeset_status_lbl(status)}">
+                      <i class="icon-circle changeset-status-${status}"></i>
+                  </span>
+                  ${h.gravatar(member.email, size=14)}
+                  <span>
                     ${member.full_name_and_username}
-                    %if c.pull_request.user_id == member.user_id:
+                    %if c.pull_request.owner_id == member.user_id:
                       (${_('Owner')})
                     %endif
-                  </div>
-                  <input type="hidden" value="${member.user_id}" name="review_members" />
+                  </span>
                   %if editable:
-                  <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}">
+                  <a href="#" class="reviewer_member_remove" onclick="removeReviewMember(${member.user_id})" title="${_('Remove reviewer')}">
                       <i class="icon-minus-circled"></i>
-                  </div>
+                  </a>
                   %endif
-                </div>
+                </span>
               </li>
             %endfor
             </ul>
           </div>
           %if editable:
-          <div class='ac'>
-            <div class="reviewer_ac">
-               ${h.text('user', class_='yui-ac-input',placeholder=_('Type name of reviewer to add'))}
-               <div id="reviewers_container"></div>
-            </div>
+          <div>
+             ${h.text('user', class_='form-control',placeholder=_('Type name of reviewer to add'))}
           </div>
           %endif
         </div>
 
         %if not c.pull_request_reviewers:
-        <div class="pr-details-title">${_('Potential Reviewers')}</div>
-        <div style="margin: 10px 0 10px 10px; max-width: 250px">
+        <h4>${_('Potential Reviewers')}</h4>
+        <div>
           <div>
             ${_('Click to add the repository owner as reviewer:')}
           </div>
-          <ul style="margin-top: 10px">
-            %for u in [c.pull_request.other_repo.user]:
+          <ul class="list-unstyled">
+            %for u in [c.pull_request.other_repo.owner]:
               <li>
-                <a class="missing_reviewer missing_reviewer_${u.user_id}"
-                  user_id="${u.user_id}"
-                  fname="${u.name}"
-                  lname="${u.lastname}"
-                  nname="${u.username}"
-                  gravatar_lnk="${h.gravatar_url(u.email, size=28)}"
-                  gravatar_size="14"
-                  title="Click to add reviewer to the list, then Save Changes.">${u.full_name}</a>
+                <a class="btn btn-default btn-xs missing_reviewer missing_reviewer_${u.user_id}"
+                  href="#"
+                  data-user_id="${u.user_id}"
+                  data-fname="${u.name}"
+                  data-lname="${u.lastname}"
+                  data-nname="${u.username}"
+                  data-gravatar_lnk="${h.gravatar_url(u.email, size=28, default='default')}"
+                  data-gravatar_size="14"
+                  title="Click to add reviewer to the list, then Save Changes."><i class="icon-plus"></i>${u.full_name}</a>
               </li>
             %endfor
           </ul>
         </div>
         %endif
     </div>
-    <div class="form" style="clear:both">
-      <div class="fields">
-        %if editable:
-          <div class="buttons">
-            ${h.submit('pr-form-save',_('Save Changes'),class_="btn btn-small")}
-            ${h.submit('pr-form-clone',_('Save as New Pull Request'),class_="btn btn-small",disabled='disabled')}
-            ${h.reset('pr-form-reset',_('Cancel Changes'),class_="btn btn-small")}
-          </div>
-        %endif
-      </div>
-    </div>
   ${h.end_form()}
 </div>
 
-<div class="box">
-    <div class="title">
-      <div class="breadcrumbs">${_('Pull Request Content')}</div>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+      <div class="panel-title">${_('Pull Request Content')}</div>
     </div>
-    <div class="table">
-          <div id="changeset_compare_view_content">
-              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+    <div class="panel-body">
+        <div>
+            <div id="changeset_compare_view_content">
+              <h5>
                   ${comment.comment_count(c.inline_cnt, len(c.comments))}
-              </div>
+              </h5>
               ##CS
-              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+              <h5>
                 ${ungettext('Showing %s commit','Showing %s commits', len(c.cs_ranges)) % len(c.cs_ranges)}
-              </div>
+              </h5>
               <%include file="/compare/compare_cs.html" />
 
-              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
+              <h5>
               ${_('Common ancestor')}:
-              ${h.link_to(h.short_id(c.a_rev),h.url('changeset_home',repo_name=c.a_repo.repo_name,revision=c.a_rev))}
-              </div>
+              ${h.link_to(h.short_id(c.a_rev),h.url('changeset_home',repo_name=c.a_repo.repo_name,revision=c.a_rev), class_="changeset_hash")}
+              </h5>
 
               ## FILES
-              <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">
-
+              <h5>
               % if c.limited_diff:
-                  ${ungettext('%s file changed', '%s files changed', len(c.files)) % len(c.files)}
+                  ${ungettext('%s file changed', '%s files changed', len(c.file_diff_data)) % len(c.file_diff_data)}:
               % else:
-                  ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.files)) % (len(c.files),c.lines_added,c.lines_deleted)}:
+                  ${ungettext('%s file changed with %s insertions and %s deletions','%s files changed with %s insertions and %s deletions', len(c.file_diff_data)) % (len(c.file_diff_data),c.lines_added,c.lines_deleted)}:
               %endif
-
-              </div>
+              </h5>
               <div class="cs_files">
-                %if not c.files:
-                   <span class="empty_data">${_('No files')}</span>
+                %if not c.file_diff_data:
+                   <span class="text-muted">${_('No files')}</span>
                 %endif
-                %for fid, change, f, stat in c.files:
-                    <div class="cs_${change}">
-                      <div class="node">
-                          <i class="icon-diff-${change}"></i>
-                          ${h.link_to(h.safe_unicode(f),'#' + fid)}
-                      </div>
-                      <div class="changes">${h.fancy_file_stats(stat)}</div>
+                %for fid, url_fid, op, a_path, path, diff, stats in c.file_diff_data:
+                    <div class="cs_${op} clearfix">
+                      <span class="node">
+                          <i class="icon-diff-${op}"></i>
+                          ${h.link_to(h.safe_unicode(path), '#%s' % fid)}
+                      </span>
+                      <div class="changes">${h.fancy_file_stats(stats)}</div>
                     </div>
                 %endfor
+                %if c.limited_diff:
+                  <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
+                %endif
               </div>
-              % if c.limited_diff:
-                <h5>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h5>
-              % endif
-          </div>
+            </div>
+        </div>
     </div>
     <script>
-    var _USERS_AC_DATA = ${c.users_array|n};
-    var _GROUPS_AC_DATA = ${c.user_groups_array|n};
     // TODO: switch this to pyroutes
-    AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
-    AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
+    AJAX_COMMENT_URL = ${h.js(url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id))};
+    AJAX_COMMENT_DELETE_URL = ${h.js(url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__'))};
 
-    pyroutes.register('pullrequest_comment', "${url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s')}", ['repo_name', 'pull_request_id']);
-    pyroutes.register('pullrequest_comment_delete', "${url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s')}", ['repo_name', 'comment_id']);
+    pyroutes.register('pullrequest_comment', ${h.js(url('pullrequest_comment',repo_name='%(repo_name)s',pull_request_id='%(pull_request_id)s'))}, ['repo_name', 'pull_request_id']);
+    pyroutes.register('pullrequest_comment_delete', ${h.js(url('pullrequest_comment_delete',repo_name='%(repo_name)s',comment_id='%(comment_id)s'))}, ['repo_name', 'comment_id']);
 
     </script>
 
     ## diff block
+    <div class="panel-body">
     <div class="commentable-diff">
     <%namespace name="diff_block" file="/changeset/diff_block.html"/>
     ${diff_block.diff_block_js()}
-    %for fid, change, f, stat in c.files:
-      ${diff_block.diff_block_simple([c.changes[fid]])}
-    %endfor
+    ${diff_block.diff_block(c.a_repo.repo_name, c.a_ref_type, c.a_ref_name, c.a_rev,
+                            c.cs_repo.repo_name, c.cs_ref_type, c.cs_ref_name, c.cs_rev, c.file_diff_data)}
     % if c.limited_diff:
       <h4>${_('Changeset was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}">${_('Show full diff anyway')}</a></h4>
     % endif
@@ -371,23 +341,24 @@
     ${comment.generate_comments()}
 
     ## main comment form and it status
-    ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
-                              pull_request_id=c.pull_request.pull_request_id),
-                       c.current_voting_result,
-                       is_pr=True, change_status=c.allowed_to_change_status)}
+    ${comment.comments(change_status=c.allowed_to_change_status)}
 
     <script type="text/javascript">
       $(document).ready(function(){
-          PullRequestAutoComplete($('#user'), $('#reviewers_container'), _USERS_AC_DATA);
-          SimpleUserAutoComplete($('#owner'), $('#owner_completion_container'), _USERS_AC_DATA);
+          PullRequestAutoComplete($('#user'));
+          SimpleUserAutoComplete($('#owner'));
 
           $('.code-difftable').on('click', '.add-bubble', function(e){
               show_comment_form($(this));
           });
 
-          var avail_jsdata = ${c.avail_jsdata|n};
+          var avail_jsdata = ${h.js(c.avail_jsdata)};
           var avail_r = new BranchRenderer('avail_graph_canvas', 'updaterevs-table', 'chg_available_');
-          avail_r.render(avail_jsdata,40);
+          avail_r.render(avail_jsdata);
+
+          $(window).resize(function(){
+              avail_r.render(avail_jsdata);
+          });
 
           move_comments($(".comments .comments-list-chunk"));
 
@@ -412,17 +383,11 @@
 
           $('.missing_reviewer').click(function(){
             var $this = $(this);
-            addReviewMember(
-                $this.attr('user_id'),
-                $this.attr('fname').html_escape(),
-                $this.attr('lname').html_escape(),
-                $this.attr('nname').html_escape(),
-                $this.attr('gravatar_lnk'),
-                $this.attr('gravatar_size')
-            );
+            addReviewMember($this.data('user_id'), $this.data('fname'), $this.data('lname'), $this.data('nname'), $this.data('gravatar_lnk'), $this.data('gravatar_size'));
           });
       });
     </script>
+    </div>
 
 </div>
 
--- a/kallithea/templates/pullrequests/pullrequest_show_all.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_show_all.html	Sun Mar 31 21:28:56 2019 +0200
@@ -21,29 +21,24 @@
 <%def name="main()">
 ${self.repo_context_bar('showpullrequest')}
 
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-        <ul class="links">
-          <li>
-             %if c.authuser.username != 'default':
-              <span>
-                  <a id="open_new_pr" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-plus"></i> ${_('Open New Pull Request')}</a>
-              </span>
-             %endif
-              <span>
-                %if c.from_:
-                    <a class="btn btn-small" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed)}"><i class="icon-git-compare"></i> ${_('Show Pull Requests to %s') % c.repo_name}</a>
-                %else:
-                    <a class="btn btn-small" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed,from_=1)}"><i class="icon-git-compare"></i> ${_("Show Pull Requests from '%s'") % c.repo_name}</a>
-                %endif
-              </span>
-          </li>
-        </ul>
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
+        <div class="pull-left">
+            ${self.breadcrumbs()}
+        </div>
+        <div class="pull-right">
+            %if request.authuser.username != 'default':
+                <a id="open_new_pr" class="btn btn-success btn-xs" href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-plus"></i>${_('Open New Pull Request')}</a>
+            %endif
+            %if c.from_:
+                <a class="btn btn-default btn-xs" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed)}"><i class="icon-git-compare"></i>${_('Show Pull Requests to %s') % c.repo_name}</a>
+            %else:
+                <a class="btn btn-default btn-xs" href="${h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed,from_=1)}"><i class="icon-git-compare"></i>${_("Show Pull Requests from '%s'") % c.repo_name}</a>
+            %endif
+        </div>
     </div>
 
-    <div class="normal-indent">
+    <div class="panel-body">
         <div>
         %if c.closed:
             ${h.link_to(_('Hide closed pull requests (only show open pull requests)'), h.url('pullrequest_show_all',repo_name=c.repo_name,from_=c.from_))}
@@ -51,9 +46,9 @@
             ${h.link_to(_('Show closed pull requests (in addition to open pull requests)'), h.url('pullrequest_show_all',repo_name=c.repo_name,from_=c.from_,closed=1))}
         %endif
         </div>
+
+        ${pullrequest_data.pullrequest_overview(c.pullrequests_pager)}
     </div>
 
-    ${pullrequest_data.pullrequest_overview(c.pullrequests_pager)}
-
 </div>
 </%def>
--- a/kallithea/templates/pullrequests/pullrequest_show_my.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/pullrequests/pullrequest_show_my.html	Sun Mar 31 21:28:56 2019 +0200
@@ -16,13 +16,13 @@
 
 <%def name="main()">
 
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div class="normal-indent">
+    <div class="panel-body">
+      <div>
         <div>
         %if c.closed:
             ${h.link_to(_('Hide closed pull requests (only show open pull requests)'), h.url('my_pullrequests'))}
@@ -30,13 +30,16 @@
             ${h.link_to(_('Show closed pull requests (in addition to open pull requests)'), h.url('my_pullrequests',closed=1))}
         %endif
         </div>
-    </div>
+
+        <h4>${_('Pull Requests Created by Me')}</h4>
+        ${pullrequest_data.pullrequest_overview(c.my_pull_requests)}
 
-    <div class="pullrequests_section_head">${_('Pull Requests Created by Me')}</div>
-    ${pullrequest_data.pullrequest_overview(c.my_pull_requests)}
+        <h4>${_('Pull Requests Needing My Review')}</h4>
+        ${pullrequest_data.pullrequest_overview(c.participate_in_pull_requests_todo)}
 
-    <div class="pullrequests_section_head" style="clear:both">${_('Pull Requests I Participate In')}</div>
-    ${pullrequest_data.pullrequest_overview(c.participate_in_pull_requests)}
-
+        <h4>${_('Pull Requests I Participate In')}</h4>
+        ${pullrequest_data.pullrequest_overview(c.participate_in_pull_requests)}
+      </div>
+    </div>
 </div>
 </%def>
--- a/kallithea/templates/register.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/register.html	Sun Mar 31 21:28:56 2019 +0200
@@ -5,114 +5,98 @@
     ${_('Sign Up')}
 </%block>
 
-<div id="register">
-    <%include file="/base/flash_msg.html"/>
-    <div class="title withlogo">
+<%block name="js_extra">
+    %if c.captcha_active:
+        <script type="text/javascript" src="https://www.google.com/recaptcha/api.js"></script>
+    %endif
+</%block>
+
+<%include file="/base/flash_msg.html"/>
+
+<div class="container">
+<div class="row">
+<div class="centered-column">
+<div id="register" class="panel panel-primary">
+    <div class="panel-heading">
         %if c.site_name:
             <h5>${_('Sign Up to %s') % c.site_name}</h5>
         %else:
             <h5>${_('Sign Up')}</h5>
         %endif
     </div>
-    <div class="inner">
+    <div class="panel-body">
         ${h.form(url('register'))}
         <div class="form">
-            <!-- fields -->
-            <div class="fields">
-                <div class="field">
-                    <div class="label">
-                        <label for="username">${_('Username')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('username',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="username">${_('Username')}:</label>
+                    <div>
+                        ${h.text('username',class_='form-control')}
                     </div>
                 </div>
 
-                <div class="field">
-                    <div class="label">
-                        <label for="password">${_('Password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="password">${_('Password')}:</label>
+                    <div>
+                        ${h.password('password',class_='form-control')}
                     </div>
                 </div>
 
-                <div class="field">
-                    <div class="label">
-                        <label for="password_confirmation">${_('Re-enter password')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.password('password_confirmation',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="password_confirmation">${_('Re-enter password')}:</label>
+                    <div>
+                        ${h.password('password_confirmation',class_='form-control')}
                     </div>
                 </div>
 
-                <div class="field">
-                    <div class="label">
-                        <label for="firstname">${_('First Name')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('firstname',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="firstname">${_('First Name')}:</label>
+                    <div>
+                        ${h.text('firstname',class_='form-control')}
                     </div>
                 </div>
 
-                <div class="field">
-                    <div class="label">
-                        <label for="lastname">${_('Last Name')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('lastname',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="lastname">${_('Last Name')}:</label>
+                    <div>
+                        ${h.text('lastname',class_='form-control')}
                     </div>
                 </div>
 
-                <div class="field">
-                    <div class="label">
-                        <label for="email">${_('Email')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.text('email',class_="medium")}
+                <div class="form-group">
+                    <label class="control-label" for="email">${_('Email')}:</label>
+                    <div>
+                        ${h.text('email',class_='form-control')}
                     </div>
                 </div>
 
                 %if c.captcha_active:
-                <div class="field">
-                    <div class="label">
-                        <label for="email">${_('Captcha')}:</label>
-                    </div>
-                    <div class="input">
-                        ${h.hidden('recaptcha_field')}
-                        <div id="recaptcha"></div>
+                <div class="form-group">
+                    <label class="control-label" for="recaptcha_field">${_('Captcha')}:</label>
+                    <div>
+                        <div id="recaptcha_field" class="g-recaptcha" data-sitekey="${c.captcha_public_key}"></div>
                     </div>
                 </div>
                 %endif
 
-                <div class="buttons">
-                    <div class="nohighlight">
-                      ${h.submit('sign_up',_('Sign Up'),class_="btn")}
-                      %if c.auto_active:
-                          <div class="activation_msg">${_('Registered accounts are ready to use and need no further action.')}</div>
-                      %else:
-                          <div class="activation_msg">${_('Please wait for an administrator to activate your account.')}</div>
-                      %endif
+                <div class="form-group">
+                    <div class="buttons">
+                        ${h.submit('sign_up',_('Sign Up'),class_="btn btn-default")}
+                        %if c.auto_active:
+                            <div class="alert alert-info">${_('Registered accounts are ready to use and need no further action.')}</div>
+                        %else:
+                            <div class="alert alert-info">${_('Please wait for an administrator to activate your account.')}</div>
+                        %endif
                     </div>
                 </div>
-            </div>
         </div>
         ${h.end_form()}
-        %if c.captcha_active:
-        <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
-        %endif
         <script type="text/javascript">
         $(document).ready(function(){
             $('#username').focus();
-
-            %if c.captcha_active:
-            Recaptcha.create("${c.captcha_public_key}", "recaptcha",
-                {
-                  theme: "white"
-                }
-            );
-            %endif
         });
         </script>
     </div>
  </div>
+ </div>
+ </div>
+ </div>
--- a/kallithea/templates/search/search.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/search/search.html	Sun Mar 31 21:28:56 2019 +0200
@@ -32,48 +32,49 @@
 %if c.repo_name:
 ${self.repo_context_bar('options')}
 %endif
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
+    <div class="panel-body settings form">
     %if c.repo_name:
         ${h.form(h.url('search_repo',repo_name=c.repo_name),method='get')}
     %else:
         ${h.form(h.url('search'),method='get')}
     %endif
-    <div class="form">
-        <div class="fields">
-            <div class="field field-first field-noborder">
-             <div class="label">
-                 <label for="q">${_('Search term')}:</label>
-             </div>
-                <div class="input">${h.text('q',c.cur_query,class_="small")}
-                    <div class="button highlight">
-                        <input type="submit" value="${_('Search')}" class="btn"/>
-                    </div>
+
+            <div class="form-group">
+                <label class="control-label" for="q">${_('Search term')}:</label>
+                <div>
+                    ${h.text('q',c.cur_query,class_='form-control')}
                 </div>
-                <div style="font-weight: bold;clear:both;margin-left:200px">${c.runtime}</div>
             </div>
 
-            <div class="field">
-                <div class="label">
-                    <label for="type">${_('Search in')}:</label>
-                </div>
-                <div class="select">
+            <div class="form-group">
+                <label class="control-label" for="type">${_('Search in')}:</label>
+                <div>
                     ${h.select('type',c.cur_type,[('content',_('File contents')),
                         ('commit',_('Commit messages')),
                         ('path',_('File names')),
                         ##('repository',_('Repository names')),
-                        ])}
+                        ],
+                        class_='form-control')}
                 </div>
-             </div>
+            </div>
 
-        </div>
+            <div class="form-group">
+                <div class="buttons">
+                    <input type="submit" value="${_('Search')}" class="btn btn-default"/>
+                </div>
+            </div>
+
+            <div class="form-group">
+                ## <label>${_('Status')}:</label>
+                <div>${c.runtime}</div>
+            </div>
+        ${h.end_form()}
     </div>
-    ${h.end_form()}
-    <div class="search">
+    <div class="panel-body">
     %if c.cur_type == 'content':
         <%include file='search_content.html'/>
     %elif c.cur_type == 'path':
--- a/kallithea/templates/search/search_commit.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/search/search_commit.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,45 +1,36 @@
-##commit highligthing
+##commit highlighting
 
 %for cnt,sr in enumerate(c.formated_results):
-    %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
-    <div class="table">
-        <div id="body${cnt}" class="codeblock">
-            <div class="code-header">
-                <div class="search-path">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['raw_id'])),
-                h.url('changeset_home',repo_name=sr['repository'],revision=sr['raw_id']))}
+    %if h.HasRepoPermissionLevel('read')(sr['repository'],'search results check'):
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['raw_id'])),
+                    h.url('changeset_home',repo_name=sr['repository'],revision=sr['raw_id']))}
                 ${h.fmt_date(h.time_to_datetime(sr['date']))}
-                </div>
             </div>
-            <div class="left">
+            <div class="panel-body">
                 <div class="author">
-                    <div class="gravatar">
-                      ${h.gravatar(h.email_or_none(sr['author']), size=20)}
-                    </div>
+                    ${h.gravatar_div(h.email_or_none(sr['author']), size=20)}
                     <span>${h.person(sr['author'])}</span><br/>
-                    <span>${h.email_or_none(sr['author'])}</a></span><br/>
+                    <span>${h.email_or_none(sr['author'])}</span><br/>
                 </div>
                 %if sr['message_hl']:
                 <div class="search-code-body">
                     <pre>${h.literal(sr['message_hl'])}</pre>
                 </div>
                 %else:
-                <div class="message">${h.urlify_commit(sr['message'], sr['repository'])}</div>
+                <div class="message">${h.urlify_text(sr['message'], sr['repository'])}</div>
                 %endif
             </div>
         </div>
-    </div>
     %else:
         %if cnt == 0:
-        <div class="table">
-            <div id="body${cnt}" class="codeblock">
-                <div class="error">${_('Permission denied')}</div>
+            <div class="alert alert-warning" role="alert">
+                ${_('Permission denied')}
             </div>
-        </div>
         %endif
     %endif
 %endfor
 %if c.cur_query and c.formated_results:
-<div class="pagination-wh pagination-left">
-    ${c.formated_results.pager('$link_previous ~2~ $link_next')}
-</div>
+    ${c.formated_results.pager()}
 %endif
--- a/kallithea/templates/search/search_content.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/search/search_content.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,32 +1,24 @@
-##content highligthing
+##content highlighting
 
 %for cnt,sr in enumerate(c.formated_results):
-    %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
-    <div class="table">
-        <div id="body${cnt}" class="codeblock">
-            <div class="code-header">
-                <div class="search-path">${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
-                h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
-                </div>
+    %if h.HasRepoPermissionLevel('read')(sr['repository'],'search results check'):
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
+                    h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
             </div>
-            <div class="search-code-body">
+            <div class="panel-body search-code-body">
                 <pre>${h.literal(sr['content_short_hl'])}</pre>
             </div>
         </div>
-    </div>
     %else:
         %if cnt == 0:
-        <div class="table">
-            <div id="body${cnt}" class="codeblock">
-                <div class="error">${_('Permission denied')}</div>
+            <div class="alert alert-warning" role="alert">
+                ${_('Permission denied')}
             </div>
-        </div>
         %endif
-
     %endif
 %endfor
 %if c.cur_query and c.formated_results:
-<div class="pagination-wh pagination-left" style="padding-left:16px">
-    ${c.formated_results.pager('$link_previous ~2~ $link_next')}
-</div>
+    ${c.formated_results.pager()}
 %endif
--- a/kallithea/templates/search/search_path.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/search/search_path.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,26 +1,21 @@
 ##path search
 
 %for cnt,sr in enumerate(c.formated_results):
-    %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'):
-        <div class="search_path">
-            <div class="link">
+    %if h.HasRepoPermissionLevel('read')(sr['repository'],'search results check'):
+        <div class="panel panel-default">
+            <div class="panel-heading">
                 ${h.link_to(h.literal('%s &raquo; %s' % (sr['repository'],sr['f_path'])),
                     h.url('files_home',repo_name=sr['repository'],revision='tip',f_path=sr['f_path']))}
             </div>
         </div>
     %else:
         %if cnt == 0:
-            <div class="error">
-                <div class="link">
-                    ${_('Permission denied')}
-                </div>
+            <div class="alert alert-warning" role="alert">
+                ${_('Permission denied')}
             </div>
         %endif
-
     %endif
 %endfor
 %if c.cur_query and c.formated_results:
-<div class="pagination-wh pagination-left">
-    ${c.formated_results.pager('$link_previous ~2~ $link_next')}
-</div>
+    ${c.formated_results.pager()}
 %endif
--- a/kallithea/templates/summary/statistics.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/summary/statistics.html	Sun Mar 31 21:28:56 2019 +0200
@@ -13,48 +13,46 @@
 </%block>
 
 <%block name="head_extra">
-  <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
-  <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
+  <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=request.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
+  <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=request.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
+  <script type="text/javascript" src="${h.url('/js/jquery.flot.js', ver=c.kallithea_version)}"></script>
+  <script type="text/javascript" src="${h.url('/js/jquery.flot.selection.js', ver=c.kallithea_version)}"></script>
+  <script type="text/javascript" src="${h.url('/js/jquery.flot.time.js', ver=c.kallithea_version)}"></script>
 </%block>
 
 <%def name="main()">
 ${self.repo_context_bar('summary')}
-    <%
-    summary = lambda n:{False:'summary-short'}.get(n)
-    %>
-    <div class="box">
-    <!-- box / title -->
-    <div class="title">
+    <div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
 
-    <div class="graph">
-         <div style="padding:0 10px 10px 17px;">
+    <div class="graph panel-body">
+         <div>
          %if c.no_data:
            ${c.no_data_msg}
-           %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="btn btn-mini")}
+           %if h.HasPermissionAny('hg.admin')('enable stats on from summary'):
+                ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name),class_="btn btn-default btn-xs")}
            %endif
         %else:
             ${_('Stats gathered: ')} ${c.stats_percentage}%
         %endif
         </div>
-        <div id="commit_history" style="width:450px;height:300px;float:left"></div>
+        <div id="commit_history" class="pull-left"></div>
 
-        <div id="legend_data" style="float: left;">
+        <div id="legend_data" class="pull-left">
             <div id="legend_container"></div>
             <div id="legend_choices">
-                <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
+                <table class="table" id="legend_choices_tables"></table>
             </div>
         </div>
 
-        <div style="clear: both; height: 10px;"></div>
-        <div id="overview" style="width: 450px; height: 100px; float: left;"></div>
+        <div id="overview"></div>
     </div>
 </div>
 
 <script type="text/javascript">
-var data = ${c.trending_languages|n};
+var data = ${h.js(c.trending_languages)};
 var total = 0;
 var no_data = true;
 var tbl = document.createElement('table');
@@ -86,7 +84,7 @@
     var td2 = document.createElement('td');
     td2.setAttribute('style','padding-right:14px !important');
     var trending_language = document.createElement('div');
-    var nr_files = obj.count+" ${_('files')}";
+    var nr_files = obj.count + ' ' + ${h.jshtml(_('files'))};
 
     trending_language.title = k+" "+nr_files;
 
@@ -110,7 +108,7 @@
         lnk = document.createElement('a');
 
         lnk.href='#';
-        lnk.innerHTML = "${_('Show more')}";
+        lnk.innerHTML = ${h.jshtml(_('Show more'))};
         lnk.id='code_stats_show_more';
         td.appendChild(lnk);
 
@@ -123,6 +121,7 @@
 
 </script>
 <script type="text/javascript">
+
 /**
  * Plots summary graph
  *
@@ -139,16 +138,33 @@
             "to":to
         }
     };
+    for(var key in dataset){
+      var data = dataset[key].data;
+      for(var d in data){
+        data[d].time *= 1000;
+      }
+    }
+    for(var key in overview_dataset){
+      overview_dataset[key][0] *= 1000;
+    }
     var dataset = dataset;
     var overview_dataset = [overview_dataset];
-    var choiceContainer = YUD.get("legend_choices");
-    var choiceContainerTable = YUD.get("legend_choices_tables");
-    var plotContainer = YUD.get('commit_history');
-    var overviewContainer = YUD.get('overview');
+    var choiceContainer = $("#legend_choices")[0];
+    var choiceContainerTable = $("#legend_choices_tables")[0];
+    var $plotContainer = $('#commit_history');
+    var plotContainer = $('#commit_history')[0];
+    var $overviewContainer = $('#overview');
+    var overviewContainer = $('#overview')[0];
 
     var plot_options = {
         bars: {show:true, align: 'center', lineWidth: 4},
-        legend: {show:true, container: "legend_container"},
+        legend: {show:true,
+                container: "#legend_container",
+                labelFormatter: function(label, series) {
+                        // series is the series object for the label
+                        return '<a href="javascript:void(0)"> ' + label + '</a>';
+                    }
+        },
         points: {show:true, radius: 0, fill: false},
         yaxis: {tickDecimals: 0},
         xaxis: {
@@ -192,7 +208,7 @@
     }
 
     /**
-     * generate checkboxes accordindly to data
+     * generate checkboxes accordingly to data
      * @param keys
      * @returns
      */
@@ -206,8 +222,8 @@
             i++;
             if(data[pos].label != ''){
                 choiceContainerTable.innerHTML +=
-                    '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
-                     <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
+                    '<tr style="display:none"><td><label><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
+                     {0}</label></td></tr>'.format(data[pos].label);
             }
         }
     }
@@ -226,13 +242,12 @@
             div.style.backgroundColor='#fee';
             document.body.appendChild(div);
         }
-        YUD.setStyle(div, 'opacity', 0);
+        $(div).css('opacity', 0)
         div.innerHTML = contents;
         div.style.top=(y + 5) + "px";
         div.style.left=(x + 5) + "px";
 
-        var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
-        anim.animate();
+        $(div).animate({opacity: 0.8}, 200);
     }
 
     /**
@@ -251,11 +266,12 @@
             for(var ds in dataset[key].data){
                 commit_data = dataset[key].data[ds];
                 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
-
                     if(new_dataset[key] === undefined){
-                        new_dataset[key] = {data:[],schema:["commits"],label:key};
+                        new_dataset[key] = {data:[],label:key};
                     }
-                    new_dataset[key].data.push(commit_data);
+                    new_dataset[key].data.push([
+                      commit_data.time,
+                      commit_data.commits]);
                 }
             }
             if (new_dataset[key] !== undefined){
@@ -275,12 +291,14 @@
     /**
     * redraw using new checkbox data
     */
-    function plotchoiced(e,args){
+    function plotchoiced(e){
+        args = e.data;
         var cur_data = args[0];
         var cur_ranges = args[1];
 
         var new_data = [];
         var inputs = choiceContainer.getElementsByTagName("input");
+        inputs[$(e.target).parents('tr').index()].click();
 
         //show only checked labels
         for(var i=0; i<inputs.length; i++) {
@@ -299,7 +317,7 @@
             }
         }
 
-        var new_options = YAHOO.lang.merge(plot_options, {
+        var new_options = $.extend(plot_options, {
             xaxis: {
                 min: cur_ranges.xaxis.from,
                 max: cur_ranges.xaxis.to,
@@ -311,12 +329,15 @@
             new_data = [[0,1]];
         }
         // do the zooming
-       plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
+       plot = $.plot(plotContainer, new_data, new_options);
 
-       plot.subscribe("plotselected", plotselected);
+       $plotContainer.on("plotselected", plotselected);
 
        //resubscribe plothover
-       plot.subscribe("plothover", plothover);
+       $plotContainer.on("plothover", plothover);
+
+       //resubscribe this function after plot update
+       $('#legend_container tr a').on("click", [cur_data, cur_ranges], plotchoiced);
 
        // don't fire event on the overview to prevent eternal loop
        overview.setSelection(cur_ranges, true);
@@ -328,12 +349,12 @@
      * @param ranges
      * @returns
      */
-    function plotselected(ranges,cur_data) {
+    function plotselected(e, ranges) {
         //updates the data for new plot
         var data = getDataAccordingToRanges(ranges);
         generateCheckboxes(data);
 
-        var new_options = YAHOO.lang.merge(plot_options, {
+        var new_options = $.extend(plot_options, {
             xaxis: {
                 min: ranges.xaxis.from,
                 max: ranges.xaxis.to,
@@ -342,60 +363,54 @@
             }
         });
         // do the zooming
-        plot = YAHOO.widget.Flot(plotContainer, data, new_options);
+        plot = $.plot(plotContainer, data, new_options);
 
-        plot.subscribe("plotselected", plotselected);
+        $plotContainer.on("plotselected", plotselected);
 
         //resubscribe plothover
-        plot.subscribe("plothover", plothover);
+        $plotContainer.on("plothover", plothover);
 
         // don't fire event on the overview to prevent eternal loop
         overview.setSelection(ranges, true);
 
         //resubscribe choiced
-        YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
+        $('#legend_container tr a').on("click", [data, ranges], plotchoiced);
     }
 
     var previousPoint = null;
 
-    function plothover(o) {
-        var pos = o.pos;
-        var item = o.item;
-
-        //YUD.get("x").innerHTML = pos.x.toFixed(2);
-        //YUD.get("y").innerHTML = pos.y.toFixed(2);
+    function plothover(e, pos, item) {
         if (item) {
             if (previousPoint != item.datapoint) {
                 previousPoint = item.datapoint;
 
-                var tooltip = YUD.get("tooltip");
+                var tooltip = $("#tooltip")[0];
                 if(tooltip) {
                       tooltip.parentNode.removeChild(tooltip);
                 }
-                var x = item.datapoint.x.toFixed(2);
-                var y = item.datapoint.y.toFixed(2);
+
+                var d = new Date(item.datapoint[0]);
+                var fd = d.toDateString();
+                var nr_commits = item.datapoint[1];
 
                 if (!item.series.label){
                     item.series.label = 'commits';
                 }
-                var d = new Date(x*1000);
-                var fd = d.toDateString();
-                var nr_commits = parseInt(y);
 
                 var cur_data = dataset[item.series.label].data[item.dataIndex];
                 var added = cur_data.added;
                 var changed = cur_data.changed;
                 var removed = cur_data.removed;
 
-                var nr_commits_suffix = " ${_('commits')} ";
-                var added_suffix = " ${_('files added')} ";
-                var changed_suffix = " ${_('files changed')} ";
-                var removed_suffix = " ${_('files removed')} ";
+                var nr_commits_suffix = ' ' + ${h.jshtml(_('commits'))} + ' ';
+                var added_suffix = ' ' + ${h.jshtml(_('files added'))} + ' ';
+                var changed_suffix = ' ' + ${h.jshtml(_('files changed'))} + ' ';
+                var removed_suffix = ' ' + ${h.jshtml(_('files removed'))} + ' ';
 
-                if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
-                if(added==1){added_suffix=" ${_('file added')} ";}
-                if(changed==1){changed_suffix=" ${_('file changed')} ";}
-                if(removed==1){removed_suffix=" ${_('file removed')} ";}
+                if(nr_commits == 1){ nr_commits_suffix = ' ' + ${h.jshtml(_('commit'))} + ' '; }
+                if(added == 1) { added_suffix=' ' + ${h.jshtml(_('file added'))} + ' '; }
+                if(changed == 1) { changed_suffix=' ' + ${h.jshtml(_('file changed'))} + ' '; }
+                if(removed == 1) { removed_suffix=' ' + ${h.jshtml(_('file removed'))} + ' '; }
 
                 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
                          +'<br/>'+
@@ -406,12 +421,12 @@
             }
         }
         else {
-              var tooltip = YUD.get("tooltip");
+              var tooltip = $("#tooltip")[0];
 
               if(tooltip) {
                     tooltip.parentNode.removeChild(tooltip);
               }
-            previousPoint = null;
+              previousPoint = null;
         }
     }
 
@@ -423,26 +438,26 @@
     generateCheckboxes(data);
 
     //main plot
-    var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
+    var plot = $.plot(plotContainer,data,plot_options);
 
     //overview
-    var overview = YAHOO.widget.Flot(overviewContainer,
-            overview_dataset, overview_options);
+    var overview = $.plot(overviewContainer, overview_dataset, overview_options);
 
     //show initial selection on overview
     overview.setSelection(initial_ranges);
 
-    plot.subscribe("plotselected", plotselected);
-    plot.subscribe("plothover", plothover);
+    $plotContainer.on("plotselected", plotselected);
+    $plotContainer.on("plothover", plothover);
 
-    overview.subscribe("plotselected", function (ranges) {
+    $overviewContainer.on("plotselected", function (e, ranges) {
         plot.setSelection(ranges);
     });
 
     // user choices on overview
-    YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
+    $('#legend_container tr a').on("click", [data, initial_ranges], plotchoiced);
 }
-    SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
+
+SummaryPlot(${h.js(c.ts_min)}, ${h.js(c.ts_max)}, ${h.js(c.commit_data)}, ${h.js(c.overview_data)});
 </script>
 
 </%def>
--- a/kallithea/templates/summary/summary.html	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/templates/summary/summary.html	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,5 @@
 <%inherit file="/base/base.html"/>
+<%namespace name="changelog_table" file="/changelog/changelog_table.html"/>
 
 <%block name="title">
     ${_('%s Summary') % c.repo_name}
@@ -10,24 +11,20 @@
     ## locking icon
     %if c.db_repo.enable_locking:
      %if c.db_repo.locked[0]:
-       <span class="locking_locked tooltip icon-block" title="${_('Repository locked by %s') % h.person_by_id(c.db_repo.locked[0])}"></span>
+       <span class="locking_locked icon-block" data-toggle="tooltip" title="${_('Repository locked by %s') % h.person_by_id(c.db_repo.locked[0])}"></span>
      %else:
-       <span class="locking_unlocked tooltip icon-ok" title="${_('Repository unlocked')}"></span>
+       <span class="locking_unlocked icon-ok" data-toggle="tooltip" title="${_('Repository unlocked')}"></span>
      %endif
     %endif
 
     ##FORK
     %if c.db_repo.fork:
-    <span>
-        - <i class="icon-fork"></i> ${_('Fork of')} "<a href="${h.url('summary_home',repo_name=c.db_repo.fork.repo_name)}">${c.db_repo.fork.repo_name}</a>"
-    </span>
+        - <i class="icon-fork"></i>${_('Fork of')} "<a href="${h.url('summary_home',repo_name=c.db_repo.fork.repo_name)}">${c.db_repo.fork.repo_name}</a>"
     %endif
 
     ##REMOTE
     %if c.db_repo.clone_uri:
-    <span>
-       - <i class="icon-fork"></i> ${_('Clone from')} "<a href="${h.url(str(h.hide_credentials(c.db_repo.clone_uri)))}">${h.hide_credentials(c.db_repo.clone_uri)}</a>"
-    <span>
+       - <i class="icon-fork"></i>${_('Clone from')} "<a href="${h.url(str(h.hide_credentials(c.db_repo.clone_uri)))}">${h.hide_credentials(c.db_repo.clone_uri)}</a>"
     %endif
 </%def>
 
@@ -36,14 +33,14 @@
 </%block>
 
 <%block name="head_extra">
-  <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
-  <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
+  <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=request.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
+  <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=request.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
 
   <script>
   redirect_hash_branch = function(){
     var branch = window.location.hash.replace(/^#(.*)/, '$1');
     if (branch){
-      window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
+      window.location = ${h.js(h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__'))}
         .replace('__BRANCH__',branch);
     }
   }
@@ -56,196 +53,204 @@
 
 <%def name="main()">
 ${self.repo_context_bar('summary')}
-<%
-summary = lambda n:{False:'summary-short'}.get(n)
-%>
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
+<div class="panel panel-primary">
+    <div class="panel-heading clearfix">
         ${self.breadcrumbs()}
     </div>
-    <!-- end box / title -->
-    <div class="form">
-        <div id="summary" class="fields">
-            <div class="field">
-                <div class="label-summary">
-                  <label>${_('Clone URL')}:</label>
-                </div>
-                <div class="input ${summary(c.show_stats)}">
-                  ${self.repotag(c.db_repo)}
-                  <input style="width:80%" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
-                  <input style="display:none;width:80%" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
-                  <div style="display:none" id="clone_by_name" class="btn btn-small">${_('Show by Name')}</div>
-                  <div id="clone_by_id" class="btn btn-small">${_('Show by ID')}</div>
+    <div id="summary-panel-body" class="form panel-body">
+        <div id="summary" class="pull-left">
+            <div class="form-group form-inline">
+                <label>${_('Clone URL')}:</label>
+                <div id="clone-url">
+                  <div id="clone_by_name" class="input-group">
+                    <span class="input-group-addon">${self.repolabel(c.db_repo)}</span>
+                    <input class="form-control" size="80" readonly="readonly" value="${c.clone_repo_url}"/>
+                    <span class="input-group-addon btn">${_('Show by ID')}</span>
+                  </div>
+                  <div id="clone_by_id" class="input-group" style="display:none">
+                    <span class="input-group-addon">${self.repolabel(c.db_repo)}</span>
+                    <input class="form-control" size="80" readonly="readonly" value="${c.clone_repo_url_id}"/>
+                    <span class="input-group-addon btn">${_('Show by Name')}</span>
+                  </div>
                 </div>
             </div>
 
-            <div class="field">
-              <div class="label-summary">
-                  <label>${_('Description')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.db_repo.description, stylize=c.visual.stylify_metatags)}</div>
+            <div class="form-group">
+              <label>${_('Description')}:</label>
+              <div class="formatted-fixed">${h.urlify_text(c.db_repo.description, stylize=c.visual.stylify_metalabels)}</div>
             </div>
 
-            <div class="field">
-              <div class="label-summary">
-                  <label>${_('Trending files')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)}">
+            <div class="form-group">
+              <label>${_('Trending files')}:</label>
+              <div>
                 %if c.show_stats:
                 <div id="lang_stats"></div>
                 %else:
                    ${_('Statistics are disabled for this repository')}
-                   %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
-                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'),class_="btn btn-mini")}
+                   %if h.HasPermissionAny('hg.admin')('enable stats on from summary'):
+                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'),class_="btn btn-default btn-xs")}
                    %endif
                 %endif
               </div>
             </div>
 
-            <div class="field">
-              <div class="label-summary">
-                  <label>${_('Download')}:</label>
-              </div>
-              <div class="input ${summary(c.show_stats)}">
+            <div class="form-group">
+              <label>${_('Download')}:</label>
+              <div>
                 %if len(c.db_repo_scm_instance.revisions) == 0:
                   ${_('There are no downloads yet')}
                 %elif not c.enable_downloads:
                   ${_('Downloads are disabled for this repository')}
-                    %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
-                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'),class_="btn btn-mini")}
+                    %if h.HasPermissionAny('hg.admin')('enable downloads on from summary'):
+                        ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'),class_="btn btn-default btn-xs")}
                     %endif
                 %else:
                     <span id="${'zip_link'}">
-                        <a class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.db_repo.repo_name,fname='tip.zip')}"><i class="icon-file-zip"></i> ${_('Download as zip')}</a>
+                        <a class="btn btn-default btn-sm" href="${h.url('files_archive_home',repo_name=c.db_repo.repo_name,fname='tip.zip')}"><i class="icon-file-zip"></i>${_('Download as zip')}</a>
                     </span>
                     ${h.hidden('download_options')}
-                    <span style="vertical-align: bottom">
-                      <input id="archive_subrepos" type="checkbox" name="subrepos" />
-                      <label for="archive_subrepos" class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('With subrepos')}</label>
+                    <span>
+                      <label data-toggle="tooltip" title="${_('Check this to download archive with subrepos')}">
+                          <input id="archive_subrepos" type="checkbox" name="subrepos" />
+                          ${_('With subrepos')}
+                      </label>
                     </span>
                 %endif
               </div>
             </div>
         </div>
-        <div id="summary-menu-stats">
-          <ul>
-            <li>
-               <a title="${_('Owner')} ${c.db_repo.user.email}">
-                <i class="icon-user"></i> ${c.db_repo.user.username}
-                  <div class="gravatar" style="float: right; margin: 0px 0px 0px 0px" title="${c.db_repo.user.name} ${c.db_repo.user.lastname}">
-                    ${h.gravatar(c.db_repo.user.email, size=18)}
-                  </div>
+        <ul id="summary-menu-stats" class="list-group pull-right">
+            <li class="list-group-item">
+               <a title="${_('Owner')} ${c.db_repo.owner.email}">
+                <i class="icon-user"></i>${c.db_repo.owner.username}
+                ${h.gravatar_div(c.db_repo.owner.email, size=18, div_class="pull-right")}
               </a>
             </li>
-            <li>
+            <li class="list-group-item">
                <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
-                <i class="icon-heart"></i> ${_('Followers')}
-                <span class="stats-bullet" id="current_followers_count">${c.repository_followers}</span>
+                <i class="icon-heart"></i>${_('Followers')}
+                <span class="badge pull-right" id="current_followers_count">${c.repository_followers}</span>
               </a>
             </li>
-            <li>
+            <li class="list-group-item">
               <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
-                <i class="icon-fork"></i> ${_('Forks')}
-                <span class="stats-bullet">${c.repository_forks}</span>
+                <i class="icon-fork"></i>${_('Forks')}
+                <span class="badge pull-right">${c.repository_forks}</span>
               </a>
             </li>
 
-            %if c.authuser.username != 'default':
-            <li class="repo_size">
-              <a href="#" onclick="javascript:showRepoSize('repo_size_2','${c.db_repo.repo_name}')"><i class="icon-ruler"></i> ${_('Repository Size')}</a>
-              <span  class="stats-bullet" id="repo_size_2"></span>
+            %if request.authuser.username != 'default':
+            <li class="list-group-item clearfix">
+              <a href="#" onclick="javascript:showRepoSize('repo_size_2','${c.db_repo.repo_name}')">
+                <i class="icon-ruler"></i>${_('Size')}
+                <span class="badge pull-right" id="repo_size_2"></span>
+              </a>
             </li>
             %endif
 
-            <li>
-            %if c.authuser.username != 'default':
-              <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
+            <li class="list-group-item">
+            %if request.authuser.username != 'default':
+              <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=request.authuser.api_key)}"><i class="icon-rss-squared"></i>${_('Feed')}</a>
             %else:
-              <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
+              <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name)}"><i class="icon-rss-squared"></i>${_('Feed')}</a>
             %endif
             </li>
 
             %if c.show_stats:
-            <li>
+            <li class="list-group-item">
               <a title="${_('Statistics')}" href="${h.url('repo_stats_home',repo_name=c.repo_name)}">
-                <i class="icon-graph"></i> ${_('Statistics')}
+                <i class="icon-graph"></i>${_('Statistics')}
               </a>
             </li>
             %endif
-          </ul>
-        </div>
+        </ul>
     </div>
 </div>
 
 
-<div class="box">
-    <div class="title">
-        <div class="breadcrumbs">
-        %if c.repo_changesets:
+<div class="panel panel-primary">
+    <div class="panel-heading">
+        <div class="panel-title">
+        %if c.cs_pagination:
             ${h.link_to(_('Latest Changes'),h.url('changelog_home',repo_name=c.repo_name))}
         %else:
             ${_('Quick Start')}
          %endif
         </div>
     </div>
-    <div class="table">
+    <div class="panel-body">
         <div id="shortlog_data">
-            <%include file='../changelog/changelog_summary_data.html'/>
+            %if c.cs_pagination:
+                ${changelog_table.changelog(c.repo_name, c.cs_pagination, c.cs_statuses, c.cs_comments, show_checkbox=False)}
+                ${c.cs_pagination.pager()}
+            %else:
+                %if h.HasRepoPermissionLevel('write')(c.repo_name):
+                <h4>${_('Add or upload files directly via Kallithea')}</h4>
+                <div>
+                  <div id="add_node_id" class="add_node">
+                      <a class="btn btn-default btn-xs" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}">${_('Add New File')}</a>
+                  </div>
+                </div>
+                %endif
+
+                <h4>${_('Push new repository')}</h4>
+                <pre>
+${c.db_repo_scm_instance.alias} clone ${c.clone_repo_url}
+${c.db_repo_scm_instance.alias} add README # add first file
+${c.db_repo_scm_instance.alias} commit -m "Initial" # commit with message
+${c.db_repo_scm_instance.alias} push ${'origin master' if h.is_git(c.db_repo_scm_instance) else ''} # push changes back
+                </pre>
+
+                <h4>${_('Existing repository?')}</h4>
+                <pre>
+                %if h.is_git(c.db_repo_scm_instance):
+git remote add origin ${c.clone_repo_url}
+git push -u origin master
+                %else:
+hg push ${c.clone_repo_url}
+                %endif
+                </pre>
+            %endif
         </div>
     </div>
 </div>
 
 %if c.readme_data:
 <div id="readme" class="anchor">
-<div class="box" style="background-color: #FAFAFA">
-    <div class="title" title="${_('Readme file from revision %s:%s') % (c.db_repo.landing_rev[0], c.db_repo.landing_rev[1])}">
-        <div class="breadcrumbs">
+</div>
+<div class="panel panel-primary">
+    <div class="panel-heading" title="${_('Readme file from revision %s:%s') % (c.db_repo.landing_rev[0], c.db_repo.landing_rev[1])}">
+        <div class="panel-title">
             <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
         </div>
     </div>
-    <div class="readme">
-      <div class="readme_box">
+    <div class="readme panel-body">
         ${c.readme_data|n}
-      </div>
     </div>
 </div>
-</div>
 %endif
 
 <script type="text/javascript">
 $(document).ready(function(){
-    var $clone_url = $('#clone_url');
-    var $clone_url_id = $('#clone_url_id');
-    var $clone_by_name = $('#clone_by_name');
-    var $clone_by_id = $('#clone_by_id');
-    $clone_url.click(function(e){
-        if($clone_url.hasClass('selected')){
+    $('#clone-url input').click(function(e){
+        if($(this).hasClass('selected')){
+            $(this).removeClass('selected');
             return ;
         }else{
-            $clone_url.addClass('selected');
-            $clone_url.select();
+            $(this).addClass('selected');
+            $(this).select();
         }
     });
 
-    $clone_by_name.click(function(e){
-        // show url by name and hide name button
-        $clone_url.show();
+    var $clone_by_name = $('#clone_by_name');
+    var $clone_by_id = $('#clone_by_id');
+    $clone_by_name.find('.btn').click(function(e){
         $clone_by_name.hide();
-
-        // hide url by id and show name button
         $clone_by_id.show();
-        $clone_url_id.hide();
     });
-
-    $clone_by_id.click(function(e){
-        // show url by id and hide id button
+    $clone_by_id.find('.btn').click(function(e){
         $clone_by_id.hide();
-        $clone_url_id.show();
-
-        // hide url by name and show id button
         $clone_by_name.show();
-        $clone_url.hide();
     });
 
     var cache = {}
@@ -271,7 +276,7 @@
             query.callback(data);
           }else{
               $.ajax({
-                url: pyroutes.url('repo_refs_data', {'repo_name': '${c.repo_name}'}),
+                url: pyroutes.url('repo_refs_data', {'repo_name': ${h.js(c.repo_name)}}),
                 data: {},
                 dataType: 'json',
                 type: 'GET',
@@ -290,10 +295,10 @@
        for(k in tmpl_links){
            var s = $('#'+k+'_link');
            if(s){
-             var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
+             var title_tmpl = ${h.jshtml(_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__'))};
              title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
              title_tmpl = title_tmpl.replace('__CS_EXT__',k);
-             title_tmpl = '<i class="icon-file-zip"></i> '+ title_tmpl.html_escape();
+             title_tmpl = '<i class="icon-file-zip"></i>'+ title_tmpl.html_escape();
              var url = tmpl_links[k].replace('__CS__',new_cs.id);
              var subrepos = $('#archive_subrepos').is(':checked');
              url = url.replace('__SUB__',subrepos);
@@ -306,7 +311,7 @@
 
     var tmpl_links = {};
     %for cnt,archive in enumerate(c.db_repo_scm_instance._get_archives()):
-      tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-small')}';
+      tmpl_links[${h.jshtml(archive['type'])}] = ${h.js(h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-default btn-sm'))};
     %endfor
 });
 </script>
@@ -314,11 +319,11 @@
 %if c.show_stats:
 <script type="text/javascript">
 $(document).ready(function(){
-    var data = ${c.trending_languages|n};
+    var data = ${h.js(c.trending_languages)};
     var total = 0;
     var no_data = true;
     var tbl = document.createElement('table');
-    tbl.setAttribute('class','trending_language_tbl');
+    tbl.setAttribute('class','table');
     var cnt = 0;
     for (var i=0;i<data.length;i++){
         total+= data[i][1].count;
@@ -338,7 +343,7 @@
         var percentage = Math.round((obj.count/total*100),2);
 
         var td1 = document.createElement('td');
-        td1.width = 150;
+        td1.width = 250;
         var trending_language_label = document.createElement('div');
         trending_language_label.innerHTML = obj.desc+" ("+k+")";
         td1.appendChild(trending_language_label);
@@ -346,19 +351,25 @@
         var td2 = document.createElement('td');
         td2.setAttribute('style','padding-right:14px !important');
         var trending_language = document.createElement('div');
-        var nr_files = obj.count+" ${_('files')}";
+        var nr_files = obj.count + ' ' + ${h.jshtml(_('files'))};
 
         trending_language.title = k+" "+nr_files;
 
         if (percentage>22){
-            trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
+            trending_language.innerHTML = "<b class='progress-bar' role='progressbar'"
+                + "aria-valuemin='0' aria-valuemax='100' aria-valuenow='" + percentage
+                + "' style='width: " + percentage + "%;'>" + percentage + "%, " + nr_files + "</b>";
         }
-        else{
-            trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
+        else if (percentage>5){
+            trending_language.innerHTML = "<b class='progress-bar' role='progressbar'"
+                + "aria-valuemin='0' aria-valuemax='100' aria-valuenow='" + percentage
+                + "' style='width: " + percentage + "%;'>" + percentage + "%</b>";
+        }else{
+            trending_language.innerHTML = "<b class='progress-bar' role='progressbar'"
+                + "aria-valuemin='0' aria-valuemax='100' aria-valuenow='" + percentage
+                + "' style='width: " + percentage + "%;'>&nbsp;</b>&nbsp;" + percentage + "%";
         }
 
-        trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
-        trending_language.style.width=percentage+"%";
         td2.appendChild(trending_language);
 
         tr.appendChild(td1);
@@ -370,7 +381,7 @@
             lnk = document.createElement('a');
 
             lnk.href='#';
-            lnk.innerHTML = "${_('Show more')}";
+            lnk.innerHTML = ${h.jshtml(_('Show more'))};
             lnk.id='code_stats_show_more';
             td.appendChild(lnk);
 
@@ -381,7 +392,7 @@
 
     }
     if (data.length == 0) {
-        tbl.innerHTML = "<tr><td>${_('No data ready yet')}</td></tr>";
+        tbl.innerHTML = '<tr><td>' + ${h.jshtml(_('No data ready yet'))} + '</td></tr>';
     }
 
     $('#lang_stats').append(tbl);
@@ -393,15 +404,4 @@
 </script>
 %endif
 
-## Shortlog paging
-<script type="text/javascript">
-  $(document).ready(function(){
-    var $shortlog_data = $('#shortlog_data');
-    $shortlog_data.on('click','.pager_link',function(e){
-      asynchtml(e.target.href, $shortlog_data, function(){tooltip_activate();});
-      e.preventDefault();
-    });
-  });
-</script>
-
 </%def>
--- a/kallithea/templates/switch_to_list.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-## -*- coding: utf-8 -*-
-<li>
-    <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs"><i class="icon-fork"></i> ${'%s (%s)' % (_('Branches'),len(c.db_repo_scm_instance.branches.values()))}</a>
-    <ul>
-    %if c.db_repo_scm_instance.branches.values():
-        %for cnt,branch in enumerate(c.db_repo_scm_instance.branches.items()):
-            <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=(branch[0] if '/' not in branch[0] else branch[1]), at=branch[0]))}</pre></div></li>
-        %endfor
-    %else:
-        <li>${h.link_to(_('There are no branches yet'),'#')}</li>
-    %endif
-    </ul>
-</li>
-%if c.db_repo_scm_instance.closed_branches.values():
-<li>
-    <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs"><i class="icon-fork"></i> ${'%s (%s)' % (_('Closed Branches'),len(c.db_repo_scm_instance.closed_branches.values()))}</a>
-    <ul>
-        %for cnt,branch in enumerate(c.db_repo_scm_instance.closed_branches.items()):
-            <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=(branch[0] if '/' not in branch[0] else branch[1]), at=branch[0]))}</pre></div></li>
-        %endfor
-    </ul>
-</li>
-%endif
-<li>
-    <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs"><i class="icon-tag"></i> ${'%s (%s)' % (_('Tags'),len(c.db_repo_scm_instance.tags.values()))}</a>
-    <ul>
-    %if c.db_repo_scm_instance.tags.values():
-        %for cnt,tag in enumerate(c.db_repo_scm_instance.tags.items()):
-         <li><div><pre>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=(tag[0] if '/' not in tag[0] else tag[1]), at=tag[0]))}</pre></div></li>
-        %endfor
-    %else:
-        <li>${h.link_to(_('There are no tags yet'),'#')}</li>
-    %endif
-    </ul>
-</li>
-%if c.db_repo_scm_instance.alias == 'hg':
-<li>
-    <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs"><i class="icon-bookmark"></i> ${'%s (%s)' % (_('Bookmarks'),len(c.db_repo_scm_instance.bookmarks.values()))}</a>
-    <ul>
-    %if c.db_repo_scm_instance.bookmarks.values():
-        %for cnt,book in enumerate(c.db_repo_scm_instance.bookmarks.items()):
-         <li><div><pre>${h.link_to('%s - %s' % (book[0],h.short_id(book[1])),h.url('files_home',repo_name=c.repo_name,revision=(book[0] if '/' not in book[0] else book[1]), at=book[0]))}</pre></div></li>
-        %endfor
-    %else:
-        <li>${h.link_to(_('There are no bookmarks yet'),'#')}</li>
-    %endif
-    </ul>
-</li>
-%endif
--- a/kallithea/templates/tags/tags.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/base/base.html"/>
-
-<%block name="title">
-    ${_('%s Tags') % c.repo_name}
-</%block>
-
-<%def name="breadcrumbs_links()">
-    <input class="q_filter_box" id="q_filter_tags" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
-    ${_('Tags')}
-</%def>
-
-<%block name="header_menu">
-    ${self.menu('repositories')}
-</%block>
-
-<%def name="main()">
-${self.repo_context_bar('switch-to')}
-<div class="box">
-    <!-- box / title -->
-    <div class="title">
-        ${self.breadcrumbs()}
-    </div>
-    <!-- end box / title -->
-    %if c.repo_tags:
-    <div class="info_box" id="compare_tags" style="clear: both;padding: 10px 19px;text-align: right;"><a href="#" class="btn btn-small">${_('Compare Tags')}</a></div>
-    %endif
-    <div class="table">
-        <%include file='tags_data.html'/>
-    </div>
-</div>
-
-<script type="text/javascript">
-$('#compare_tags').click(function(e){
-    e.preventDefault();
-    var org = $('input[name=compare_org]:checked')[0];
-    var other = $('input[name=compare_other]:checked')[0];
-
-    if(org && other){
-        var compare_url = "${h.url('compare_url',repo_name=c.repo_name,org_ref_type='tag',org_ref_name='__ORG__',other_ref_type='tag',other_ref_name='__OTHER__')}";
-        var u = compare_url.replace('__ORG__',org.value)
-                           .replace('__OTHER__',other.value);
-        window.location=u;
-    }
-});
-
-// main table sorting
-var myColumnDefs = [
-    {key:"name",label:"${_('Name')}",sortable:true,
-        sortOptions: { sortFunction: nameSort }},
-    {key:"date",label:"${_('Date')}",sortable:true,
-        sortOptions: { sortFunction: dateSort }},
-    {key:"author",label:"${_('Author')}",sortable:true},
-    {key:"revision",label:"${_('Revision')}",sortable:true,
-        sortOptions: { sortFunction: revisionSort }},
-    {key:"compare",label:"${_('Compare')}",sortable:false}
-];
-
-var myDataSource = new YAHOO.util.DataSource(YUD.get("tags_data"));
-
-myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
-
-myDataSource.responseSchema = {
-    fields: [
-        {key:"raw_name"},
-        {key:"name"},
-        {key:"raw_date"},
-        {key:"date"},
-        {key:"author"},
-        {key:"last_rev_raw"},
-        {key:"revision"},
-        {key:"compare"}
-    ]
-};
-
-var myDataTable = new YAHOO.widget.DataTable("table_wrap", myColumnDefs, myDataSource,
-    {
-         sortedBy:{key:"name",dir:"asc"},
-         MSG_SORTASC:"${_('Click to sort ascending')}",
-         MSG_SORTDESC:"${_('Click to sort descending')}",
-         MSG_EMPTY:"${_('No records found.')}",
-         MSG_ERROR:"${_('Data error.')}",
-         MSG_LOADING:"${_('Loading...')}"
-    }
-);
-myDataTable.subscribe('postRenderEvent',function(oArgs) {
-    tooltip_activate();
-    var func = function(node){
-        return node.parentNode.parentNode.parentNode.parentNode.parentNode;
-    }
-    q_filter('q_filter_tags',$('div.table tr td .logtags .tagtag a'),func);
-});
-
-</script>
-
-</%def>
--- a/kallithea/templates/tags/tags_data.html	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-%if c.repo_tags:
-   <div id="table_wrap" class="yui-skin-sam">
-    <table id="tags_data">
-      <thead>
-        <tr>
-            <th class="left">Raw name</th> ##notranslation
-            <th class="left">${_('Name')}</th>
-            <th class="left">Raw date</th> ##notranslation
-            <th class="left">${_('Date')}</th>
-            <th class="left">${_('Author')}</th>
-            <th class="left">Raw rev</th> ##notranslation
-            <th class="left">${_('Revision')}</th>
-            <th class="left">${_('Compare')}</th>
-        </tr>
-      </thead>
-        %for cnt,tag in enumerate(c.repo_tags.items()):
-        <tr class="parity${cnt%2}">
-            <td>${tag[0]}</td>
-            <td>
-                <span class="logtags">
-                    <span class="tagtag">${h.link_to(tag[0],
-                    h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id))}
-                    </span>
-                </span>
-            </td>
-            <td>${tag[1]._timestamp}</td>
-            <td><span class="tooltip" title="${h.age(tag[1].date)}">${h.fmt_date(tag[1].date)}</span></td>
-            <td title="${tag[1].author}">${h.person(tag[1].author)}</td>
-            <td>${tag[1].revision}</td>
-            <td>
-                <div>
-                    <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=tag[1].raw_id)}" class="revision-link">${h.show_id(tag[1])}</a>
-                </div>
-            </td>
-            <td>
-                <input class="branch-compare" type="radio" name="compare_org" value="${tag[0]}"/>
-                <input class="branch-compare" type="radio" name="compare_other" value="${tag[0]}"/>
-            </td>
-        </tr>
-        %endfor
-    </table>
-   </div>
-%else:
-    ${_('There are no tags yet')}
-%endif
--- a/kallithea/tests/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -13,230 +13,16 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-Pylons application test package
-
-This package assumes the Pylons environment is already loaded, such as
-when this script is imported from the `nosetests --with-pylons=test.ini`
-command.
-
-This module initializes the application via ``websetup`` (`paster
-setup-app`) and provides the base testing objects.
-
-nosetests -x - fail on first error
-nosetests kallithea.tests.functional.test_admin_settings:TestSettingsController.test_my_account
-nosetests --pdb --pdb-failures
-nosetests --with-coverage --cover-package=kallithea.model.validators kallithea.tests.test_validators
-
-optional FLAGS:
-    KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
-    KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for test_vcs_operations
-
-"""
-import os
-import re
-import time
-import logging
-import datetime
-import hashlib
-import tempfile
-from os.path import join as jn
-
-from tempfile import _RandomNameSequence
-
-import pylons
-import pylons.test
-from pylons import config, url
-from pylons.i18n.translation import _get_translator
-from pylons.util import ContextObj
-
-from routes.util import URLGenerator
-from webtest import TestApp
-from nose.plugins.skip import SkipTest
-
-from kallithea.lib.compat import unittest
-from kallithea import is_windows
-from kallithea.model.db import Notification, User, UserNotification
-from kallithea.model.meta import Session
-from kallithea.tests.parameterized import parameterized
-from kallithea.lib.utils2 import safe_str
-
-
-os.environ['TZ'] = 'UTC'
-if not is_windows:
-    time.tzset()
-
-log = logging.getLogger(__name__)
+Kallithea test package
 
-__all__ = [
-    'parameterized', 'environ', 'url', 'TestController',
-    'SkipTest', 'ldap_lib_installed', 'pam_lib_installed', 'BaseTestCase', 'init_stack',
-    'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
-    'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
-    'TEST_USER_ADMIN_EMAIL', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
-    'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
-    'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
-    'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
-    'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
-    'GIT_REMOTE_REPO', 'SCM_TESTS',
-]
-
-# Invoke websetup with the current config file
-# SetupCommand('setup-app').run([config_file])
-
-environ = {}
-
-#SOME GLOBALS FOR TESTS
-
-TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
-TEST_USER_ADMIN_LOGIN = 'test_admin'
-TEST_USER_ADMIN_PASS = 'test12'
-TEST_USER_ADMIN_EMAIL = 'test_admin@example.com'
-
-TEST_USER_REGULAR_LOGIN = 'test_regular'
-TEST_USER_REGULAR_PASS = 'test12'
-TEST_USER_REGULAR_EMAIL = 'test_regular@example.com'
+Refer to docs/contributing.rst for details on running the test suite.
+"""
 
-TEST_USER_REGULAR2_LOGIN = 'test_regular2'
-TEST_USER_REGULAR2_PASS = 'test12'
-TEST_USER_REGULAR2_EMAIL = 'test_regular2@example.com'
-
-HG_REPO = 'vcs_test_hg'
-GIT_REPO = 'vcs_test_git'
-
-NEW_HG_REPO = 'vcs_test_hg_new'
-NEW_GIT_REPO = 'vcs_test_git_new'
-
-HG_FORK = 'vcs_test_hg_fork'
-GIT_FORK = 'vcs_test_git_fork'
-
-## VCS
-SCM_TESTS = ['hg', 'git']
-uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
-
-GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
-
-TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
-TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)
-TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)
-
-
-HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
-
-TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO)
-TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix)
-TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix)
+import pytest
 
-TEST_DIR = tempfile.gettempdir()
-TEST_REPO_PREFIX = 'vcs-test'
-
-# cached repos if any !
-# comment out to get some other repos from bb or github
-GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO)
-HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO)
-
-#skip ldap tests if LDAP lib is not installed
-ldap_lib_installed = False
-try:
-    import ldap
-    ldap.API_VERSION
-    ldap_lib_installed = True
-except ImportError:
-    # means that python-ldap is not installed
-    pass
-
-try:
-    import pam
-    pam.PAM_TEXT_INFO
-    pam_lib_installed = True
-except ImportError:
-    pam_lib_installed = False
-
-import logging
-
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-
-def init_stack(config=None):
-    if not config:
-        config = pylons.test.pylonsapp.config
-    url._push_object(URLGenerator(config['routes.map'], environ))
-    pylons.app_globals._push_object(config['pylons.app_globals'])
-    pylons.config._push_object(config)
-    pylons.tmpl_context._push_object(ContextObj())
-    # Initialize a translator for tests that utilize i18n
-    translator = _get_translator(pylons.config.get('lang'))
-    pylons.translator._push_object(translator)
-    h = NullHandler()
-    logging.getLogger("kallithea").addHandler(h)
-
-
-class BaseTestCase(unittest.TestCase):
-    def __init__(self, *args, **kwargs):
-        self.wsgiapp = pylons.test.pylonsapp
-        init_stack(self.wsgiapp.config)
-        unittest.TestCase.__init__(self, *args, **kwargs)
-
-    def remove_all_notifications(self):
-        Notification.query().delete()
-
-        # Because query().delete() does not (by default) trigger cascades.
-        # http://docs.sqlalchemy.org/en/rel_0_7/orm/collections.html#passive-deletes
-        UserNotification.query().delete()
-
-        Session().commit()
-
-
-class TestController(BaseTestCase):
-
-    def __init__(self, *args, **kwargs):
-        BaseTestCase.__init__(self, *args, **kwargs)
-        self.app = TestApp(self.wsgiapp)
-        self.maxDiff = None
-        self.index_location = config['app_conf']['index_dir']
-
-    def log_user(self, username=TEST_USER_ADMIN_LOGIN,
-                 password=TEST_USER_ADMIN_PASS):
-        self._logged_username = username
-        response = self.app.post(url(controller='login', action='index'),
-                                 {'username': username,
-                                  'password': password})
-
-        if 'Invalid username or password' in response.body:
-            self.fail('could not login using %s %s' % (username, password))
-
-        self.assertEqual(response.status, '302 Found')
-        self.assert_authenticated_user(response, username)
-
-        response = response.follow()
-        return response.session['authuser']
-
-    def _get_logged_user(self):
-        return User.get_by_username(self._logged_username)
-
-    def assert_authenticated_user(self, response, expected_username):
-        cookie = response.session.get('authuser')
-        user = cookie and cookie.get('user_id')
-        user = user and User.get(user)
-        user = user and user.username
-        self.assertEqual(user, expected_username)
-        self.assertEqual(cookie.get('is_authenticated'), True)
-
-    def authentication_token(self):
-        return self.app.get(url('authentication_token')).body
-
-    def checkSessionFlash(self, response, msg=None, skip=0, _matcher=lambda msg, m: msg in m):
-        if 'flash' not in response.session:
-            self.fail(safe_str(u'msg `%s` not found - session has no flash:\n%s' % (msg, response)))
-        try:
-            level, m = response.session['flash'][-1 - skip]
-            if _matcher(msg, m):
-                return
-        except IndexError:
-            pass
-        self.fail(safe_str(u'msg `%s` not found in session flash (skipping %s): %s' %
-                           (msg, skip,
-                            ', '.join('`%s`' % m for level, m in response.session['flash']))))
-
-    def checkSessionFlashRegex(self, response, regex, skip=0):
-        self.checkSessionFlash(response, regex, skip=skip, _matcher=re.search)
+if getattr(pytest, 'register_assert_rewrite', None):
+    # make sure that all asserts under kallithea/tests benefit from advanced
+    # assert reporting with pytest-3.0.0+, including api/api_base.py,
+    # models/common.py etc.
+    # See also: https://docs.pytest.org/en/latest/assert.html#advanced-assertion-introspection
+    pytest.register_assert_rewrite('kallithea.tests')
--- a/kallithea/tests/api/api_base.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/api/api_base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -13,16 +13,17 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-tests for api. run with::
-
-    KALLITHEA_WHOOSH_TEST_DISABLE=1 nosetests --with-coverage --cover-package=kallithea.controllers.api.api -x kallithea/tests/api
+Tests for the JSON-RPC web api.
 """
 
 import os
 import random
 import mock
+import re
 
-from kallithea.tests import *
+import pytest
+
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.lib.compat import json
 from kallithea.lib.auth import AuthUser
@@ -33,13 +34,14 @@
 from kallithea.model.meta import Session
 from kallithea.model.scm import ScmModel
 from kallithea.model.gist import GistModel
-from kallithea.model.db import Repository, User, Setting, RepoGroup
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.db import Repository, User, Setting, Ui, PullRequest, ChangesetStatus, RepoGroup
 from kallithea.lib.utils2 import time_to_datetime
 
 
 API_URL = '/_admin/api'
-TEST_USER_GROUP = 'test_user_group'
-TEST_REPO_GROUP = 'test_repo_group'
+TEST_USER_GROUP = u'test_user_group'
+TEST_REPO_GROUP = u'test_repo_group'
 
 fixture = Fixture()
 
@@ -99,8 +101,8 @@
             username='test-api',
             password='test',
             email='test@example.com',
-            firstname='first',
-            lastname='last'
+            firstname=u'first',
+            lastname=u'last'
         )
         Session().commit()
         cls.TEST_USER_LOGIN = cls.test_user.username
@@ -110,12 +112,11 @@
     def teardown_class(cls):
         pass
 
-    def setUp(self):
-        self.maxDiff = None
+    def setup_method(self, method):
         make_user_group()
         make_repo_group()
 
-    def tearDown(self):
+    def teardown_method(self, method):
         fixture.destroy_user_group(TEST_USER_GROUP)
         fixture.destroy_gists()
         fixture.destroy_repo_group(TEST_REPO_GROUP)
@@ -127,7 +128,7 @@
             'result': expected
         })
         given = json.loads(given)
-        self.assertEqual(expected, given)
+        assert expected == given, (expected, given)
 
     def _compare_error(self, id_, expected, given):
         expected = jsonify({
@@ -136,30 +137,30 @@
             'result': None
         })
         given = json.loads(given)
-        self.assertEqual(expected, given)
+        assert expected == given, (expected, given)
 
     def test_Optional_object(self):
         from kallithea.controllers.api.api import Optional
 
         option1 = Optional(None)
-        self.assertEqual('<Optional:%s>' % None, repr(option1))
-        self.assertEqual(option1(), None)
+        assert '<Optional:%s>' % None == repr(option1)
+        assert option1() is None
 
-        self.assertEqual(1, Optional.extract(Optional(1)))
-        self.assertEqual('trololo', Optional.extract('trololo'))
+        assert 1 == Optional.extract(Optional(1))
+        assert 'trololo' == Optional.extract('trololo')
 
     def test_Optional_OAttr(self):
         from kallithea.controllers.api.api import Optional, OAttr
 
         option1 = Optional(OAttr('apiuser'))
-        self.assertEqual('apiuser', Optional.extract(option1))
+        assert 'apiuser' == Optional.extract(option1)
 
     def test_OAttr_object(self):
         from kallithea.controllers.api.api import OAttr
 
         oattr1 = OAttr('apiuser')
-        self.assertEqual('<OptionalAttr:apiuser>', repr(oattr1))
-        self.assertEqual(oattr1(), oattr1)
+        assert '<OptionalAttr:apiuser>' == repr(oattr1)
+        assert oattr1() == oattr1
 
     def test_api_wrong_key(self):
         id_, params = _build_data('trololo', 'get_user')
@@ -195,13 +196,13 @@
         id_, params = _build_data(self.apikey, 'get_users', )
         params = params.replace('"args": {}', '"args": null')
         response = api_call(self, params)
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
 
     def test_api_args_is_bad(self):
         id_, params = _build_data(self.apikey, 'get_users', )
         params = params.replace('"args": {}', '"args": 1')
         response = api_call(self, params)
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
 
     def test_api_args_different_args(self):
         import string
@@ -212,14 +213,14 @@
         }
         id_, params = _build_data(self.apikey, 'test', args=expected)
         response = api_call(self, params)
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         self._compare_ok(id_, expected, response.body)
 
     def test_api_get_users(self):
         id_, params = _build_data(self.apikey, 'get_users', )
         response = api_call(self, params)
         ret_all = []
-        _users = User.query().filter(User.username != User.DEFAULT_USER) \
+        _users = User.query().filter_by(is_default_user=False) \
             .order_by(User.username).all()
         for usr in _users:
             ret = usr.get_api_data()
@@ -277,16 +278,15 @@
         expected = 'userid is not the same as your user'
         self._compare_error(id_, expected, given=response.body)
 
-    def test_api_pull(self):
+    def test_api_pull_remote(self):
         # Note: pulling from local repos is a mis-feature - it will bypass access control
         # ... but ok, if the path already has been set in the database
-        repo_name = 'test_pull'
+        repo_name = u'test_pull'
         r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         # hack around that clone_uri can't be set to to a local path
         # (as shown by test_api_create_repo_clone_uri_local)
-        r.clone_uri = os.path.join(TESTS_TMP_PATH, self.REPO)
-        Session.add(r)
-        Session.commit()
+        r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
+        Session().commit()
 
         id_, params = _build_data(self.apikey, 'pull',
                                   repoid=repo_name,)
@@ -298,7 +298,21 @@
 
         fixture.destroy_repo(repo_name)
 
-    def test_api_pull_error(self):
+    def test_api_pull_fork(self):
+        fork_name = u'fork'
+        fixture.create_fork(self.REPO, fork_name)
+        id_, params = _build_data(self.apikey, 'pull',
+                                  repoid=fork_name,)
+        response = api_call(self, params)
+
+        expected = {'msg': 'Pulled from `%s`' % fork_name,
+                    'repository': fork_name}
+        self._compare_ok(id_, expected, given=response.body)
+
+        fixture.destroy_repo(fork_name)
+
+    def test_api_pull_error_no_remote_no_fork(self):
+        # should fail because no clone_uri is set
         id_, params = _build_data(self.apikey, 'pull',
                                   repoid=self.REPO, )
         response = api_call(self, params)
@@ -306,6 +320,23 @@
         expected = 'Unable to pull changes from `%s`' % self.REPO
         self._compare_error(id_, expected, given=response.body)
 
+    def test_api_pull_custom_remote(self):
+        repo_name = u'test_pull_custom_remote'
+        fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
+
+        custom_remote_path = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
+
+        id_, params = _build_data(self.apikey, 'pull',
+                                  repoid=repo_name,
+                                  clone_uri=custom_remote_path)
+        response = api_call(self, params)
+
+        expected = {'msg': 'Pulled from `%s`' % repo_name,
+                    'repository': repo_name}
+        self._compare_ok(id_, expected, given=response.body)
+
+        fixture.destroy_repo(repo_name)
+
     def test_api_rescan_repos(self):
         id_, params = _build_data(self.apikey, 'rescan_repos')
         response = api_call(self, params)
@@ -355,24 +386,28 @@
         expected = "repository `%s` does not exist" % (self.REPO,)
         self._compare_error(id_, expected, given=response.body)
 
-    def test_api_lock_repo_lock_aquire(self):
-        id_, params = _build_data(self.apikey, 'lock',
-                                  userid=TEST_USER_ADMIN_LOGIN,
-                                  repoid=self.REPO,
-                                  locked=True)
-        response = api_call(self, params)
-        expected = {
-            'repo': self.REPO, 'locked': True,
-            'locked_since': response.json['result']['locked_since'],
-            'locked_by': TEST_USER_ADMIN_LOGIN,
-            'lock_state_changed': True,
-            'msg': ('User `%s` set lock state for repo `%s` to `%s`'
-                    % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
-        }
-        self._compare_ok(id_, expected, given=response.body)
+    def test_api_lock_repo_lock_acquire(self):
+        try:
+            id_, params = _build_data(self.apikey, 'lock',
+                                      userid=TEST_USER_ADMIN_LOGIN,
+                                      repoid=self.REPO,
+                                      locked=True)
+            response = api_call(self, params)
+            expected = {
+                'repo': self.REPO, 'locked': True,
+                'locked_since': response.json['result']['locked_since'],
+                'locked_by': TEST_USER_ADMIN_LOGIN,
+                'lock_state_changed': True,
+                'msg': ('User `%s` set lock state for repo `%s` to `%s`'
+                        % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
+            }
+            self._compare_ok(id_, expected, given=response.body)
+        finally:
+            # cleanup
+            Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
 
-    def test_api_lock_repo_lock_aquire_by_non_admin(self):
-        repo_name = 'api_delete_me'
+    def test_api_lock_repo_lock_acquire_by_non_admin(self):
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                             cur_user=self.TEST_USER_LOGIN)
         try:
@@ -393,8 +428,8 @@
         finally:
             fixture.destroy_repo(repo_name)
 
-    def test_api_lock_repo_lock_aquire_non_admin_with_userid(self):
-        repo_name = 'api_delete_me'
+    def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                             cur_user=self.TEST_USER_LOGIN)
         try:
@@ -408,7 +443,7 @@
         finally:
             fixture.destroy_repo(repo_name)
 
-    def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self):
+    def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
         id_, params = _build_data(self.apikey_regular, 'lock',
                                   repoid=self.REPO,
                                   locked=True)
@@ -433,61 +468,58 @@
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    def test_api_lock_repo_lock_aquire_optional_userid(self):
-        id_, params = _build_data(self.apikey, 'lock',
-                                  repoid=self.REPO,
-                                  locked=True)
-        response = api_call(self, params)
-        time_ = response.json['result']['locked_since']
-        expected = {
-            'repo': self.REPO,
-            'locked': True,
-            'locked_since': time_,
-            'locked_by': TEST_USER_ADMIN_LOGIN,
-            'lock_state_changed': True,
-            'msg': ('User `%s` set lock state for repo `%s` to `%s`'
-                    % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
-        }
-
-        self._compare_ok(id_, expected, given=response.body)
-
-    def test_api_lock_repo_lock_optional_locked(self):
+        # test_api_lock_repo_lock_optional_not_locked(self):
         id_, params = _build_data(self.apikey, 'lock',
                                   repoid=self.REPO)
         response = api_call(self, params)
-        time_ = response.json['result']['locked_since']
         expected = {
             'repo': self.REPO,
-            'locked': True,
-            'locked_since': time_,
-            'locked_by': TEST_USER_ADMIN_LOGIN,
+            'locked': False,
+            'locked_since': None,
+            'locked_by': None,
             'lock_state_changed': False,
-            'msg': ('Repo `%s` locked by `%s` on `%s`.'
-                    % (self.REPO, TEST_USER_ADMIN_LOGIN,
-                       json.dumps(time_to_datetime(time_))))
+            'msg': ('Repo `%s` not locked.' % (self.REPO,))
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    def test_api_lock_repo_lock_optional_not_locked(self):
-        repo_name = 'api_not_locked'
-        repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
-                            cur_user=self.TEST_USER_LOGIN)
-        self.assertEqual(repo.locked, [None, None])
+    def test_api_lock_repo_lock_acquire_optional_userid(self):
         try:
             id_, params = _build_data(self.apikey, 'lock',
-                                      repoid=repo.repo_id)
+                                      repoid=self.REPO,
+                                      locked=True)
             response = api_call(self, params)
+            time_ = response.json['result']['locked_since']
             expected = {
-                'repo': repo_name,
-                'locked': False,
-                'locked_since': None,
-                'locked_by': None,
+                'repo': self.REPO,
+                'locked': True,
+                'locked_since': time_,
+                'locked_by': TEST_USER_ADMIN_LOGIN,
+                'lock_state_changed': True,
+                'msg': ('User `%s` set lock state for repo `%s` to `%s`'
+                        % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
+            }
+
+            self._compare_ok(id_, expected, given=response.body)
+
+            # test_api_lock_repo_lock_optional_locked
+            id_, params = _build_data(self.apikey, 'lock',
+                                      repoid=self.REPO)
+            response = api_call(self, params)
+            time_ = response.json['result']['locked_since']
+            expected = {
+                'repo': self.REPO,
+                'locked': True,
+                'locked_since': time_,
+                'locked_by': TEST_USER_ADMIN_LOGIN,
                 'lock_state_changed': False,
-                'msg': ('Repo `%s` not locked.' % (repo_name,))
+                'msg': ('Repo `%s` locked by `%s` on `%s`.'
+                        % (self.REPO, TEST_USER_ADMIN_LOGIN,
+                           json.dumps(time_to_datetime(time_))))
             }
             self._compare_ok(id_, expected, given=response.body)
         finally:
-            fixture.destroy_repo(repo_name)
+            # cleanup
+            Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
 
     @mock.patch.object(Repository, 'lock', crash)
     def test_api_lock_error(self):
@@ -520,7 +552,7 @@
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_locks_with_one_locked_repo(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                                    cur_user=self.TEST_USER_LOGIN)
         Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
@@ -533,7 +565,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                                    cur_user=self.TEST_USER_LOGIN)
         Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
@@ -686,18 +718,19 @@
         expected = ret
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([('firstname', 'new_username'),
-                           ('lastname', 'new_username'),
-                           ('email', 'new_username'),
-                           ('admin', True),
-                           ('admin', False),
-                           ('extern_type', 'ldap'),
-                           ('extern_type', None),
-                           ('extern_name', 'test'),
-                           ('extern_name', None),
-                           ('active', False),
-                           ('active', True),
-                           ('password', 'newpass')
+    @parametrize('name,expected', [
+        ('firstname', 'new_username'),
+        ('lastname', 'new_username'),
+        ('email', 'new_username'),
+        ('admin', True),
+        ('admin', False),
+        ('extern_type', 'ldap'),
+        ('extern_type', None),
+        ('extern_name', 'test'),
+        ('extern_name', None),
+        ('active', False),
+        ('active', True),
+        ('password', 'newpass'),
     ])
     def test_api_update_user(self, name, expected):
         usr = User.get_by_username(self.TEST_USER_LOGIN)
@@ -770,7 +803,7 @@
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_get_repo(self):
-        new_group = 'some_new_group'
+        new_group = u'some_new_group'
         make_user_group(new_group)
         RepoModel().grant_user_group_permission(repo=self.REPO,
                                                 group_name=new_group,
@@ -779,6 +812,8 @@
         id_, params = _build_data(self.apikey, 'get_repo',
                                   repoid=self.REPO)
         response = api_call(self, params)
+        assert u"tags" not in response.json[u'result']
+        assert u'pull_requests' not in response.json[u'result']
 
         repo = RepoModel().get_by_repo_name(self.REPO)
         ret = repo.get_api_data()
@@ -809,10 +844,17 @@
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_user_group(new_group)
 
-    @parameterized.expand([
-        ('repository.admin',),
-        ('repository.write',),
-        ('repository.read',),
+        id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
+                                  with_revision_names=True,
+                                  with_pullrequests=True)
+        response = api_call(self, params)
+        assert u"v0.2.0" in response.json[u'result'][u'tags']
+        assert u'pull_requests' in response.json[u'result']
+
+    @parametrize('grant_perm', [
+        ('repository.admin'),
+        ('repository.write'),
+        ('repository.read'),
     ])
     def test_api_get_repo_by_non_admin(self, grant_perm):
         RepoModel().grant_user_permission(repo=self.REPO,
@@ -828,7 +870,7 @@
 
         members = []
         followers = []
-        self.assertEqual(2, len(repo.repo_to_perm))
+        assert 2 == len(repo.repo_to_perm)
         for user in repo.repo_to_perm:
             perm = user.permission.permission_name
             user_obj = user.user
@@ -880,29 +922,29 @@
         id_, params = _build_data(self.apikey, 'get_repos')
         response = api_call(self, params)
 
-        result = []
-        for repo in RepoModel().get_all():
-            result.append(repo.get_api_data())
-        ret = jsonify(result)
+        expected = jsonify([
+            repo.get_api_data()
+            for repo in Repository.query()
+        ])
 
-        expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_repos_non_admin(self):
         id_, params = _build_data(self.apikey_regular, 'get_repos')
         response = api_call(self, params)
 
-        result = []
-        for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN):
-            result.append(repo.get_api_data())
-        ret = jsonify(result)
+        expected = jsonify([
+            repo.get_api_data()
+            for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
+        ])
 
-        expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    @parameterized.expand([('all', 'all'),
-                           ('dirs', 'dirs'),
-                           ('files', 'files'), ])
+    @parametrize('name,ret_type', [
+        ('all', 'all'),
+        ('dirs', 'dirs'),
+        ('files', 'files'),
+    ])
     def test_api_get_repo_nodes(self, name, ret_type):
         rev = 'tip'
         path = '/'
@@ -953,9 +995,11 @@
                     % (','.join(['files', 'dirs', 'all'])))
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([('all', 'all', 'repository.write'),
-                           ('dirs', 'dirs', 'repository.admin'),
-                           ('files', 'files', 'repository.read'), ])
+    @parametrize('name,ret_type,grant_perm', [
+        ('all', 'all', 'repository.write'),
+        ('dirs', 'dirs', 'repository.admin'),
+        ('files', 'files', 'repository.read'),
+    ])
     def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
         RepoModel().grant_user_permission(repo=self.REPO,
                                           user=self.TEST_USER_LOGIN,
@@ -979,7 +1023,7 @@
             RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
 
     def test_api_create_repo(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
                                   owner=TEST_USER_ADMIN_LOGIN,
@@ -988,7 +1032,7 @@
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
-        self.assertNotEqual(repo, None)
+        assert repo is not None
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
             'success': True,
@@ -998,13 +1042,13 @@
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_repo(repo_name)
 
-    @parameterized.expand([
-        (u'',),
-        (u'.',),
-        (u'..',),
-        (u':',),
-        (u'/',),
-        (u'<test>',),
+    @parametrize('repo_name', [
+        u'',
+        u'.',
+        u'..',
+        u':',
+        u'/',
+        u'<test>',
     ])
     def test_api_create_repo_bad_names(self, repo_name):
         id_, params = _build_data(self.apikey, 'create_repo',
@@ -1022,7 +1066,7 @@
         fixture.destroy_repo(repo_name)
 
     def test_api_create_repo_clone_uri_local(self):
-        # cloning from local repo was a mis-feature - it would bypass access control
+        # cloning from local repos was a mis-feature - it would bypass access control
         # TODO: introduce other test coverage of actual remote cloning
         clone_uri = os.path.join(TESTS_TMP_PATH, self.REPO)
         repo_name = u'api-repo'
@@ -1038,8 +1082,8 @@
         fixture.destroy_repo(repo_name)
 
     def test_api_create_repo_and_repo_group(self):
-        repo_group_name = 'my_gr'
-        repo_name = '%s/api-repo' % repo_group_name
+        repo_group_name = u'my_gr'
+        repo_name = u'%s/api-repo' % repo_group_name
 
         # repo creation can no longer also create repo group
         id_, params = _build_data(self.apikey, 'create_repo',
@@ -1067,19 +1111,19 @@
         }
         self._compare_ok(id_, expected, given=response.body)
         repo = RepoModel().get_by_repo_name(repo_name)
-        self.assertNotEqual(repo, None)
+        assert repo is not None
 
         fixture.destroy_repo(repo_name)
         fixture.destroy_repo_group(repo_group_name)
 
     def test_api_create_repo_in_repo_group_without_permission(self):
-        repo_group_basename = 'api-repo-repo'
-        repo_group_name = '%s/%s' % (TEST_REPO_GROUP, repo_group_basename)
-        repo_name = '%s/api-repo' % repo_group_name
+        repo_group_basename = u'api-repo-repo'
+        repo_group_name = u'%s/%s' % (TEST_REPO_GROUP, repo_group_basename)
+        repo_name = u'%s/api-repo' % repo_group_name
 
         top_group = RepoGroup.get_by_group_name(TEST_REPO_GROUP)
         assert top_group
-        rg = fixture.create_repo_group(repo_group_basename, group_parent_id=top_group)
+        rg = fixture.create_repo_group(repo_group_basename, parent_group_id=top_group)
         Session().commit()
         RepoGroupModel().grant_user_permission(repo_group_name,
                                                self.TEST_USER_LOGIN,
@@ -1109,7 +1153,7 @@
         fixture.destroy_repo_group(repo_group_name)
 
     def test_api_create_repo_unknown_owner(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
@@ -1121,7 +1165,7 @@
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_create_repo_dont_specify_owner(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
@@ -1130,7 +1174,7 @@
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
-        self.assertNotEqual(repo, None)
+        assert repo is not None
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
             'success': True,
@@ -1141,7 +1185,7 @@
         fixture.destroy_repo(repo_name)
 
     def test_api_create_repo_by_non_admin(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey_regular, 'create_repo',
                                   repo_name=repo_name,
@@ -1150,7 +1194,7 @@
         response = api_call(self, params)
 
         repo = RepoModel().get_by_repo_name(repo_name)
-        self.assertNotEqual(repo, None)
+        assert repo is not None
         ret = {
             'msg': 'Created new repository `%s`' % repo_name,
             'success': True,
@@ -1161,7 +1205,7 @@
         fixture.destroy_repo(repo_name)
 
     def test_api_create_repo_by_non_admin_specify_owner(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey_regular, 'create_repo',
                                   repo_name=repo_name,
@@ -1198,7 +1242,7 @@
 
     @mock.patch.object(RepoModel, 'create', crash)
     def test_api_create_repo_exception_occurred(self):
-        repo_name = 'api-repo'
+        repo_name = u'api-repo'
         id_, params = _build_data(self.apikey, 'create_repo',
                                   repo_name=repo_name,
                                   owner=TEST_USER_ADMIN_LOGIN,
@@ -1207,11 +1251,9 @@
         expected = 'failed to create repository `%s`' % repo_name
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('changing_attr,updates', [
         ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
-        ('description', {'description': 'new description'}),
-        ('active', {'active': True}),
-        ('active', {'active': False}),
+        ('description', {'description': u'new description'}),
         ('clone_uri', {'clone_uri': 'http://example.com/repo'}), # will fail - pulling from non-existing repo should fail
         ('clone_uri', {'clone_uri': '/repo'}), # will fail - pulling from local repo was a mis-feature - it would bypass access control
         ('clone_uri', {'clone_uri': None}),
@@ -1219,11 +1261,11 @@
         ('enable_statistics', {'enable_statistics': True}),
         ('enable_locking', {'enable_locking': True}),
         ('enable_downloads', {'enable_downloads': True}),
-        ('name', {'name': 'new_repo_name'}),
-        ('repo_group', {'group': 'test_group_for_update'}),
+        ('name', {'name': u'new_repo_name'}),
+        ('repo_group', {'group': u'test_group_for_update'}),
     ])
     def test_api_update_repo(self, changing_attr, updates):
-        repo_name = 'api_update_me'
+        repo_name = u'api_update_me'
         repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         if changing_attr == 'repo_group':
             fixture.create_repo_group(updates['group'])
@@ -1234,7 +1276,7 @@
         if changing_attr == 'name':
             repo_name = updates['name']
         if changing_attr == 'repo_group':
-            repo_name = '/'.join([updates['group'], repo_name])
+            repo_name = u'/'.join([updates['group'], repo_name])
         try:
             if changing_attr == 'clone_uri' and updates['clone_uri']:
                 expected = u'failed to update repo `%s`' % repo_name
@@ -1250,11 +1292,9 @@
             if changing_attr == 'repo_group':
                 fixture.destroy_repo_group(updates['group'])
 
-    @parameterized.expand([
+    @parametrize('changing_attr,updates', [
         ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
         ('description', {'description': u'new description'}),
-        ('active', {'active': True}),
-        ('active', {'active': False}),
         ('clone_uri', {'clone_uri': 'http://example.com/repo'}), # will fail - pulling from non-existing repo should fail
         ('clone_uri', {'clone_uri': '/repo'}), # will fail - pulling from local repo was a mis-feature - it would bypass access control
         ('clone_uri', {'clone_uri': None}),
@@ -1297,7 +1337,7 @@
         fixture.destroy_repo_group(group_name)
 
     def test_api_update_repo_repo_group_does_not_exist(self):
-        repo_name = 'admin_owned'
+        repo_name = u'admin_owned'
         fixture.create_repo(repo_name)
         updates = {'group': 'test_group_for_update'}
         id_, params = _build_data(self.apikey, 'update_repo',
@@ -1310,9 +1350,9 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_update_repo_regular_user_not_allowed(self):
-        repo_name = 'admin_owned'
+        repo_name = u'admin_owned'
         fixture.create_repo(repo_name)
-        updates = {'active': False}
+        updates = {'description': 'something else'}
         id_, params = _build_data(self.apikey_regular, 'update_repo',
                                   repoid=repo_name, **updates)
         response = api_call(self, params)
@@ -1324,7 +1364,7 @@
 
     @mock.patch.object(RepoModel, 'update', crash)
     def test_api_update_repo_exception_occurred(self):
-        repo_name = 'api_update_me'
+        repo_name = u'api_update_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         id_, params = _build_data(self.apikey, 'update_repo',
                                   repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
@@ -1336,8 +1376,8 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_update_repo_regular_user_change_repo_name(self):
-        repo_name = 'admin_owned'
-        new_repo_name = 'new_repo_name'
+        repo_name = u'admin_owned'
+        new_repo_name = u'new_repo_name'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         RepoModel().grant_user_permission(repo=repo_name,
                                           user=self.TEST_USER_LOGIN,
@@ -1356,8 +1396,8 @@
             fixture.destroy_repo(new_repo_name)
 
     def test_api_update_repo_regular_user_change_repo_name_allowed(self):
-        repo_name = 'admin_owned'
-        new_repo_name = 'new_repo_name'
+        repo_name = u'admin_owned'
+        new_repo_name = u'new_repo_name'
         repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         RepoModel().grant_user_permission(repo=repo_name,
                                           user=self.TEST_USER_LOGIN,
@@ -1379,7 +1419,7 @@
             fixture.destroy_repo(new_repo_name)
 
     def test_api_update_repo_regular_user_change_owner(self):
-        repo_name = 'admin_owned'
+        repo_name = u'admin_owned'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         RepoModel().grant_user_permission(repo=repo_name,
                                           user=self.TEST_USER_LOGIN,
@@ -1395,7 +1435,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_delete_repo(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
 
         id_, params = _build_data(self.apikey, 'delete_repo',
@@ -1413,7 +1453,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_delete_repo_by_non_admin(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
                             cur_user=self.TEST_USER_LOGIN)
         id_, params = _build_data(self.apikey_regular, 'delete_repo',
@@ -1431,7 +1471,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_delete_repo_by_non_admin_no_permission(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         try:
             id_, params = _build_data(self.apikey_regular, 'delete_repo',
@@ -1443,7 +1483,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_delete_repo_exception_occurred(self):
-        repo_name = 'api_delete_me'
+        repo_name = u'api_delete_me'
         fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
         try:
             with mock.patch.object(RepoModel, 'delete', crash):
@@ -1457,7 +1497,7 @@
             fixture.destroy_repo(repo_name)
 
     def test_api_fork_repo(self):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
@@ -1475,9 +1515,9 @@
         self._compare_ok(id_, expected, given=response.body)
         fixture.destroy_repo(fork_name)
 
-    @parameterized.expand([
-        (u'api-repo-fork',),
-        (u'%s/api-repo-fork' % TEST_REPO_GROUP,),
+    @parametrize('fork_name', [
+        u'api-repo-fork',
+        u'%s/api-repo-fork' % TEST_REPO_GROUP,
     ])
     def test_api_fork_repo_non_admin(self, fork_name):
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
@@ -1497,7 +1537,7 @@
         fixture.destroy_repo(fork_name)
 
     def test_api_fork_repo_non_admin_specify_owner(self):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
@@ -1512,7 +1552,7 @@
         RepoModel().grant_user_permission(repo=self.REPO,
                                           user=self.TEST_USER_LOGIN,
                                           perm='repository.none')
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         id_, params = _build_data(self.apikey_regular, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
@@ -1522,11 +1562,13 @@
         self._compare_error(id_, expected, given=response.body)
         fixture.destroy_repo(fork_name)
 
-    @parameterized.expand([('read', 'repository.read'),
-                           ('write', 'repository.write'),
-                           ('admin', 'repository.admin')])
+    @parametrize('name,perm', [
+        ('read', 'repository.read'),
+        ('write', 'repository.write'),
+        ('admin', 'repository.admin'),
+    ])
     def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         # regardless of base repository permission, forking is disallowed
         # when repository creation is disabled
         RepoModel().grant_user_permission(repo=self.REPO,
@@ -1544,7 +1586,7 @@
         fixture.destroy_repo(fork_name)
 
     def test_api_fork_repo_unknown_owner(self):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         owner = 'i-dont-exist'
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
@@ -1556,11 +1598,11 @@
         self._compare_error(id_, expected, given=response.body)
 
     def test_api_fork_repo_fork_exists(self):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         fixture.create_fork(self.REPO, fork_name)
 
         try:
-            fork_name = 'api-repo-fork'
+            fork_name = u'api-repo-fork'
 
             id_, params = _build_data(self.apikey, 'fork_repo',
                                       repoid=self.REPO,
@@ -1589,7 +1631,7 @@
 
     @mock.patch.object(RepoModel, 'create_fork', crash)
     def test_api_fork_repo_exception_occurred(self):
-        fork_name = 'api-repo-fork'
+        fork_name = u'api-repo-fork'
         id_, params = _build_data(self.apikey, 'fork_repo',
                                   repoid=self.REPO,
                                   fork_name=fork_name,
@@ -1618,15 +1660,15 @@
         self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_user_groups(self):
-        gr_name = 'test_user_group2'
+        gr_name = u'test_user_group2'
         make_user_group(gr_name)
 
-        id_, params = _build_data(self.apikey, 'get_user_groups', )
-        response = api_call(self, params)
+        try:
+            id_, params = _build_data(self.apikey, 'get_user_groups', )
+            response = api_call(self, params)
 
-        try:
             expected = []
-            for gr_name in [TEST_USER_GROUP, 'test_user_group2']:
+            for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
                 user_group = UserGroupModel().get_group(gr_name)
                 ret = user_group.get_api_data()
                 expected.append(ret)
@@ -1635,7 +1677,7 @@
             fixture.destroy_user_group(gr_name)
 
     def test_api_create_user_group(self):
-        group_name = 'some_new_group'
+        group_name = u'some_new_group'
         id_, params = _build_data(self.apikey, 'create_user_group',
                                   group_name=group_name)
         response = api_call(self, params)
@@ -1661,7 +1703,7 @@
 
     @mock.patch.object(UserGroupModel, 'create', crash)
     def test_api_get_user_group_exception_occurred(self):
-        group_name = 'exception_happens'
+        group_name = u'exception_happens'
         id_, params = _build_data(self.apikey, 'create_user_group',
                                   group_name=group_name)
         response = api_call(self, params)
@@ -1669,18 +1711,20 @@
         expected = 'failed to create group `%s`' % group_name
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([('group_name', {'group_name': 'new_group_name'}),
-                           ('group_name', {'group_name': 'test_group_for_update'}),
-                           ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
-                           ('active', {'active': False}),
-                           ('active', {'active': True})])
+    @parametrize('changing_attr,updates', [
+        ('group_name', {'group_name': u'new_group_name'}),
+        ('group_name', {'group_name': u'test_group_for_update'}),
+        ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
+        ('active', {'active': False}),
+        ('active', {'active': True}),
+    ])
     def test_api_update_user_group(self, changing_attr, updates):
-        gr_name = 'test_group_for_update'
+        gr_name = u'test_group_for_update'
         user_group = fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'update_user_group',
-                                  usergroupid=gr_name, **updates)
-        response = api_call(self, params)
         try:
+            id_, params = _build_data(self.apikey, 'update_user_group',
+                                      usergroupid=gr_name, **updates)
+            response = api_call(self, params)
             expected = {
                'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
                                                      user_group.users_group_name),
@@ -1695,25 +1739,25 @@
 
     @mock.patch.object(UserGroupModel, 'update', crash)
     def test_api_update_user_group_exception_occurred(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'update_user_group',
-                                  usergroupid=gr_name)
-        response = api_call(self, params)
         try:
+            id_, params = _build_data(self.apikey, 'update_user_group',
+                                      usergroupid=gr_name)
+            response = api_call(self, params)
             expected = 'failed to update user group `%s`' % gr_name
             self._compare_error(id_, expected, given=response.body)
         finally:
             fixture.destroy_user_group(gr_name)
 
     def test_api_add_user_to_user_group(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
         try:
+            id_, params = _build_data(self.apikey, 'add_user_to_user_group',
+                                      usergroupid=gr_name,
+                                      userid=TEST_USER_ADMIN_LOGIN)
+            response = api_call(self, params)
             expected = {
             'msg': 'added member `%s` to user group `%s`' % (
                     TEST_USER_ADMIN_LOGIN, gr_name),
@@ -1734,30 +1778,28 @@
 
     @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
     def test_api_add_user_to_user_group_exception_occurred(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         fixture.create_user_group(gr_name)
-        id_, params = _build_data(self.apikey, 'add_user_to_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
-
         try:
+            id_, params = _build_data(self.apikey, 'add_user_to_user_group',
+                                      usergroupid=gr_name,
+                                      userid=TEST_USER_ADMIN_LOGIN)
+            response = api_call(self, params)
             expected = 'failed to add member to user group `%s`' % gr_name
             self._compare_error(id_, expected, given=response.body)
         finally:
             fixture.destroy_user_group(gr_name)
 
     def test_api_remove_user_from_user_group(self):
-        gr_name = 'test_group_3'
+        gr_name = u'test_group_3'
         gr = fixture.create_user_group(gr_name)
         UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
-        id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
-
         try:
+            id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
+                                      usergroupid=gr_name,
+                                      userid=TEST_USER_ADMIN_LOGIN)
+            response = api_call(self, params)
             expected = {
                 'msg': 'removed member `%s` from user group `%s`' % (
                     TEST_USER_ADMIN_LOGIN, gr_name
@@ -1769,30 +1811,28 @@
 
     @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
     def test_api_remove_user_from_user_group_exception_occurred(self):
-        gr_name = 'test_group_3'
+        gr_name = u'test_group_3'
         gr = fixture.create_user_group(gr_name)
         UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
-        id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
         try:
+            id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
+                                      usergroupid=gr_name,
+                                      userid=TEST_USER_ADMIN_LOGIN)
+            response = api_call(self, params)
             expected = 'failed to remove member from user group `%s`' % gr_name
             self._compare_error(id_, expected, given=response.body)
         finally:
             fixture.destroy_user_group(gr_name)
 
     def test_api_delete_user_group(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         ugroup = fixture.create_user_group(gr_name)
         gr_id = ugroup.users_group_id
-        id_, params = _build_data(self.apikey, 'delete_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
-
         try:
+            id_, params = _build_data(self.apikey, 'delete_user_group',
+                                      usergroupid=gr_name)
+            response = api_call(self, params)
             expected = {
                 'user_group': None,
                 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
@@ -1803,19 +1843,17 @@
                 fixture.destroy_user_group(gr_name)
 
     def test_api_delete_user_group_that_is_assigned(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         ugroup = fixture.create_user_group(gr_name)
         gr_id = ugroup.users_group_id
 
         ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
         msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
 
-        id_, params = _build_data(self.apikey, 'delete_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
-        response = api_call(self, params)
-
         try:
+            id_, params = _build_data(self.apikey, 'delete_user_group',
+                                      usergroupid=gr_name)
+            response = api_call(self, params)
             expected = msg
             self._compare_error(id_, expected, given=response.body)
         finally:
@@ -1823,12 +1861,11 @@
                 fixture.destroy_user_group(gr_name)
 
     def test_api_delete_user_group_exception_occurred(self):
-        gr_name = 'test_group'
+        gr_name = u'test_group'
         ugroup = fixture.create_user_group(gr_name)
         gr_id = ugroup.users_group_id
         id_, params = _build_data(self.apikey, 'delete_user_group',
-                                  usergroupid=gr_name,
-                                  userid=TEST_USER_ADMIN_LOGIN)
+                                  usergroupid=gr_name)
 
         try:
             with mock.patch.object(UserGroupModel, 'delete', crash):
@@ -1838,10 +1875,12 @@
         finally:
             fixture.destroy_user_group(gr_name)
 
-    @parameterized.expand([('none', 'repository.none'),
-                           ('read', 'repository.read'),
-                           ('write', 'repository.write'),
-                           ('admin', 'repository.admin')])
+    @parametrize('name,perm', [
+        ('none', 'repository.none'),
+        ('read', 'repository.read'),
+        ('write', 'repository.write'),
+        ('admin', 'repository.admin'),
+    ])
     def test_api_grant_user_permission(self, name, perm):
         id_, params = _build_data(self.apikey,
                                   'grant_user_permission',
@@ -1914,10 +1953,12 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([('none', 'repository.none'),
-                           ('read', 'repository.read'),
-                           ('write', 'repository.write'),
-                           ('admin', 'repository.admin')])
+    @parametrize('name,perm', [
+        ('none', 'repository.none'),
+        ('read', 'repository.read'),
+        ('write', 'repository.write'),
+        ('admin', 'repository.admin'),
+    ])
     def test_api_grant_user_group_permission(self, name, perm):
         id_, params = _build_data(self.apikey,
                                   'grant_user_group_permission',
@@ -1994,7 +2035,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,perm,apply_to_children', [
         ('none', 'group.none', 'none'),
         ('read', 'group.read', 'none'),
         ('write', 'group.write', 'none'),
@@ -2032,7 +2073,7 @@
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
         ('none_fails', 'group.none', 'none', False, False),
         ('read_fails', 'group.read', 'none', False, False),
         ('write_fails', 'group.write', 'none', False, False),
@@ -2098,7 +2139,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,apply_to_children', [
         ('none', 'none'),
         ('all', 'all'),
         ('repos', 'repos'),
@@ -2125,7 +2166,7 @@
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,apply_to_children,grant_admin,access_ok', [
         ('none', 'none', False, False),
         ('all', 'all', False, False),
         ('repos', 'repos', False, False),
@@ -2181,7 +2222,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,perm,apply_to_children', [
         ('none', 'group.none', 'none'),
         ('read', 'group.read', 'none'),
         ('write', 'group.write', 'none'),
@@ -2220,7 +2261,7 @@
         expected = ret
         self._compare_ok(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
         ('none_fails', 'group.none', 'none', False, False),
         ('read_fails', 'group.read', 'none', False, False),
         ('write_fails', 'group.write', 'none', False, False),
@@ -2287,7 +2328,7 @@
         )
         self._compare_error(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,apply_to_children', [
         ('none', 'none'),
         ('all', 'all'),
         ('repos', 'repos'),
@@ -2313,7 +2354,7 @@
         }
         self._compare_ok(id_, expected, given=response.body)
 
-    @parameterized.expand([
+    @parametrize('name,apply_to_children,grant_admin,access_ok', [
         ('none', 'none', False, False),
         ('all', 'all', False, False),
         ('repos', 'repos', False, False),
@@ -2413,7 +2454,7 @@
         id_, params = _build_data(self.apikey, 'get_gists')
         response = api_call(self, params)
         expected = response.json
-        self.assertEqual(len(response.json['result']), 2)
+        assert len(response.json['result']) == 2
         #self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_gists_regular_user(self):
@@ -2429,7 +2470,7 @@
         id_, params = _build_data(self.apikey_regular, 'get_gists')
         response = api_call(self, params)
         expected = response.json
-        self.assertEqual(len(response.json['result']), 3)
+        assert len(response.json['result']) == 3
         #self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_gists_only_for_regular_user(self):
@@ -2446,7 +2487,7 @@
                                   userid=self.TEST_USER_LOGIN)
         response = api_call(self, params)
         expected = response.json
-        self.assertEqual(len(response.json['result']), 3)
+        assert len(response.json['result']) == 3
         #self._compare_ok(id_, expected, given=response.body)
 
     def test_api_get_gists_regular_user_with_different_userid(self):
@@ -2533,3 +2574,177 @@
         response = api_call(self, params)
         expected = Setting.get_server_info()
         self._compare_ok(id_, expected, given=response.body)
+
+    def test_api_get_changesets(self):
+        id_, params = _build_data(self.apikey, 'get_changesets',
+                                  repoid=self.REPO, start=0, end=2)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert len(result) == 3
+        assert 'message' in result[0]
+        assert 'added' not in result[0]
+
+    def test_api_get_changesets_with_max_revisions(self):
+        id_, params = _build_data(self.apikey, 'get_changesets',
+                                  repoid=self.REPO, start_date="2011-02-24T00:00:00", max_revisions=10)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert len(result) == 10
+        assert 'message' in result[0]
+        assert 'added' not in result[0]
+
+    def test_api_get_changesets_with_branch(self):
+        if self.REPO == 'vcs_test_hg':
+            branch = 'stable'
+        else:
+            pytest.skip("skipping due to missing branches in git test repo")
+        id_, params = _build_data(self.apikey, 'get_changesets',
+                                  repoid=self.REPO, branch_name=branch, start_date="2011-02-24T00:00:00")
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert len(result) == 5
+        assert 'message' in result[0]
+        assert 'added' not in result[0]
+
+    def test_api_get_changesets_with_file_list(self):
+        id_, params = _build_data(self.apikey, 'get_changesets',
+                                  repoid=self.REPO, start_date="2010-04-07T23:30:30", end_date="2010-04-08T00:31:14", with_file_list=True)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert len(result) == 3
+        assert 'message' in result[0]
+        assert 'added' in result[0]
+
+    def test_api_get_changeset(self):
+        review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id=self.TEST_REVISION)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert result["raw_id"] == self.TEST_REVISION
+        assert "reviews" not in result
+
+    def test_api_get_changeset_with_reviews(self):
+        reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id=self.TEST_REVISION,
+                                  with_reviews=True)
+        response = api_call(self, params)
+        result = json.loads(response.body)["result"]
+        assert result["raw_id"] == self.TEST_REVISION
+        assert "reviews" in result
+        assert len(result["reviews"]) == 1
+        review = result["reviews"][0]
+        expected = {
+            'status': 'approved',
+            'modified_at': reviewobjs[0].modified_at.replace(microsecond=0).isoformat(),
+            'reviewer': 'test_admin',
+        }
+        assert review == expected
+
+    def test_api_get_changeset_that_does_not_exist(self):
+        """ Fetch changeset status for non-existant changeset.
+        revision id is the above git hash used in the test above with the
+        last 3 nibbles replaced with 0xf.  Should not exist for git _or_ hg.
+        """
+        id_, params = _build_data(self.apikey, 'get_changeset',
+                                  repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
+        response = api_call(self, params)
+        expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_changeset_without_permission(self):
+        review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
+        RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
+        RepoModel().revoke_user_permission(repo=self.REPO, user="default")
+        id_, params = _build_data(self.apikey_regular, 'get_changeset',
+                                  repoid=self.REPO, raw_id=self.TEST_REVISION)
+        response = api_call(self, params)
+        expected = u'Access denied to repo %s' % self.REPO
+        self._compare_error(id_, expected, given=response.body)
+
+    def test_api_get_pullrequest(self):
+        pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'get test')
+        random_id = random.randrange(1, 9999)
+        params = json.dumps({
+            "id": random_id,
+            "api_key": self.apikey,
+            "method": 'get_pullrequest',
+            "args": {"pullrequest_id": pull_request_id},
+        })
+        response = api_call(self, params)
+        pullrequest = PullRequest().get(pull_request_id)
+        expected = {
+            "status": "new",
+            "pull_request_id": pull_request_id,
+            "description": "No description",
+            "url": "/%s/pull-request/%s/_/%s" % (self.REPO, pull_request_id, "stable"),
+            "reviewers": [{"username": "test_regular"}],
+            "org_repo_url": "http://localhost:80/%s" % self.REPO,
+            "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
+            "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
+            "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
+                         "comment_id": pullrequest.comments[0].comment_id}],
+            "owner": TEST_USER_ADMIN_LOGIN,
+            "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00"} for i in range(0, len(self.TEST_PR_REVISIONS))],
+            "title": "get test",
+            "revisions": self.TEST_PR_REVISIONS,
+        }
+        self._compare_ok(random_id, expected,
+                         given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d",
+                                      "2000-01-01T00:00:00", response.body))
+
+    def test_api_close_pullrequest(self):
+        pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'close test')
+        random_id = random.randrange(1, 9999)
+        params = json.dumps({
+            "id": random_id,
+            "api_key": self.apikey,
+            "method": "comment_pullrequest",
+            "args": {"pull_request_id": pull_request_id, "close_pr": True},
+        })
+        response = api_call(self, params)
+        self._compare_ok(random_id, True, given=response.body)
+        pullrequest = PullRequest().get(pull_request_id)
+        assert pullrequest.comments[-1].text == ''
+        assert pullrequest.status == PullRequest.STATUS_CLOSED
+        assert pullrequest.is_closed() == True
+
+    def test_api_status_pullrequest(self):
+        pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"status test")
+
+        random_id = random.randrange(1, 9999)
+        params = json.dumps({
+            "id": random_id,
+            "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
+            "method": "comment_pullrequest",
+            "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
+        })
+        response = api_call(self, params)
+        pullrequest = PullRequest().get(pull_request_id)
+        self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
+        assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
+        params = json.dumps({
+            "id": random_id,
+            "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
+            "method": "comment_pullrequest",
+            "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
+        })
+        response = api_call(self, params)
+        self._compare_ok(random_id, True, given=response.body)
+        pullrequest = PullRequest().get(pull_request_id)
+        assert ChangesetStatus.STATUS_APPROVED == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
+
+    def test_api_comment_pullrequest(self):
+        pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"comment test")
+        random_id = random.randrange(1, 9999)
+        params = json.dumps({
+            "id": random_id,
+            "api_key": self.apikey,
+            "method": "comment_pullrequest",
+            "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
+        })
+        response = api_call(self, params)
+        self._compare_ok(random_id, True, given=response.body)
+        pullrequest = PullRequest().get(pull_request_id)
+        assert pullrequest.comments[-1].text == u'Looks good to me'
--- a/kallithea/tests/api/test_api_git.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/api/test_api_git.py	Sun Mar 31 21:28:56 2019 +0200
@@ -12,10 +12,17 @@
 # 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 TestController, GIT_REPO
+from kallithea.tests.base import TestController, GIT_REPO, GIT_TEST_REVISION
 from kallithea.tests.api.api_base import _BaseTestApi
 
 
 class TestGitApi(_BaseTestApi, TestController):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
+    TEST_REVISION = GIT_TEST_REVISION
+    TEST_PR_SRC = u'c60f01b77c42dce653d6b1d3b04689862c261929'
+    TEST_PR_DST = u'10cddef6b794696066fb346434014f0a56810218'
+    TEST_PR_REVISIONS = [u'1bead5880d2dbe831762bf7fb439ba2919b75fdd',
+                         u'9bcd3ecfc8832a8cd881c1c1bbe2d13ffa9d94c7',
+                         u'283de4dfca8479875a1befb8d4059f3bbb725145',
+                         u'c60f01b77c42dce653d6b1d3b04689862c261929']
--- a/kallithea/tests/api/test_api_hg.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/api/test_api_hg.py	Sun Mar 31 21:28:56 2019 +0200
@@ -12,10 +12,18 @@
 # 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 TestController, HG_REPO
+from kallithea.tests.base import TestController, HG_REPO, HG_TEST_REVISION
 from kallithea.tests.api.api_base import _BaseTestApi
 
 
 class TestHgApi(_BaseTestApi, TestController):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
+    TEST_REVISION = HG_TEST_REVISION
+    TEST_PR_SRC = u'4f7e2131323e0749a740c0a56ab68ae9269c562a'
+    TEST_PR_DST = u'92831aebf2f8dd4879e897024b89d09af214df1c'
+    TEST_PR_REVISIONS = [u'720bbdb27665d6262b313e8a541b654d0cbd5b27',
+                         u'f41649565a9e89919a588a163e717b4084f8a3b1',
+                         u'94f45ed825a113e61af7e141f44ca578374abef0',
+                         u'fef5bfe1dc17611d5fb59a7f6f95c55c3606f933',
+                         u'4f7e2131323e0749a740c0a56ab68ae9269c562a']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,195 @@
+# -*- 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/>.
+
+import datetime
+import logging
+import os
+import pytest
+import re
+import tempfile
+import time
+
+from tg import config
+from webtest import TestApp
+
+from kallithea import model
+from kallithea.model.db import User
+from kallithea.model.meta import Session
+from kallithea.lib.utils2 import safe_str
+
+
+log = logging.getLogger(__name__)
+
+skipif = pytest.mark.skipif
+parametrize = pytest.mark.parametrize
+
+# Hack: These module global values MUST be set to actual values before running any tests. This is currently done by conftest.py.
+url = None
+testapp = None
+
+__all__ = [
+    'skipif', 'parametrize', 'url', 'TestController',
+    'ldap_lib_installed', 'pam_lib_installed', 'invalidate_all_caches',
+    'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO',
+    'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS',
+    'TEST_USER_ADMIN_EMAIL', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
+    'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
+    'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO',
+    'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO',
+    'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO',
+    'GIT_REMOTE_REPO', 'HG_TEST_REVISION', 'GIT_TEST_REVISION',
+]
+
+## SOME GLOBALS FOR TESTS
+
+TESTS_TMP_PATH = os.environ.get('KALLITHEA_TESTS_TMP_PATH', tempfile.mkdtemp(prefix='kallithea-test-'))
+
+TEST_USER_ADMIN_LOGIN = 'test_admin'
+TEST_USER_ADMIN_PASS = 'test12'
+TEST_USER_ADMIN_EMAIL = 'test_admin@example.com'
+
+TEST_USER_REGULAR_LOGIN = 'test_regular'
+TEST_USER_REGULAR_PASS = 'test12'
+TEST_USER_REGULAR_EMAIL = 'test_regular@example.com'
+
+TEST_USER_REGULAR2_LOGIN = 'test_regular2'
+TEST_USER_REGULAR2_PASS = 'test12'
+TEST_USER_REGULAR2_EMAIL = 'test_regular2@example.com'
+
+HG_REPO = u'vcs_test_hg'
+GIT_REPO = u'vcs_test_git'
+
+NEW_HG_REPO = u'vcs_test_hg_new'
+NEW_GIT_REPO = u'vcs_test_git_new'
+
+HG_FORK = u'vcs_test_hg_fork'
+GIT_FORK = u'vcs_test_git_fork'
+
+HG_TEST_REVISION = u"a53d9201d4bc278910d416d94941b7ea007ecd52"
+GIT_TEST_REVISION = u"7ab37bc680b4aa72c34d07b230c866c28e9fc204"
+
+
+## VCS
+uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
+
+GIT_REMOTE_REPO = os.path.join(TESTS_TMP_PATH, GIT_REPO)
+
+TEST_GIT_REPO = os.path.join(TESTS_TMP_PATH, GIT_REPO)
+TEST_GIT_REPO_CLONE = os.path.join(TESTS_TMP_PATH, 'vcs-git-clone-%s' % uniq_suffix)
+TEST_GIT_REPO_PULL = os.path.join(TESTS_TMP_PATH, 'vcs-git-pull-%s' % uniq_suffix)
+
+HG_REMOTE_REPO = os.path.join(TESTS_TMP_PATH, HG_REPO)
+
+TEST_HG_REPO = os.path.join(TESTS_TMP_PATH, HG_REPO)
+TEST_HG_REPO_CLONE = os.path.join(TESTS_TMP_PATH, 'vcs-hg-clone-%s' % uniq_suffix)
+TEST_HG_REPO_PULL = os.path.join(TESTS_TMP_PATH, 'vcs-hg-pull-%s' % uniq_suffix)
+
+# By default, some of the tests will utilise locally available
+# repositories stored within tar.gz archives as source for
+# cloning. Should you wish to use some other, remote archive, simply
+# uncomment these entries and/or update the URLs to use.
+#
+# GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
+# HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
+
+# skip ldap tests if LDAP lib is not installed
+ldap_lib_installed = False
+try:
+    import ldap
+    ldap.API_VERSION
+    ldap_lib_installed = True
+except ImportError:
+    # means that python-ldap is not installed
+    pass
+
+try:
+    import pam
+    pam.PAM_TEXT_INFO
+    pam_lib_installed = True
+except ImportError:
+    pam_lib_installed = False
+
+
+def invalidate_all_caches():
+    """Invalidate all beaker caches currently configured.
+    Useful when manipulating IP permissions in a test and changes need to take
+    effect immediately.
+    Note: Any use of this function is probably a workaround - it should be
+    replaced with a more specific cache invalidation in code or test."""
+    from beaker.cache import cache_managers
+    for cache in cache_managers.values():
+        cache.clear()
+
+
+class NullHandler(logging.Handler):
+    def emit(self, record):
+        pass
+
+
+class TestController(object):
+    """Pytest-style test controller"""
+
+    # Note: pytest base classes cannot have an __init__ method
+
+    @pytest.fixture(autouse=True)
+    def app_fixture(self):
+        h = NullHandler()
+        logging.getLogger("kallithea").addHandler(h)
+        self.app = TestApp(testapp)
+        return self.app
+
+    def log_user(self, username=TEST_USER_ADMIN_LOGIN,
+                 password=TEST_USER_ADMIN_PASS):
+        self._logged_username = username
+        response = self.app.post(url(controller='login', action='index'),
+                                 {'username': username,
+                                  'password': password})
+
+        if 'Invalid username or password' in response.body:
+            pytest.fail('could not login using %s %s' % (username, password))
+
+        assert response.status == '302 Found'
+        self.assert_authenticated_user(response, username)
+
+        response = response.follow()
+        return response.session['authuser']
+
+    def _get_logged_user(self):
+        return User.get_by_username(self._logged_username)
+
+    def assert_authenticated_user(self, response, expected_username):
+        cookie = response.session.get('authuser')
+        user = cookie and cookie.get('user_id')
+        user = user and User.get(user)
+        user = user and user.username
+        assert user == expected_username
+
+    def authentication_token(self):
+        return self.app.get(url('authentication_token')).body
+
+    def checkSessionFlash(self, response, msg=None, skip=0, _matcher=lambda msg, m: msg in m):
+        if 'flash' not in response.session:
+            pytest.fail(safe_str(u'msg `%s` not found - session has no flash:\n%s' % (msg, response)))
+        try:
+            level, m = response.session['flash'][-1 - skip]
+            if _matcher(msg, m):
+                return
+        except IndexError:
+            pass
+        pytest.fail(safe_str(u'msg `%s` not found in session flash (skipping %s): %s' %
+                           (msg, skip,
+                            ', '.join('`%s`' % m for level, m in response.session['flash']))))
+
+    def checkSessionFlashRegex(self, response, regex, skip=0):
+        self.checkSessionFlash(response, regex, skip=skip, _matcher=re.search)
--- a/kallithea/tests/conftest.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/conftest.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,34 +1,211 @@
 import os
+import re
 import sys
 import logging
+import pkg_resources
+import time
 
-import pkg_resources
-from paste.deploy import loadapp
-import pylons.test
-from pylons.i18n.translation import _get_translator
+import formencode
+from paste.deploy import loadwsgi
+from routes.util import URLGenerator
+import pytest
+from pytest_localserver.http import WSGIServer
+
+from kallithea.controllers.root import RootController
+from kallithea.lib import inifile
+from kallithea.lib.utils import repo2db_mapper
+from kallithea.model.user import UserModel
+from kallithea.model.meta import Session
+from kallithea.model.db import Setting, User, UserIpMap
+from kallithea.model.scm import ScmModel
+from kallithea.tests.base import invalidate_all_caches, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, \
+    TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS
+import kallithea.tests.base # FIXME: needed for setting testapp instance!!!
+
+from tg.util.webtest import test_context
 
 
 def pytest_configure():
+    os.environ['TZ'] = 'UTC'
+    if not kallithea.is_windows:
+        time.tzset() # only available on Unix
+
     path = os.getcwd()
     sys.path.insert(0, path)
     pkg_resources.working_set.add_entry(path)
 
     # Disable INFO logging of test database creation, restore with NOTSET
     logging.disable(logging.INFO)
-    pylons.test.pylonsapp = loadapp('config:kallithea/tests/test.ini', relative_to=path)
+
+    ini_settings = {
+        '[server:main]': {
+            'port': '4999',
+        },
+        '[app:main]': {
+            'app_instance_uuid': 'test',
+            'show_revision_number': 'true',
+            'beaker.cache.sql_cache_short.expire': '1',
+            'beaker.session.secret': '{74e0cd75-b339-478b-b129-07dd221def1f}',
+            #'i18n.lang': '',
+        },
+        '[handler_console]': {
+            'formatter': 'color_formatter',
+        },
+        # The 'handler_console_sql' block is very similar to the one in
+        # development.ini, but without the explicit 'level=DEBUG' setting:
+        # it causes duplicate sqlalchemy debug logs, one through
+        # handler_console_sql and another through another path.
+        '[handler_console_sql]': {
+            'formatter': 'color_formatter_sql',
+        },
+    }
+    if os.environ.get('TEST_DB'):
+        ini_settings['[app:main]']['sqlalchemy.url'] = os.environ.get('TEST_DB')
+
+    test_ini_file = os.path.join(TESTS_TMP_PATH, 'test.ini')
+    inifile.create(test_ini_file, None, ini_settings)
+
+    context = loadwsgi.loadcontext(loadwsgi.APP, 'config:%s' % test_ini_file)
+    from kallithea.tests.fixture import create_test_env, create_test_index
+
+    # set KALLITHEA_NO_TMP_PATH=1 to disable re-creating the database and test repos
+    if not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0)):
+        create_test_env(TESTS_TMP_PATH, context.config())
+
+    # set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
+    if not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)):
+        create_test_index(TESTS_TMP_PATH, context.config(), True)
+
+    kallithea.tests.base.testapp = context.create()
+    # do initial repo scan
+    repo2db_mapper(ScmModel().repo_scan(TESTS_TMP_PATH))
+
     logging.disable(logging.NOTSET)
 
-    # Setup the config and app_globals, only works if we can get
-    # to the config object
-    conf = getattr(pylons.test.pylonsapp, 'config')
-    if conf:
-        pylons.config._push_object(conf)
+    kallithea.tests.base.url = URLGenerator(RootController().mapper, {'HTTP_HOST': 'example.com'})
+
+    # set fixed language for form messages, regardless of environment settings
+    formencode.api.set_stdtranslation(languages=[])
+
+
+@pytest.fixture
+def create_test_user():
+    """Provide users that automatically disappear after test is over."""
+    test_user_ids = []
+
+    def _create_test_user(user_form):
+        user = UserModel().create(user_form)
+        test_user_ids.append(user.user_id)
+        return user
+    yield _create_test_user
+    for user_id in test_user_ids:
+        UserModel().delete(user_id)
+    Session().commit()
+
+
+def _set_settings(*kvtseq):
+    session = Session()
+    for kvt in kvtseq:
+        assert len(kvt) in (2, 3)
+        k = kvt[0]
+        v = kvt[1]
+        t = kvt[2] if len(kvt) == 3 else 'unicode'
+        Setting.create_or_update(k, v, t)
+    session.commit()
+
+
+@pytest.fixture
+def set_test_settings():
+    """Restore settings after test is over."""
+    # Save settings.
+    settings_snapshot = [
+        (s.app_settings_name, s.app_settings_value, s.app_settings_type)
+        for s in Setting.query().all()]
+    yield _set_settings
+    # Restore settings.
+    session = Session()
+    keys = frozenset(k for (k, v, t) in settings_snapshot)
+    for s in Setting.query().all():
+        if s.app_settings_name not in keys:
+            session.delete(s)
+    for k, v, t in settings_snapshot:
+        if t == 'list' and hasattr(v, '__iter__'):
+            v = ','.join(v) # Quirk: must format list value manually.
+        Setting.create_or_update(k, v, t)
+    session.commit()
+
+
+@pytest.fixture
+def auto_clear_ip_permissions():
+    """Fixture that provides nothing but clearing IP permissions upon test
+    exit. This clearing is needed to avoid other test failing to make fake http
+    accesses."""
+    yield
+    # cleanup
+    user_model = UserModel()
 
-        if 'pylons.app_globals' in conf:
-            pylons.app_globals._push_object(conf['pylons.app_globals'])
+    user_ids = []
+    user_ids.append(User.get_default_user().user_id)
+    user_ids.append(User.get_by_username(TEST_USER_REGULAR_LOGIN).user_id)
+
+    for user_id in user_ids:
+        for ip in UserIpMap.query().filter(UserIpMap.user_id == user_id):
+            user_model.delete_extra_ip(user_id, ip.ip_id)
+
+    # IP permissions are cached, need to invalidate this cache explicitly
+    invalidate_all_caches()
+
+
+@pytest.fixture
+def test_context_fixture(app_fixture):
+    """
+    Encompass the entire test using this fixture in a test_context,
+    making sure that certain functionality still works even if no call to
+    self.app.get/post has been made.
+    The typical error message indicating you need a test_context is:
+        TypeError: No object (name: context) has been registered for this thread
+
+    The standard way to fix this is simply using the test_context context
+    manager directly inside your test:
+        with test_context(self.app):
+            <actions>
+    but if test setup code (xUnit-style or pytest fixtures) also needs to be
+    executed inside the test context, that method is not possible.
+    Even if there is no such setup code, the fixture may reduce code complexity
+    if the entire test needs to run inside a test context.
 
-    # Initialize a translator for tests that utilize i18n
-    translator = _get_translator(pylons.config.get('lang'))
-    pylons.translator._push_object(translator)
+    To apply this fixture (like any other fixture) to all test methods of a
+    class, use the following class decorator:
+        @pytest.mark.usefixtures("test_context_fixture")
+        class TestFoo(TestController):
+            ...
+    """
+    with test_context(app_fixture):
+        yield
+
 
-    return pylons.test.pylonsapp
+class MyWSGIServer(WSGIServer):
+    def repo_url(self, repo_name, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
+        """Return URL to repo on this web server."""
+        host, port = self.server_address
+        proto = 'http' if self._server.ssl_context is None else 'https'
+        auth = ''
+        if username is not None:
+            auth = username
+            if password is not None:
+                auth += ':' + password
+        if auth:
+            auth += '@'
+        return '%s://%s%s:%s/%s' % (proto, auth, host, port, repo_name)
+
+
+@pytest.yield_fixture(scope="session")
+def webserver():
+    """Start web server while tests are running.
+    Useful for debugging and necessary for vcs operation tests."""
+    server = MyWSGIServer(application=kallithea.tests.base.testapp)
+    server.start()
+
+    yield server
+
+    server.stop()
--- a/kallithea/tests/fixture.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/fixture.py	Sun Mar 31 21:28:56 2019 +0200
@@ -15,10 +15,18 @@
 """
 Helpers for fixture generation
 """
+
+import logging
 import os
-import time
-from kallithea.tests import *
-from kallithea.model.db import Repository, User, RepoGroup, UserGroup
+import shutil
+import tarfile
+from os.path import dirname
+
+import mock
+from tg import request
+from tg.util.webtest import test_context
+
+from kallithea.model.db import Repository, User, RepoGroup, UserGroup, Gist, ChangesetStatus
 from kallithea.model.meta import Session
 from kallithea.model.repo import RepoModel
 from kallithea.model.user import UserModel
@@ -26,10 +34,20 @@
 from kallithea.model.user_group import UserGroupModel
 from kallithea.model.gist import GistModel
 from kallithea.model.scm import ScmModel
+from kallithea.model.comment import ChangesetCommentsModel
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.pull_request import CreatePullRequestAction#, CreatePullRequestIterationAction, PullRequestModel
+from kallithea.lib import helpers
+from kallithea.lib.auth import AuthUser
+from kallithea.lib.db_manage import DbManage
 from kallithea.lib.vcs.backends.base import EmptyChangeset
+from kallithea.tests.base import invalidate_all_caches, GIT_REPO, HG_REPO, \
+    TESTS_TMP_PATH, TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_EMAIL
 
-dn = os.path.dirname
-FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
+
+log = logging.getLogger(__name__)
+
+FIXTURES = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'tests', 'fixtures')
 
 
 def error_function(*args, **kwargs):
@@ -58,25 +76,24 @@
                 anon = User.get_default_user()
                 self._before = anon.active
                 anon.active = status
-                Session().add(anon)
                 Session().commit()
-                time.sleep(1.5)  # hack: wait for beaker sql_cache_short to expire
+                invalidate_all_caches()
 
             def __exit__(self, exc_type, exc_val, exc_tb):
                 anon = User.get_default_user()
                 anon.active = self._before
-                Session().add(anon)
                 Session().commit()
 
         return context()
 
     def _get_repo_create_params(self, **custom):
+        """Return form values to be validated through RepoForm"""
         defs = dict(
             repo_name=None,
             repo_type='hg',
             clone_uri='',
             repo_group=u'-1',
-            repo_description='DESC',
+            repo_description=u'DESC',
             repo_private=False,
             repo_landing_rev='rev:tip',
             repo_copy_permissions=False,
@@ -92,11 +109,12 @@
 
         return defs
 
-    def _get_group_create_params(self, **custom):
+    def _get_repo_group_create_params(self, **custom):
+        """Return form values to be validated through RepoGroupForm"""
         defs = dict(
             group_name=None,
-            group_description='DESC',
-            group_parent_id=None,
+            group_description=u'DESC',
+            parent_group_id=u'-1',
             perms_updates=[],
             perms_new=[],
             enable_locking=False,
@@ -111,8 +129,8 @@
             username=name,
             password='qweqwe',
             email='%s+test@example.com' % name,
-            firstname='TestUser',
-            lastname='Test',
+            firstname=u'TestUser',
+            lastname=u'Test',
             active=True,
             admin=False,
             extern_type='internal',
@@ -125,7 +143,7 @@
     def _get_user_group_create_params(self, name, **custom):
         defs = dict(
             users_group_name=name,
-            user_group_description='DESC',
+            user_group_description=u'DESC',
             users_group_active=True,
             user_group_data={},
         )
@@ -133,20 +151,22 @@
 
         return defs
 
-    def create_repo(self, name, **kwargs):
+    def create_repo(self, name, repo_group=None, **kwargs):
         if 'skip_if_exists' in kwargs:
             del kwargs['skip_if_exists']
             r = Repository.get_by_repo_name(name)
             if r:
                 return r
 
-        if isinstance(kwargs.get('repo_group'), RepoGroup):
-            kwargs['repo_group'] = kwargs['repo_group'].group_id
+        if isinstance(repo_group, RepoGroup):
+            repo_group = repo_group.group_id
 
         form_data = self._get_repo_create_params(repo_name=name, **kwargs)
+        form_data['repo_group'] = repo_group # patch form dict so it can be used directly by model
         cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
         RepoModel().create(form_data, cur_user)
         Session().commit()
+        ScmModel().mark_for_invalidation(name)
         return Repository.get_by_repo_name(name)
 
     def create_fork(self, repo_to_fork, fork_name, **kwargs):
@@ -156,9 +176,7 @@
                                             fork_parent_id=repo_to_fork,
                                             repo_type=repo_to_fork.repo_type,
                                             **kwargs)
-        form_data['update_after_clone'] = False
-
-        #TODO: fix it !!
+        # patch form dict so it can be used directly by model
         form_data['description'] = form_data['repo_description']
         form_data['private'] = form_data['repo_private']
         form_data['landing_rev'] = form_data['repo_landing_rev']
@@ -166,6 +184,7 @@
         owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
         RepoModel().create_fork(form_data, cur_user=owner)
         Session().commit()
+        ScmModel().mark_for_invalidation(fork_name)
         r = Repository.get_by_repo_name(fork_name)
         assert r
         return r
@@ -174,19 +193,20 @@
         RepoModel().delete(repo_name, **kwargs)
         Session().commit()
 
-    def create_repo_group(self, name, **kwargs):
+    def create_repo_group(self, name, parent_group_id=None, **kwargs):
         assert '/' not in name, (name, kwargs) # use group_parent_id to make nested groups
         if 'skip_if_exists' in kwargs:
             del kwargs['skip_if_exists']
             gr = RepoGroup.get_by_group_name(group_name=name)
             if gr:
                 return gr
-        form_data = self._get_group_create_params(group_name=name, **kwargs)
-        owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
+        form_data = self._get_repo_group_create_params(group_name=name, **kwargs)
         gr = RepoGroupModel().create(
             group_name=form_data['group_name'],
             group_description=form_data['group_name'],
-            owner=owner, parent=form_data['group_parent_id'])
+            parent=parent_group_id,
+            owner=kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN),
+            )
         Session().commit()
         gr = RepoGroup.get_by_group_name(gr.group_name)
         return gr
@@ -236,13 +256,13 @@
         form_data = {
             'description': u'new-gist',
             'owner': TEST_USER_ADMIN_LOGIN,
-            'gist_type': GistModel.cls.GIST_PUBLIC,
+            'gist_type': Gist.GIST_PUBLIC,
             'lifetime': -1,
-            'gist_mapping': {'filename1.txt':{'content':'hello world'},}
+            'gist_mapping': {'filename1.txt': {'content': 'hello world'}}
         }
         form_data.update(kwargs)
         gist = GistModel().create(
-            description=form_data['description'],owner=form_data['owner'],
+            description=form_data['description'], owner=form_data['owner'],
             gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
             lifetime=form_data['lifetime']
         )
@@ -251,7 +271,7 @@
         return gist
 
     def destroy_gists(self, gistid=None):
-        for g in GistModel.cls.get_all():
+        for g in Gist.query():
             if gistid:
                 if gistid == g.gist_access_id:
                     GistModel().delete(g)
@@ -260,18 +280,21 @@
         Session().commit()
 
     def load_resource(self, resource_name, strip=True):
-        with open(os.path.join(FIXTURES, resource_name)) as f:
+        with open(os.path.join(FIXTURES, resource_name), 'rb') as f:
             source = f.read()
             if strip:
                 source = source.strip()
 
         return source
 
-    def commit_change(self, repo, filename, content, message, vcs_type, parent=None, newfile=False):
+    def commit_change(self, repo, filename, content, message, vcs_type,
+                      parent=None, newfile=False, author=None):
         repo = Repository.get_by_repo_name(repo)
         _cs = parent
-        if not parent:
+        if parent is None:
             _cs = EmptyChangeset(alias=vcs_type)
+        if author is None:
+            author = '%s <%s>' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_EMAIL)
 
         if newfile:
             nodes = {
@@ -284,15 +307,127 @@
                 message=message,
                 nodes=nodes,
                 parent_cs=_cs,
-                author=TEST_USER_ADMIN_LOGIN,
+                author=author,
             )
         else:
             cs = ScmModel().commit_change(
                 repo=repo.scm_instance, repo_name=repo.repo_name,
                 cs=parent, user=TEST_USER_ADMIN_LOGIN,
-                author=TEST_USER_ADMIN_LOGIN,
+                author=author,
                 message=message,
                 content=content,
                 f_path=filename
             )
         return cs
+
+    def review_changeset(self, repo, revision, status, author=TEST_USER_ADMIN_LOGIN):
+        comment = ChangesetCommentsModel().create(u"review comment", repo, author, revision=revision, send_email=False)
+        csm = ChangesetStatusModel().set_status(repo, ChangesetStatus.STATUS_APPROVED, author, comment, revision=revision)
+        Session().commit()
+        return csm
+
+    def create_pullrequest(self, testcontroller, repo_name, pr_src_rev, pr_dst_rev, title=u'title'):
+        org_ref = 'branch:stable:%s' % pr_src_rev
+        other_ref = 'branch:default:%s' % pr_dst_rev
+        with test_context(testcontroller.app): # needed to be able to mock request user
+            org_repo = other_repo = Repository.get_by_repo_name(repo_name)
+            owner_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+            reviewers = [User.get_by_username(TEST_USER_REGULAR_LOGIN)]
+            request.authuser = AuthUser(dbuser=owner_user)
+            # creating a PR sends a message with an absolute URL - without routing that requires mocking
+            with mock.patch.object(helpers, 'url', (lambda arg, qualified=False, **kwargs: ('https://localhost' if qualified else '') + '/fake/' + arg)):
+                cmd = CreatePullRequestAction(org_repo, other_repo, org_ref, other_ref, title, u'No description', owner_user, reviewers)
+                pull_request = cmd.execute()
+            Session().commit()
+        return pull_request.pull_request_id
+
+
+#==============================================================================
+# Global test environment setup
+#==============================================================================
+
+def create_test_env(repos_test_path, config):
+    """
+    Makes a fresh database and
+    install test repository into tmp dir
+    """
+
+    # PART ONE create db
+    dbconf = config['sqlalchemy.url']
+    log.debug('making test db %s', dbconf)
+
+    # create test dir if it doesn't exist
+    if not os.path.isdir(repos_test_path):
+        log.debug('Creating testdir %s', repos_test_path)
+        os.makedirs(repos_test_path)
+
+    dbmanage = DbManage(dbconf=dbconf, root=config['here'],
+                        tests=True)
+    dbmanage.create_tables(override=True)
+    # for tests dynamically set new root paths based on generated content
+    dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
+    dbmanage.create_default_user()
+    dbmanage.admin_prompt()
+    dbmanage.create_permissions()
+    dbmanage.populate_default_permissions()
+    Session().commit()
+    # PART TWO make test repo
+    log.debug('making test vcs repositories')
+
+    idx_path = config['index_dir']
+    data_path = config['cache_dir']
+
+    # clean index and data
+    if idx_path and os.path.exists(idx_path):
+        log.debug('remove %s', idx_path)
+        shutil.rmtree(idx_path)
+
+    if data_path and os.path.exists(data_path):
+        log.debug('remove %s', data_path)
+        shutil.rmtree(data_path)
+
+    # CREATE DEFAULT TEST REPOS
+    tar = tarfile.open(os.path.join(FIXTURES, 'vcs_test_hg.tar.gz'))
+    tar.extractall(os.path.join(TESTS_TMP_PATH, HG_REPO))
+    tar.close()
+
+    tar = tarfile.open(os.path.join(FIXTURES, 'vcs_test_git.tar.gz'))
+    tar.extractall(os.path.join(TESTS_TMP_PATH, GIT_REPO))
+    tar.close()
+
+    # LOAD VCS test stuff
+    from kallithea.tests.vcs import setup_package
+    setup_package()
+
+
+def create_test_index(repo_location, config, full_index):
+    """
+    Makes default test index
+    """
+
+    from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
+    from kallithea.lib.pidlock import DaemonLock, LockHeld
+
+    index_location = os.path.join(config['index_dir'])
+    if not os.path.exists(index_location):
+        os.makedirs(index_location)
+
+    l = DaemonLock(os.path.join(index_location, 'make_index.lock'))
+    WhooshIndexingDaemon(index_location=index_location,
+                         repo_location=repo_location) \
+        .run(full_index=full_index)
+    l.release()
+
+
+def failing_test_hook(ui, repo, **kwargs):
+    ui.write("failing_test_hook failed\n")
+    return 1
+
+
+def exception_test_hook(ui, repo, **kwargs):
+    raise Exception("exception_test_hook threw an exception")
+
+
+def passing_test_hook(ui, repo, **kwargs):
+    ui.write("passing_test_hook succeeded\n")
+    return 0
--- a/kallithea/tests/fixtures/journal_dump.csv	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/fixtures/journal_dump.csv	Sun Mar 31 21:28:56 2019 +0200
@@ -2033,3 +2033,5 @@
 2097,5,natosha.bard,414,natosha-repo1-fork,"",user_created_fork:natosha-repo1-fork,2012-12-05 16:47:24.113866
 2098,5,natosha.bard,414,natosha-repo1-fork,62.116.219.97,"push:a25c825fd81d069596d614efcf92505aed46227a,ac513595518923aca8b39f0a1c33c4c6e0f9d83a,d395e22e8e16373a1fffbe66b322581d69a7db17",2012-12-05 16:47:44.701535
 2099,2,admin,38,code-review-test,"",user_commented_pull_request:73,2012-12-05 17:23:18.059481
+2100,390,this-is-it,415,this,"",user_created_repo,2017-01-22 00:00:00.000000
+2101,390,this-is-it,416,this/is-it,"",user_created_repo,2017-01-22 00:00:01.000000
--- a/kallithea/tests/fixtures/markuptest.diff	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/fixtures/markuptest.diff	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,7 @@
 diff --git a/f b/f
 --- a/f	
 +++ b/f	
-@@ -51,5 +51,12 @@
+@@ -51,6 +51,13 @@
  	begin();
  	
 +	int foo;
@@ -15,3 +15,5 @@
 +	
 +	#define MAX_STEPS (64)
  
+-	#define MIN_STEPS (48)
++	#define MIN_STEPS (42)
--- a/kallithea/tests/functional/test_admin.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,13 @@
 import os
 import csv
 import datetime
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.db import UserLog
 from kallithea.model.meta import Session
 from kallithea.lib.utils2 import safe_unicode
+from os.path import dirname
 
-dn = os.path.dirname
-FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
+FIXTURES = os.path.join(dirname(dirname(os.path.abspath(__file__))), 'fixtures')
 
 
 class TestAdminController(TestController):
@@ -56,94 +56,140 @@
     def test_filter_all_entries(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',))
-        response.mustcontain('2034 Entries')
+        response.mustcontain(' 2036 Entries')
 
     def test_filter_journal_filter_exact_match_on_repository(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:xxx'))
-        response.mustcontain('3 Entries')
+        response.mustcontain(' 3 Entries')
 
     def test_filter_journal_filter_exact_match_on_repository_CamelCase(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:XxX'))
-        response.mustcontain('3 Entries')
+        response.mustcontain(' 3 Entries')
 
     def test_filter_journal_filter_wildcard_on_repository(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:*test*'))
-        response.mustcontain('862 Entries')
+        response.mustcontain(' 862 Entries')
 
     def test_filter_journal_filter_prefix_on_repository(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:test*'))
-        response.mustcontain('257 Entries')
+        response.mustcontain(' 257 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_CamelCase(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:Test*'))
-        response.mustcontain('257 Entries')
+        response.mustcontain(' 257 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_and_user(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:test* AND username:demo'))
-        response.mustcontain('130 Entries')
+        response.mustcontain(' 130 Entries')
 
     def test_filter_journal_filter_prefix_on_repository_or_other_repo(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='repository:test* OR repository:xxx'))
-        response.mustcontain('260 Entries')  # 257 + 3
+        response.mustcontain(' 260 Entries')  # 257 + 3
 
     def test_filter_journal_filter_exact_match_on_username(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='username:demo'))
-        response.mustcontain('1087 Entries')
+        response.mustcontain(' 1087 Entries')
 
     def test_filter_journal_filter_exact_match_on_username_camelCase(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='username:DemO'))
-        response.mustcontain('1087 Entries')
+        response.mustcontain(' 1087 Entries')
 
     def test_filter_journal_filter_wildcard_on_username(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='username:*test*'))
-        response.mustcontain('100 Entries')
+        response.mustcontain(' 100 Entries')
 
     def test_filter_journal_filter_prefix_on_username(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='username:demo*'))
-        response.mustcontain('1101 Entries')
+        response.mustcontain(' 1101 Entries')
 
     def test_filter_journal_filter_prefix_on_user_or_other_user(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='username:demo OR username:volcan'))
-        response.mustcontain('1095 Entries')  # 1087 + 8
+        response.mustcontain(' 1095 Entries')  # 1087 + 8
 
     def test_filter_journal_filter_wildcard_on_action(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='action:*pull_request*'))
-        response.mustcontain('187 Entries')
+        response.mustcontain(' 187 Entries')
 
     def test_filter_journal_filter_on_date(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='date:20121010'))
-        response.mustcontain('47 Entries')
+        response.mustcontain(' 47 Entries')
 
     def test_filter_journal_filter_on_date_2(self):
         self.log_user()
         response = self.app.get(url(controller='admin/admin', action='index',
                                     filter='date:20121020'))
-        response.mustcontain('17 Entries')
+        response.mustcontain(' 17 Entries')
+
+    @parametrize('filter,hit', [
+        #### "repository:" filtering
+        # "/" is used for grouping
+        ('repository:group/test', 4),
+        # "-" is often used for "-fork"
+        ('repository:fork-test1', 5),
+        # using "stop words"
+        ('repository:this', 1),
+        ('repository:this/is-it', 1),
+
+        ## additional tests to quickly find out regression in the future
+        ## (and check case-insensitive search, too)
+        # non-ascii character "." and "-"
+        ('repository:TESTIES1.2.3', 4),
+        ('repository:test_git_repo', 2),
+        # combination with wildcard "*"
+        ('repository:GROUP/*', 182),
+        ('repository:*/test', 7),
+        ('repository:fork-*', 273),
+        ('repository:*-Test1', 5),
+
+        #### "username:" filtering
+        # "-" is valid character
+        ('username:peso-xxx', 4),
+        # using "stop words"
+        ('username:this-is-it', 2),
+
+        ## additional tests to quickly find out regression in the future
+        ## (and check case-insensitive search, too)
+        # non-ascii character "." and "-"
+        ('username:ADMIN_xanroot', 6),
+        ('username:robert.Zaremba', 3),
+        # combination with wildcard "*"
+        ('username:THIS-*', 2),
+        ('username:*-IT', 2),
+    ])
+    def test_filter_journal_filter_tokenization(self, filter, hit):
+        self.log_user()
+
+        response = self.app.get(url(controller='admin/admin', action='index',
+                                    filter=filter))
+        if hit != 1:
+            response.mustcontain(' %s Entries' % hit)
+        else:
+            response.mustcontain(' 1 Entry')
--- a/kallithea/tests/functional/test_admin_auth_settings.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_auth_settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.db import Setting
 
 
@@ -21,16 +21,16 @@
                                     action='index'))
         response.mustcontain('Authentication Plugins')
 
+    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_save_settings(self):
         self.log_user()
-        if not ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
         params.update({'auth_ldap_host': u'dc.example.com',
                        'auth_ldap_port': '999',
                        'auth_ldap_tls_kind': 'PLAIN',
                        'auth_ldap_tls_reqcert': 'NEVER',
+                       'auth_ldap_cacertdir': '',
                        'auth_ldap_dn_user': 'test_user',
                        'auth_ldap_dn_pass': 'test_pass',
                        'auth_ldap_base_dn': 'test_base_dn',
@@ -48,13 +48,11 @@
         self.checkSessionFlash(response, 'Auth settings updated successfully')
 
         new_settings = Setting.get_auth_settings()
-        self.assertEqual(new_settings['auth_ldap_host'], u'dc.example.com',
-                         'fail db write compare')
+        assert new_settings['auth_ldap_host'] == u'dc.example.com', 'fail db write compare'
 
+    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_error_form_wrong_port_number(self):
         self.log_user()
-        if not ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
         params.update({'auth_ldap_host': '',
@@ -78,10 +76,9 @@
         response.mustcontain("""<span class="error-message">"""
                              """Please enter a number</span>""")
 
+    @skipif(not ldap_lib_installed, reason='skipping due to missing ldap lib')
     def test_ldap_error_form(self):
         self.log_user()
-        if not ldap_lib_installed:
-            raise SkipTest('skipping due to missing ldap lib')
 
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_ldap')
         params.update({'auth_ldap_host': 'Host',
@@ -135,6 +132,9 @@
     def test_container_auth_login_header(self):
         self._container_auth_setup(
             auth_container_header='THE_USER_NAME',
+            auth_container_email_header='',
+            auth_container_firstname_header='',
+            auth_container_lastname_header='',
             auth_container_fallback_header='',
             auth_container_clean_username='False',
         )
@@ -143,9 +143,33 @@
             resulting_username='john@example.org',
         )
 
+    def test_container_auth_login_header_attr(self):
+        self._container_auth_setup(
+            auth_container_header='THE_USER_NAME',
+            auth_container_email_header='THE_USER_EMAIL',
+            auth_container_firstname_header='THE_USER_FIRSTNAME',
+            auth_container_lastname_header='THE_USER_LASTNAME',
+            auth_container_fallback_header='',
+            auth_container_clean_username='False',
+        )
+        response = self.app.get(
+            url=url(controller='admin/my_account', action='my_account'),
+            extra_environ={'THE_USER_NAME': 'johnd',
+                           'THE_USER_EMAIL': 'john@example.org',
+                           'THE_USER_FIRSTNAME': 'John',
+                           'THE_USER_LASTNAME': 'Doe',
+                           }
+        )
+        assert response.form['email'].value == 'john@example.org'
+        assert response.form['firstname'].value == 'John'
+        assert response.form['lastname'].value == 'Doe'
+
     def test_container_auth_login_fallback_header(self):
         self._container_auth_setup(
             auth_container_header='THE_USER_NAME',
+            auth_container_email_header='',
+            auth_container_firstname_header='',
+            auth_container_lastname_header='',
             auth_container_fallback_header='HTTP_X_YZZY',
             auth_container_clean_username='False',
         )
@@ -157,6 +181,9 @@
     def test_container_auth_clean_username_at(self):
         self._container_auth_setup(
             auth_container_header='REMOTE_USER',
+            auth_container_email_header='',
+            auth_container_firstname_header='',
+            auth_container_lastname_header='',
             auth_container_fallback_header='',
             auth_container_clean_username='True',
         )
@@ -168,6 +195,9 @@
     def test_container_auth_clean_username_backslash(self):
         self._container_auth_setup(
             auth_container_header='REMOTE_USER',
+            auth_container_email_header='',
+            auth_container_firstname_header='',
+            auth_container_lastname_header='',
             auth_container_fallback_header='',
             auth_container_clean_username='True',
         )
@@ -179,6 +209,9 @@
     def test_container_auth_no_logout(self):
         self._container_auth_setup(
             auth_container_header='REMOTE_USER',
+            auth_container_email_header='',
+            auth_container_firstname_header='',
+            auth_container_lastname_header='',
             auth_container_fallback_header='',
             auth_container_clean_username='True',
         )
@@ -186,7 +219,7 @@
             url=url(controller='admin/my_account', action='my_account'),
             extra_environ={'REMOTE_USER': 'john'},
         )
-        self.assertNotIn('Log Out', response.normal_body)
+        assert 'Log Out' not in response.normal_body
 
     def test_crowd_save_settings(self):
         self.log_user()
@@ -196,6 +229,7 @@
                        'auth_crowd_app_password': 'secret',
                        'auth_crowd_admin_groups': 'mygroup',
                        'auth_crowd_port': '123',
+                       'auth_crowd_method': 'https',
                        'auth_crowd_app_name': 'xyzzy'})
 
         test_url = url(controller='admin/auth_settings',
@@ -205,15 +239,12 @@
         self.checkSessionFlash(response, 'Auth settings updated successfully')
 
         new_settings = Setting.get_auth_settings()
-        self.assertEqual(new_settings['auth_crowd_host'], u'hostname',
-                         'fail db write compare')
+        assert new_settings['auth_crowd_host'] == u'hostname', 'fail db write compare'
 
+    @skipif(not pam_lib_installed, reason='skipping due to missing pam lib')
     def test_pam_save_settings(self):
         self.log_user()
 
-        if not pam_lib_installed:
-            raise SkipTest('skipping due to missing pam lib')
-
         params = self._enable_plugins('kallithea.lib.auth_modules.auth_internal,kallithea.lib.auth_modules.auth_pam')
         params.update({'auth_pam_service': 'kallithea',
                        'auth_pam_gecos': '^foo-.*'})
@@ -225,5 +256,4 @@
         self.checkSessionFlash(response, 'Auth settings updated successfully')
 
         new_settings = Setting.get_auth_settings()
-        self.assertEqual(new_settings['auth_pam_service'], u'kallithea',
-                         'fail db write compare')
+        assert new_settings['auth_pam_service'] == u'kallithea', 'fail db write compare'
--- a/kallithea/tests/functional/test_admin_defaults.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_defaults.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.db import Setting
 
 
@@ -12,19 +12,6 @@
         response.mustcontain('default_repo_enable_downloads')
         response.mustcontain('default_repo_enable_locking')
 
-    def test_index_as_xml(self):
-        response = self.app.get(url('formatted_defaults', format='xml'))
-
-    def test_create(self):
-        response = self.app.post(url('defaults'),
-            {'_authentication_token': self.authentication_token()})
-
-    def test_new(self):
-        response = self.app.get(url('new_default'))
-
-    def test_new_as_xml(self):
-        response = self.app.get(url('formatted_new_default', format='xml'))
-
     def test_update_params_true_hg(self):
         self.log_user()
         params = {
@@ -35,12 +22,12 @@
             'default_repo_type': 'hg',
             '_authentication_token': self.authentication_token(),
         }
-        response = self.app.put(url('default', id='default'), params=params)
+        response = self.app.post(url('defaults_update', id='default'), params=params)
         self.checkSessionFlash(response, 'Default settings updated successfully')
 
         params.pop('_authentication_token')
         defs = Setting.get_default_repo_settings()
-        self.assertEqual(params, defs)
+        assert params == defs
 
     def test_update_params_false_git(self):
         self.log_user()
@@ -52,31 +39,9 @@
             'default_repo_type': 'git',
             '_authentication_token': self.authentication_token(),
         }
-        response = self.app.put(url('default', id='default'), params=params)
+        response = self.app.post(url('defaults_update', id='default'), params=params)
         self.checkSessionFlash(response, 'Default settings updated successfully')
 
         params.pop('_authentication_token')
         defs = Setting.get_default_repo_settings()
-        self.assertEqual(params, defs)
-
-    def test_update_browser_fakeout(self):
-        response = self.app.post(url('default', id=1), params=dict(_method='put', _authentication_token=self.authentication_token()))
-
-    def test_delete(self):
-        # Not possible due to CSRF protection.
-        response = self.app.delete(url('default', id=1), status=405)
-
-    def test_delete_browser_fakeout(self):
-        response = self.app.post(url('default', id=1), params=dict(_method='delete', _authentication_token=self.authentication_token()))
-
-    def test_show(self):
-        response = self.app.get(url('default', id=1))
-
-    def test_show_as_xml(self):
-        response = self.app.get(url('formatted_default', id=1, format='xml'))
-
-    def test_edit(self):
-        response = self.app.get(url('edit_default', id=1))
-
-    def test_edit_as_xml(self):
-        response = self.app.get(url('formatted_edit_default', id=1, format='xml'))
+        assert params == defs
--- a/kallithea/tests/functional/test_admin_gists.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_gists.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.gist import GistModel
 from kallithea.model.meta import Session
 from kallithea.model.db import User, Gist
@@ -10,8 +10,8 @@
     gist_mapping = {
         f_name: {'content': content}
     }
-    user = User.get_by_username(owner)
-    gist = GistModel().create(description, owner=user,
+    owner = User.get_by_username(owner)
+    gist = GistModel().create(description, owner=owner,
                        gist_mapping=gist_mapping, gist_type=gist_type,
                        lifetime=lifetime)
     Session().commit()
@@ -20,8 +20,8 @@
 
 class TestGistsController(TestController):
 
-    def tearDown(self):
-        for g in Gist.get_all():
+    def teardown_method(self, method):
+        for g in Gist.query():
             GistModel().delete(g)
         Session().commit()
 
@@ -50,7 +50,7 @@
         response = self.app.get(url('gists', private=1))
         # Test response...
 
-        #and privates
+        # and privates
         response.mustcontain('gist: %s' % gist.gist_access_id)
 
     def test_create_missing_description(self):
@@ -73,7 +73,7 @@
         response = response.follow()
         response.mustcontain('added file: foo')
         response.mustcontain('gist test')
-        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
+        response.mustcontain('<div class="label label-success">Public Gist</div>')
 
     def test_create_with_path_with_dirs(self):
         self.log_user()
@@ -90,7 +90,6 @@
         self.log_user()
         gist = _create_gist('never-see-me')
         gist.gist_expires = 0  # 1970
-        Session().add(gist)
         Session().commit()
 
         response = self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
@@ -107,7 +106,7 @@
         response = response.follow()
         response.mustcontain('added file: private-foo<')
         response.mustcontain('private gist test')
-        response.mustcontain('<div class="btn btn-mini btn-warning disabled">Private Gist</div>')
+        response.mustcontain('<div class="label label-warning">Private Gist</div>')
 
     def test_create_with_description(self):
         self.log_user()
@@ -123,33 +122,29 @@
         response.mustcontain('added file: foo-desc')
         response.mustcontain('gist test')
         response.mustcontain('gist-desc')
-        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
+        response.mustcontain('<div class="label label-success">Public Gist</div>')
 
     def test_new(self):
         self.log_user()
         response = self.app.get(url('new_gist'))
 
-    def test_update(self):
-        self.skipTest('not implemented')
-        response = self.app.put(url('gist', gist_id=1))
-
     def test_delete(self):
         self.log_user()
         gist = _create_gist('delete-me')
-        response = self.app.post(url('gist', gist_id=gist.gist_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('gist_delete', gist_id=gist.gist_id),
+            params={'_authentication_token': self.authentication_token()})
 
     def test_delete_normal_user_his_gist(self):
         self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
         gist = _create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
-        response = self.app.post(url('gist', gist_id=gist.gist_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('gist_delete', gist_id=gist.gist_id),
+            params={'_authentication_token': self.authentication_token()})
 
     def test_delete_normal_user_not_his_own_gist(self):
         self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
         gist = _create_gist('delete-me')
-        response = self.app.post(url('gist', gist_id=gist.gist_id), status=403,
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('gist_delete', gist_id=gist.gist_id), status=403,
+            params={'_authentication_token': self.authentication_token()})
 
     def test_show(self):
         gist = _create_gist('gist-show-me')
@@ -157,20 +152,20 @@
         response.mustcontain('added file: gist-show-me<')
         response.mustcontain('%s - created' % TEST_USER_ADMIN_LOGIN)
         response.mustcontain('gist-desc')
-        response.mustcontain('<div class="btn btn-mini btn-success disabled">Public Gist</div>')
+        response.mustcontain('<div class="label label-success">Public Gist</div>')
 
     def test_show_as_raw(self):
         gist = _create_gist('gist-show-me', content='GIST CONTENT')
         response = self.app.get(url('formatted_gist',
                                     gist_id=gist.gist_access_id, format='raw'))
-        self.assertEqual(response.body, 'GIST CONTENT')
+        assert response.body == 'GIST CONTENT'
 
     def test_show_as_raw_individual_file(self):
         gist = _create_gist('gist-show-me-raw', content='GIST BODY')
         response = self.app.get(url('formatted_gist_file',
                                     gist_id=gist.gist_access_id, format='raw',
                                     revision='tip', f_path='gist-show-me-raw'))
-        self.assertEqual(response.body, 'GIST BODY')
+        assert response.body == 'GIST BODY'
 
     def test_edit(self):
         response = self.app.get(url('edit_gist', gist_id=1))
--- a/kallithea/tests/functional/test_admin_notifications.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-from kallithea.tests import *
-from kallithea.model.db import User
-
-from kallithea.model.user import UserModel
-from kallithea.model.notification import NotificationModel
-from kallithea.model.meta import Session
-from kallithea.lib import helpers as h
-
-
-class TestNotificationsController(TestController):
-    def setUp(self):
-        self.remove_all_notifications()
-
-    def test_index(self):
-        self.log_user()
-
-        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
-                                          email='u1@example.com',
-                                          firstname='u1', lastname='u1')
-        u1 = u1.user_id
-
-        response = self.app.get(url('notifications'))
-        response.mustcontain('<div class="table">No notifications here yet</div>')
-
-        cur_user = self._get_logged_user()
-        notif = NotificationModel().create(created_by=u1, subject=u'test_notification_1',
-                                           body=u'notification_1', recipients=[cur_user])
-        Session().commit()
-        response = self.app.get(url('notifications'))
-        response.mustcontain('id="notification_%s"' % notif.notification_id)
-
-    def test_delete(self):
-        self.log_user()
-        cur_user = self._get_logged_user()
-
-        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
-                                               email='u1@example.com',
-                                               firstname='u1', lastname='u1')
-        u2 = UserModel().create_or_update(username='u2', password='qweqwe',
-                                               email='u2@example.com',
-                                               firstname='u2', lastname='u2')
-
-        # make notifications
-        notification = NotificationModel().create(created_by=cur_user,
-                                                  subject=u'test',
-                                                  body=u'hi there',
-                                                  recipients=[cur_user, u1, u2])
-        Session().commit()
-        u1 = User.get(u1.user_id)
-        u2 = User.get(u2.user_id)
-
-        # check DB
-        get_notif = lambda un: [x.notification for x in un]
-        self.assertEqual(get_notif(cur_user.notifications), [notification])
-        self.assertEqual(get_notif(u1.notifications), [notification])
-        self.assertEqual(get_notif(u2.notifications), [notification])
-        cur_usr_id = cur_user.user_id
-
-        response = self.app.post(
-            url('notification', notification_id=notification.notification_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
-        self.assertEqual(response.body, 'ok')
-
-        cur_user = User.get(cur_usr_id)
-        self.assertEqual(cur_user.notifications, [])
-
-    def test_show(self):
-        self.log_user()
-        cur_user = self._get_logged_user()
-        u1 = UserModel().create_or_update(username='u1', password='qweqwe',
-                                          email='u1@example.com',
-                                          firstname='u1', lastname='u1')
-        u2 = UserModel().create_or_update(username='u2', password='qweqwe',
-                                          email='u2@example.com',
-                                          firstname='u2', lastname='u2')
-
-        subject = u'test'
-        notif_body = u'hi there'
-        notification = NotificationModel().create(created_by=cur_user,
-                                                  subject=subject,
-                                                  body=notif_body,
-                                                  recipients=[cur_user, u1, u2])
-
-        response = self.app.get(url('notification',
-                                    notification_id=notification.notification_id))
-
-        response.mustcontain(subject)
-        response.mustcontain(notif_body)
-
-    def test_description_with_age(self):
-        self.log_user()
-        cur_user = self._get_logged_user()
-        subject = u'test'
-        notify_body = u'hi there'
-        notification = NotificationModel().create(created_by = cur_user,
-                                                  subject    = subject,
-                                                  body       = notify_body)
-
-        description = NotificationModel().make_description(notification)
-        self.assertEqual(
-            description,
-            "{0} sent message {1}".format(
-                cur_user.username,
-                h.age(notification.created_on)
-                )
-            )
-
-    def test_description_with_datetime(self):
-        self.log_user()
-        cur_user = self._get_logged_user()
-        subject = u'test'
-        notify_body = u'hi there'
-        notification = NotificationModel().create(created_by = cur_user,
-                                                  subject    = subject,
-                                                  body       = notify_body)
-
-        description = NotificationModel().make_description(notification, False)
-        self.assertEqual(
-            description,
-            "{0} sent message at {1}".format(
-                cur_user.username,
-                h.fmt_date(notification.created_on)
-                )
-            )
--- a/kallithea/tests/functional/test_admin_permissions.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_permissions.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,12 @@
+import time
+
 from kallithea.model.db import User, UserIpMap
-from kallithea.tests import *
+from kallithea.model.user import UserModel
+from kallithea.model.meta import Session
+from kallithea.tests.base import *
+
+from tg.util.webtest import test_context
+
 
 class TestAdminPermissionsController(TestController):
 
@@ -14,32 +21,55 @@
         # Test response...
         response.mustcontain('All IP addresses are allowed')
 
-    def test_add_ips(self):
+    def test_add_delete_ips(self, auto_clear_ip_permissions):
         self.log_user()
         default_user_id = User.get_default_user().user_id
-        response = self.app.put(url('edit_user_ips', id=default_user_id),
-                                 params=dict(new_ip='127.0.0.0/24',
-                                 _authentication_token=self.authentication_token()))
+
+        # Add IP and verify it is shown in UI and both gives access and rejects
 
-        response = self.app.get(url('admin_permissions_ips'))
-        response.mustcontain('127.0.0.0/24')
-        response.mustcontain('127.0.0.0 - 127.0.0.255')
+        response = self.app.post(url('edit_user_ips_update', id=default_user_id),
+                                 params=dict(new_ip='0.0.0.0/24',
+                                 _authentication_token=self.authentication_token()))
+        invalidate_all_caches()
+        response = self.app.get(url('admin_permissions_ips'),
+                                extra_environ={'REMOTE_ADDR': '0.0.0.1'})
+        response.mustcontain('0.0.0.0/24')
+        response.mustcontain('0.0.0.0 - 0.0.0.255')
+
+        response = self.app.get(url('admin_permissions_ips'),
+                                extra_environ={'REMOTE_ADDR': '0.0.1.1'}, status=403)
+
+        # Add another IP and verify previously rejected now works
+
+        response = self.app.post(url('edit_user_ips_update', id=default_user_id),
+                                 params=dict(new_ip='0.0.1.0/24',
+                                 _authentication_token=self.authentication_token()))
+        invalidate_all_caches()
 
-        ## delete
-        default_user_id = User.get_default_user().user_id
-        del_ip_id = UserIpMap.query().filter(UserIpMap.user_id ==
-                                             default_user_id).first().ip_id
+        response = self.app.get(url('admin_permissions_ips'),
+                                extra_environ={'REMOTE_ADDR': '0.0.1.1'})
+
+        # Delete latest IP and verify same IP is rejected again
+
+        x = UserIpMap.query().filter_by(ip_addr='0.0.1.0/24').first()
+        response = self.app.post(url('edit_user_ips_delete', id=default_user_id),
+                                 params=dict(del_ip_id=x.ip_id,
+                                             _authentication_token=self.authentication_token()))
+        invalidate_all_caches()
 
-        response = self.app.post(url('edit_user_ips', id=default_user_id),
-                                 params=dict(_method='delete',
-                                             del_ip_id=del_ip_id,
-                                             _authentication_token=self.authentication_token()))
+        response = self.app.get(url('admin_permissions_ips'),
+                                extra_environ={'REMOTE_ADDR': '0.0.1.1'}, status=403)
+
+        # Delete first IP and verify unlimited access again
 
-        response = self.app.get(url('admin_permissions_ips'))
-        response.mustcontain('All IP addresses are allowed')
-        response.mustcontain(no=['127.0.0.0/24'])
-        response.mustcontain(no=['127.0.0.0 - 127.0.0.255'])
+        x = UserIpMap.query().filter_by(ip_addr='0.0.0.0/24').first()
+        response = self.app.post(url('edit_user_ips_delete', id=default_user_id),
+                                 params=dict(del_ip_id=x.ip_id,
+                                             _authentication_token=self.authentication_token()))
+        invalidate_all_caches()
 
+        response = self.app.get(url('admin_permissions_ips'),
+                                extra_environ={'REMOTE_ADDR': '0.0.1.1'})
 
     def test_index_overview(self):
         self.log_user()
@@ -53,7 +83,6 @@
         response = self.app.post(
             url('edit_repo_perms_update', repo_name=HG_REPO),
             params=dict(
-                _method='put',
                 perm_new_member_1='repository.read',
                 perm_new_member_name_1=user.username,
                 perm_new_member_type_1='user',
@@ -66,7 +95,6 @@
         response = self.app.post(
             url('edit_repo_perms_revoke', repo_name=HG_REPO),
             params=dict(
-                _method='delete',
                 obj_type='user',
                 user_id=user.user_id,
                 _authentication_token=self.authentication_token()),
@@ -80,7 +108,6 @@
         response = self.app.post(
             url('edit_repo_perms_update', repo_name=HG_REPO),
             params=dict(
-                _method='put',
                 perm_new_member_1='repository.read',
                 perm_new_member_name_1=user.username,
                 perm_new_member_type_1='user',
@@ -92,9 +119,8 @@
         response = self.app.post(
             url('edit_repo_perms_revoke', repo_name=HG_REPO),
             params=dict(
-                _method='delete',
                 obj_type='user',
                 user_id=user.user_id,
                 _authentication_token=self.authentication_token()),
-            status=200) # success has no content
+            status=200)
         assert not response.body
--- a/kallithea/tests/functional/test_admin_repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,27 @@
-from kallithea.tests import *
+from kallithea.model.db import Repository
+from kallithea.model.meta import Session
+from kallithea.model.repo_group import RepoGroupModel
+from kallithea.tests.base import TestController, url
+from kallithea.tests.fixture import Fixture
+
+
+fixture = Fixture()
+
 
 class TestRepoGroupsController(TestController):
-    pass
+
+    def test_case_insensitivity(self):
+        self.log_user()
+        group_name = u'newgroup'
+        response = self.app.post(url('repos_groups'),
+                                 fixture._get_repo_group_create_params(group_name=group_name,
+                                                                 _authentication_token=self.authentication_token()))
+        # try to create repo group with swapped case
+        swapped_group_name = group_name.swapcase()
+        response = self.app.post(url('repos_groups'),
+                                 fixture._get_repo_group_create_params(group_name=swapped_group_name,
+                                                                 _authentication_token=self.authentication_token()))
+        response.mustcontain('already exists')
+
+        RepoGroupModel().delete(group_name)
+        Session().commit()
--- a/kallithea/tests/functional/test_admin_repos.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_repos.py	Sun Mar 31 21:28:56 2019 +0200
@@ -5,30 +5,32 @@
 import urllib
 
 import pytest
+from sqlalchemy import func
 
 from kallithea.lib import vcs
-from kallithea.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
-    Permission
+from kallithea.lib.utils2 import safe_str, safe_unicode
+from kallithea.model.db import Repository, RepoGroup, UserRepoToPerm, User, \
+    Permission, Ui
 from kallithea.model.user import UserModel
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.repo import RepoModel
-from kallithea.model.meta import Session
+from kallithea.model.meta import Session, Base
 from kallithea.tests.fixture import Fixture, error_function
 
 fixture = Fixture()
 
 
 def _get_permission_for_user(user, repo):
-    perm = UserRepoToPerm.query()\
+    perm = UserRepoToPerm.query() \
                 .filter(UserRepoToPerm.repository ==
-                        Repository.get_by_repo_name(repo))\
-                .filter(UserRepoToPerm.user == User.get_by_username(user))\
+                        Repository.get_by_repo_name(repo)) \
+                .filter(UserRepoToPerm.user == User.get_by_username(user)) \
                 .all()
     return perm
 
 
-class _BaseTest(object):
+class _BaseTestCase(TestController):
     """
     Write all tests here
     """
@@ -38,14 +40,6 @@
     OTHER_TYPE_REPO = None
     OTHER_TYPE = None
 
-    @classmethod
-    def setup_class(cls):
-        pass
-
-    @classmethod
-    def teardown_class(cls):
-        pass
-
     def test_index(self):
         self.log_user()
         response = self.app.get(url('repos'))
@@ -53,7 +47,7 @@
     def test_create(self):
         self.log_user()
         repo_name = self.NEW_REPO
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -62,17 +56,17 @@
                                                 _authentication_token=self.authentication_token()))
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=repo_name))
-        self.assertEqual(response.json, {u'result': True})
+        assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name, repo_name))
 
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name).one()
 
-        self.assertEqual(new_repo.repo_name, repo_name)
-        self.assertEqual(new_repo.description, description)
+        assert new_repo.repo_name == repo_name
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name))
@@ -81,63 +75,49 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)))
         except vcs.exceptions.VCSError:
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
         RepoModel().delete(repo_name)
         Session().commit()
 
-    def test_create_non_ascii(self):
+    def test_case_insensitivity(self):
         self.log_user()
-        non_ascii = "ąęł"
-        repo_name = "%s%s" % (self.NEW_REPO, non_ascii)
-        repo_name_unicode = repo_name.decode('utf8')
-        description = 'description for newly created repo' + non_ascii
-        description_unicode = description.decode('utf8')
+        repo_name = self.NEW_REPO
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
-                        fixture._get_repo_create_params(repo_private=False,
-                                                repo_name=repo_name,
-                                                repo_type=self.REPO_TYPE,
-                                                repo_description=description,
-                                                _authentication_token=self.authentication_token()))
-        ## run the check page that triggers the flash message
-        response = self.app.get(url('repo_check_home', repo_name=repo_name))
-        self.assertEqual(response.json, {u'result': True})
-        self.checkSessionFlash(response,
-                               u'Created repository <a href="/%s">%s</a>'
-                               % (urllib.quote(repo_name), repo_name_unicode))
-        # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
-            .filter(Repository.repo_name == repo_name_unicode).one()
+                                 fixture._get_repo_create_params(repo_private=False,
+                                                                 repo_name=repo_name,
+                                                                 repo_type=self.REPO_TYPE,
+                                                                 repo_description=description,
+                                                                 _authentication_token=self.authentication_token()))
+        # try to create repo with swapped case
+        swapped_repo_name = repo_name.swapcase()
+        response = self.app.post(url('repos'),
+                                 fixture._get_repo_create_params(repo_private=False,
+                                                                 repo_name=swapped_repo_name,
+                                                                 repo_type=self.REPO_TYPE,
+                                                                 repo_description=description,
+                                                                 _authentication_token=self.authentication_token()))
+        response.mustcontain('already exists')
 
-        self.assertEqual(new_repo.repo_name, repo_name_unicode)
-        self.assertEqual(new_repo.description, description_unicode)
-
-        # test if the repository is visible in the list ?
-        response = self.app.get(url('summary_home', repo_name=repo_name))
-        response.mustcontain(repo_name)
-        response.mustcontain(self.REPO_TYPE)
-
-        # test if the repository was created on filesystem
-        try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
-        except vcs.exceptions.VCSError:
-            self.fail('no repo %s in filesystem' % repo_name)
+        RepoModel().delete(repo_name)
+        Session().commit()
 
     def test_create_in_group(self):
         self.log_user()
 
         ## create GROUP
-        group_name = 'sometest_%s' % self.REPO_TYPE
+        group_name = u'sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
-                                     group_description='test',
+                                     group_description=u'test',
                                      owner=TEST_USER_ADMIN_LOGIN)
         Session().commit()
 
-        repo_name = 'ingroup'
+        repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -147,34 +127,34 @@
                                                 _authentication_token=self.authentication_token()))
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
-        self.assertEqual(response.json, {u'result': True})
+        assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name_full, repo_name_full))
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name_full).one()
         new_repo_id = new_repo.repo_id
 
-        self.assertEqual(new_repo.repo_name, repo_name_full)
-        self.assertEqual(new_repo.description, description)
+        assert new_repo.repo_name == repo_name_full
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name_full))
         response.mustcontain(repo_name_full)
         response.mustcontain(self.REPO_TYPE)
 
-        inherited_perms = UserRepoToPerm.query()\
+        inherited_perms = UserRepoToPerm.query() \
             .filter(UserRepoToPerm.repository_id == new_repo_id).all()
-        self.assertEqual(len(inherited_perms), 1)
+        assert len(inherited_perms) == 1
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
         RepoModel().delete(repo_name_full)
         RepoGroupModel().delete(group_name)
@@ -200,21 +180,21 @@
         Session().commit()
 
         ## create GROUP
-        group_name = 'reg_sometest_%s' % self.REPO_TYPE
+        group_name = u'reg_sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
-                                     group_description='test',
+                                     group_description=u'test',
                                      owner=TEST_USER_ADMIN_LOGIN)
         Session().commit()
 
-        group_name_allowed = 'reg_sometest_allowed_%s' % self.REPO_TYPE
+        group_name_allowed = u'reg_sometest_allowed_%s' % self.REPO_TYPE
         gr_allowed = RepoGroupModel().create(group_name=group_name_allowed,
-                                     group_description='test',
+                                     group_description=u'test',
                                      owner=TEST_USER_REGULAR_LOGIN)
         Session().commit()
 
-        repo_name = 'ingroup'
+        repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -226,9 +206,9 @@
         response.mustcontain('Invalid value')
 
         # user is allowed to create in this group
-        repo_name = 'ingroup'
+        repo_name = u'ingroup'
         repo_name_full = RepoGroup.url_sep().join([group_name_allowed, repo_name])
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -239,34 +219,34 @@
 
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=repo_name_full))
-        self.assertEqual(response.json, {u'result': True})
+        assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name_full, repo_name_full))
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name_full).one()
         new_repo_id = new_repo.repo_id
 
-        self.assertEqual(new_repo.repo_name, repo_name_full)
-        self.assertEqual(new_repo.description, description)
+        assert new_repo.repo_name == repo_name_full
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name_full))
         response.mustcontain(repo_name_full)
         response.mustcontain(self.REPO_TYPE)
 
-        inherited_perms = UserRepoToPerm.query()\
+        inherited_perms = UserRepoToPerm.query() \
             .filter(UserRepoToPerm.repository_id == new_repo_id).all()
-        self.assertEqual(len(inherited_perms), 1)
+        assert len(inherited_perms) == 1
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
         RepoModel().delete(repo_name_full)
         RepoGroupModel().delete(group_name)
@@ -277,9 +257,9 @@
         self.log_user()
 
         ## create GROUP
-        group_name = 'sometest_%s' % self.REPO_TYPE
+        group_name = u'sometest_%s' % self.REPO_TYPE
         gr = RepoGroupModel().create(group_name=group_name,
-                                     group_description='test',
+                                     group_description=u'test',
                                      owner=TEST_USER_ADMIN_LOGIN)
         perm = Permission.get_by_key('repository.write')
         RepoGroupModel().grant_user_permission(gr, TEST_USER_REGULAR_LOGIN, perm)
@@ -287,9 +267,9 @@
         ## add repo permissions
         Session().commit()
 
-        repo_name = 'ingroup_inherited_%s' % self.REPO_TYPE
+        repo_name = u'ingroup_inherited_%s' % self.REPO_TYPE
         repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -305,12 +285,12 @@
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name_full, repo_name_full))
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name_full).one()
         new_repo_id = new_repo.repo_id
 
-        self.assertEqual(new_repo.repo_name, repo_name_full)
-        self.assertEqual(new_repo.description, description)
+        assert new_repo.repo_name == repo_name_full
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name_full))
@@ -319,21 +299,21 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_full)))
         except vcs.exceptions.VCSError:
             RepoGroupModel().delete(group_name)
             Session().commit()
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
-        #check if inherited permissiona are applied
-        inherited_perms = UserRepoToPerm.query()\
+        # check if inherited permissiona are applied
+        inherited_perms = UserRepoToPerm.query() \
             .filter(UserRepoToPerm.repository_id == new_repo_id).all()
-        self.assertEqual(len(inherited_perms), 2)
+        assert len(inherited_perms) == 2
 
-        self.assertTrue(TEST_USER_REGULAR_LOGIN in [x.user.username
-                                                    for x in inherited_perms])
-        self.assertTrue('repository.write' in [x.permission.permission_name
-                                               for x in inherited_perms])
+        assert TEST_USER_REGULAR_LOGIN in [x.user.username
+                                                    for x in inherited_perms]
+        assert 'repository.write' in [x.permission.permission_name
+                                               for x in inherited_perms]
 
         RepoModel().delete(repo_name_full)
         RepoGroupModel().delete(group_name)
@@ -342,7 +322,7 @@
     def test_create_remote_repo_wrong_clone_uri(self):
         self.log_user()
         repo_name = self.NEW_REPO
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -352,11 +332,10 @@
                                                 _authentication_token=self.authentication_token()))
         response.mustcontain('Invalid repository URL')
 
-
     def test_create_remote_repo_wrong_clone_uri_hg_svn(self):
         self.log_user()
         repo_name = self.NEW_REPO
-        description = 'description for newly created repo'
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -366,11 +345,10 @@
                                                 _authentication_token=self.authentication_token()))
         response.mustcontain('Invalid repository URL')
 
-
     def test_delete(self):
         self.log_user()
-        repo_name = 'vcs_test_new_to_delete_%s' % self.REPO_TYPE
-        description = 'description for newly created repo'
+        repo_name = u'vcs_test_new_to_delete_%s' % self.REPO_TYPE
+        description = u'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_type=self.REPO_TYPE,
@@ -383,11 +361,11 @@
                                'Created repository <a href="/%s">%s</a>'
                                % (repo_name, repo_name))
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name).one()
 
-        self.assertEqual(new_repo.repo_name, repo_name)
-        self.assertEqual(new_repo.description, description)
+        assert new_repo.repo_name == repo_name
+        assert new_repo.description == description
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name))
@@ -396,33 +374,32 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)))
         except vcs.exceptions.VCSError:
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
         response = self.app.post(url('delete_repo', repo_name=repo_name),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+            params={'_authentication_token': self.authentication_token()})
 
         self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name))
 
         response.follow()
 
-        #check if repo was deleted from db
-        deleted_repo = Session().query(Repository)\
+        # check if repo was deleted from db
+        deleted_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name).scalar()
 
-        self.assertEqual(deleted_repo, None)
+        assert deleted_repo is None
 
-        self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
-                                  False)
+        assert os.path.isdir(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name)) == False
 
     def test_delete_non_ascii(self):
         self.log_user()
         non_ascii = "ąęł"
-        repo_name = "%s%s" % (self.NEW_REPO, non_ascii)
-        repo_name_unicode = repo_name.decode('utf8')
+        repo_name = "%s%s" % (safe_str(self.NEW_REPO), non_ascii)
+        repo_name_unicode = safe_unicode(repo_name)
         description = 'description for newly created repo' + non_ascii
-        description_unicode = description.decode('utf8')
+        description_unicode = safe_unicode(description)
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=repo_name,
@@ -431,16 +408,16 @@
                                                 _authentication_token=self.authentication_token()))
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=repo_name))
-        self.assertEqual(response.json, {u'result': True})
+        assert response.json == {u'result': True}
         self.checkSessionFlash(response,
                                u'Created repository <a href="/%s">%s</a>'
                                % (urllib.quote(repo_name), repo_name_unicode))
         # test if the repo was created in the database
-        new_repo = Session().query(Repository)\
+        new_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name_unicode).one()
 
-        self.assertEqual(new_repo.repo_name, repo_name_unicode)
-        self.assertEqual(new_repo.description, description_unicode)
+        assert new_repo.repo_name == repo_name_unicode
+        assert new_repo.description == description_unicode
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=repo_name))
@@ -449,31 +426,30 @@
 
         # test if the repository was created on filesystem
         try:
-            vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
+            vcs.get_repo(safe_str(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_unicode)))
         except vcs.exceptions.VCSError:
-            self.fail('no repo %s in filesystem' % repo_name)
+            pytest.fail('no repo %s in filesystem' % repo_name)
 
         response = self.app.post(url('delete_repo', repo_name=repo_name),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Deleted repository %s' % (repo_name_unicode))
         response.follow()
 
-        #check if repo was deleted from db
-        deleted_repo = Session().query(Repository)\
+        # check if repo was deleted from db
+        deleted_repo = Session().query(Repository) \
             .filter(Repository.repo_name == repo_name_unicode).scalar()
 
-        self.assertEqual(deleted_repo, None)
+        assert deleted_repo is None
 
-        self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
-                                  False)
+        assert os.path.isdir(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name_unicode)) == False
 
     def test_delete_repo_with_group(self):
-        #TODO:
+        # TODO:
         pass
 
     def test_delete_browser_fakeout(self):
         response = self.app.post(url('delete_repo', repo_name=self.REPO),
-                                 params=dict(_method='delete', _authentication_token=self.authentication_token()))
+                                 params=dict(_authentication_token=self.authentication_token()))
 
     def test_show(self):
         self.log_user()
@@ -484,45 +460,44 @@
 
     def test_set_private_flag_sets_default_to_none(self):
         self.log_user()
-        #initially repository perm should be read
+        # initially repository perm should be read
         perm = _get_permission_for_user(user='default', repo=self.REPO)
-        self.assertTrue(len(perm), 1)
-        self.assertEqual(perm[0].permission.permission_name, 'repository.read')
-        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, False)
+        assert len(perm), 1
+        assert perm[0].permission.permission_name == 'repository.read'
+        assert Repository.get_by_repo_name(self.REPO).private == False
 
-        response = self.app.put(url('put_repo', repo_name=self.REPO),
+        response = self.app.post(url('update_repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=1,
                                                 repo_name=self.REPO,
                                                 repo_type=self.REPO_TYPE,
-                                                user=TEST_USER_ADMIN_LOGIN,
+                                                owner=TEST_USER_ADMIN_LOGIN,
                                                 _authentication_token=self.authentication_token()))
         self.checkSessionFlash(response,
                                msg='Repository %s updated successfully' % (self.REPO))
-        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, True)
+        assert Repository.get_by_repo_name(self.REPO).private == True
 
-        #now the repo default permission should be None
+        # now the repo default permission should be None
         perm = _get_permission_for_user(user='default', repo=self.REPO)
-        self.assertTrue(len(perm), 1)
-        self.assertEqual(perm[0].permission.permission_name, 'repository.none')
+        assert len(perm), 1
+        assert perm[0].permission.permission_name == 'repository.none'
 
-        response = self.app.put(url('put_repo', repo_name=self.REPO),
+        response = self.app.post(url('update_repo', repo_name=self.REPO),
                         fixture._get_repo_create_params(repo_private=False,
                                                 repo_name=self.REPO,
                                                 repo_type=self.REPO_TYPE,
-                                                user=TEST_USER_ADMIN_LOGIN,
+                                                owner=TEST_USER_ADMIN_LOGIN,
                                                 _authentication_token=self.authentication_token()))
         self.checkSessionFlash(response,
                                msg='Repository %s updated successfully' % (self.REPO))
-        self.assertEqual(Repository.get_by_repo_name(self.REPO).private, False)
+        assert Repository.get_by_repo_name(self.REPO).private == False
 
-        #we turn off private now the repo default permission should stay None
+        # we turn off private now the repo default permission should stay None
         perm = _get_permission_for_user(user='default', repo=self.REPO)
-        self.assertTrue(len(perm), 1)
-        self.assertEqual(perm[0].permission.permission_name, 'repository.none')
+        assert len(perm), 1
+        assert perm[0].permission.permission_name == 'repository.none'
 
-        #update this permission back
+        # update this permission back
         perm[0].permission = Permission.get_by_key('repository.read')
-        Session().add(perm[0])
         Session().commit()
 
     def test_set_repo_fork_has_no_self_id(self):
@@ -534,11 +509,11 @@
 
     def test_set_fork_of_other_repo(self):
         self.log_user()
-        other_repo = 'other_%s' % self.REPO_TYPE
+        other_repo = u'other_%s' % self.REPO_TYPE
         fixture.create_repo(other_repo, repo_type=self.REPO_TYPE)
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(other_repo)
-        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo2.repo_id, _authentication_token=self.authentication_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(other_repo)
@@ -559,7 +534,7 @@
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
-        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo2.repo_id, _authentication_token=self.authentication_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
@@ -569,7 +544,7 @@
     def test_set_fork_of_none(self):
         self.log_user()
         ## mark it as None
-        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=None, _authentication_token=self.authentication_token()))
         repo = Repository.get_by_repo_name(self.REPO)
         repo2 = Repository.get_by_repo_name(self.OTHER_TYPE_REPO)
@@ -581,7 +556,7 @@
     def test_set_fork_of_same_repo(self):
         self.log_user()
         repo = Repository.get_by_repo_name(self.REPO)
-        response = self.app.put(url('edit_repo_advanced_fork', repo_name=self.REPO),
+        response = self.app.post(url('edit_repo_advanced_fork', repo_name=self.REPO),
                                 params=dict(id_fork_of=repo.repo_id, _authentication_token=self.authentication_token()))
         self.checkSessionFlash(response,
                                'An error occurred during this operation')
@@ -606,7 +581,7 @@
 
         user = User.get(usr['user_id'])
 
-        repo_name = self.NEW_REPO+'no_perms'
+        repo_name = self.NEW_REPO + u'no_perms'
         description = 'description for newly created repo'
         response = self.app.post(url('repos'),
                         fixture._get_repo_create_params(repo_private=False,
@@ -637,12 +612,13 @@
                                'Error creating repository %s' % repo_name)
         # repo must not be in db
         repo = Repository.get_by_repo_name(repo_name)
-        self.assertEqual(repo, None)
+        assert repo is None
 
         # repo must not be in filesystem !
-        self.assertFalse(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)))
+        assert not os.path.isdir(os.path.join(Ui.get_by_key('paths', '/').ui_value, repo_name))
 
-class TestAdminReposControllerGIT(TestController, _BaseTest):
+
+class TestAdminReposControllerGIT(_BaseTestCase):
     REPO = GIT_REPO
     REPO_TYPE = 'git'
     NEW_REPO = NEW_GIT_REPO
@@ -650,7 +626,7 @@
     OTHER_TYPE = 'hg'
 
 
-class TestAdminReposControllerHG(TestController, _BaseTest):
+class TestAdminReposControllerHG(_BaseTestCase):
     REPO = HG_REPO
     REPO_TYPE = 'hg'
     NEW_REPO = NEW_HG_REPO
@@ -658,6 +634,11 @@
     OTHER_TYPE = 'git'
 
     def test_permanent_url_protocol_access(self):
-        with pytest.raises(Exception) as e:
-            self.app.get(url('summary_home', repo_name='_1'), extra_environ={'HTTP_ACCEPT': 'application/mercurial'})
-        assert 'Unable to detect pull/push action' in str(e)
+        repo = Repository.get_by_repo_name(self.REPO)
+        permanent_name = '_%d' % repo.repo_id
+
+        # 400 Bad Request - Unable to detect pull/push action
+        self.app.get(url('summary_home', repo_name=permanent_name),
+            extra_environ={'HTTP_ACCEPT': 'application/mercurial'},
+            status=400,
+        )
--- a/kallithea/tests/functional/test_admin_settings.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from kallithea.model.db import Setting, Ui
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 
 fixture = Fixture()
@@ -37,31 +37,69 @@
         self.log_user()
         response = self.app.post(url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='test_hooks_1',
-                                            new_hook_ui_value='cd /tmp',
+                                            new_hook_ui_value='cd %s' % TESTS_TMP_PATH,
+                                            _authentication_token=self.authentication_token()))
+
+        self.checkSessionFlash(response, 'Added new hook')
+        response = response.follow()
+        response.mustcontain('test_hooks_1')
+        response.mustcontain('cd %s' % TESTS_TMP_PATH)
+
+    def test_edit_custom_hook(self):
+        self.log_user()
+        response = self.app.post(url('admin_settings_hooks'),
+                                params=dict(hook_ui_key='test_hooks_1',
+                                            hook_ui_value='old_value_of_hook_1',
+                                            hook_ui_value_new='new_value_of_hook_1',
                                             _authentication_token=self.authentication_token()))
 
         response = response.follow()
         response.mustcontain('test_hooks_1')
-        response.mustcontain('cd /tmp')
+        response.mustcontain('new_value_of_hook_1')
+
+    def test_add_existing_custom_hook(self):
+        self.log_user()
+        response = self.app.post(url('admin_settings_hooks'),
+                                params=dict(new_hook_ui_key='test_hooks_1',
+                                            new_hook_ui_value='attempted_new_value',
+                                            _authentication_token=self.authentication_token()))
+
+        self.checkSessionFlash(response, 'Hook already exists')
+        response = response.follow()
+        response.mustcontain('test_hooks_1')
+        response.mustcontain('new_value_of_hook_1')
 
     def test_create_custom_hook_delete(self):
         self.log_user()
         response = self.app.post(url('admin_settings_hooks'),
                                 params=dict(new_hook_ui_key='test_hooks_2',
-                                            new_hook_ui_value='cd /tmp2',
+                                            new_hook_ui_value='cd %s2' % TESTS_TMP_PATH,
                                             _authentication_token=self.authentication_token()))
 
+        self.checkSessionFlash(response, 'Added new hook')
         response = response.follow()
         response.mustcontain('test_hooks_2')
-        response.mustcontain('cd /tmp2')
+        response.mustcontain('cd %s2' % TESTS_TMP_PATH)
 
-        hook_id = Ui.get_by_key('test_hooks_2').ui_id
+        hook_id = Ui.get_by_key('hooks', 'test_hooks_2').ui_id
         ## delete
         self.app.post(url('admin_settings_hooks'),
                         params=dict(hook_id=hook_id, _authentication_token=self.authentication_token()))
         response = self.app.get(url('admin_settings_hooks'))
         response.mustcontain(no=['test_hooks_2'])
-        response.mustcontain(no=['cd /tmp2'])
+        response.mustcontain(no=['cd %s2' % TESTS_TMP_PATH])
+
+    def test_add_existing_builtin_hook(self):
+        self.log_user()
+        response = self.app.post(url('admin_settings_hooks'),
+                                params=dict(new_hook_ui_key='changegroup.update',
+                                            new_hook_ui_value='attempted_new_value',
+                                            _authentication_token=self.authentication_token()))
+
+        self.checkSessionFlash(response, 'Builtin hooks are read-only')
+        response = response.follow()
+        response.mustcontain('changegroup.update')
+        response.mustcontain('hg update &gt;&amp;2')
 
     def test_index_search(self):
         self.log_user()
@@ -87,8 +125,7 @@
 
         self.checkSessionFlash(response, 'Updated application settings')
 
-        self.assertEqual(Setting
-                         .get_app_settings()['ga_code'], new_ga_code)
+        assert Setting.get_app_settings()['ga_code'] == new_ga_code
 
         response = response.follow()
         response.mustcontain("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code)
@@ -108,8 +145,7 @@
                                  ))
 
         self.checkSessionFlash(response, 'Updated application settings')
-        self.assertEqual(Setting
-                        .get_app_settings()['ga_code'], new_ga_code)
+        assert Setting.get_app_settings()['ga_code'] == new_ga_code
 
         response = response.follow()
         response.mustcontain(no=["_gaq.push(['_setAccount', '%s']);" % new_ga_code])
@@ -129,8 +165,7 @@
                                  ))
 
         self.checkSessionFlash(response, 'Updated application settings')
-        self.assertEqual(Setting
-                        .get_app_settings()['captcha_private_key'], '1234567890')
+        assert Setting.get_app_settings()['captcha_private_key'] == '1234567890'
 
         response = self.app.get(url('register'))
         response.mustcontain('captcha')
@@ -150,8 +185,7 @@
                                  ))
 
         self.checkSessionFlash(response, 'Updated application settings')
-        self.assertEqual(Setting
-                        .get_app_settings()['captcha_private_key'], '')
+        assert Setting.get_app_settings()['captcha_private_key'] == ''
 
         response = self.app.get(url('register'))
         response.mustcontain(no=['captcha'])
@@ -173,9 +207,7 @@
                                 ))
 
             self.checkSessionFlash(response, 'Updated application settings')
-            self.assertEqual(Setting
-                             .get_app_settings()['title'],
-                             new_title.decode('utf-8'))
+            assert Setting.get_app_settings()['title'] == new_title.decode('utf-8')
 
             response = response.follow()
-            response.mustcontain("""<div class="branding">%s</div>""" % new_title)
+            response.mustcontain("""<span class="branding">%s</span>""" % new_title)
--- a/kallithea/tests/functional/test_admin_user_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_user_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.db import UserGroup, UserGroupToPerm, Permission
 from kallithea.model.meta import Session
 
-TEST_USER_GROUP = 'admins_test'
+TEST_USER_GROUP = u'admins_test'
 
 
 class TestAdminUsersGroupsController(TestController):
@@ -18,7 +18,7 @@
         users_group_name = TEST_USER_GROUP
         response = self.app.post(url('users_groups'),
                                  {'users_group_name': users_group_name,
-                                  'user_group_description': 'DESC',
+                                  'user_group_description': u'DESC',
                                   'active': True,
                                   '_authentication_token': self.authentication_token()})
         response.follow()
@@ -32,18 +32,18 @@
         response = self.app.get(url('new_users_group'))
 
     def test_update(self):
-        response = self.app.put(url('users_group', id=1), status=403)
+        response = self.app.post(url('update_users_group', id=1), status=403)
 
     def test_update_browser_fakeout(self):
-        response = self.app.post(url('users_group', id=1),
-                                 params=dict(_method='put', _authentication_token=self.authentication_token()))
+        response = self.app.post(url('update_users_group', id=1),
+                                 params=dict(_authentication_token=self.authentication_token()))
 
     def test_delete(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another'
         response = self.app.post(url('users_groups'),
-                                 {'users_group_name':users_group_name,
-                                  'user_group_description': 'DESC',
+                                 {'users_group_name': users_group_name,
+                                  'user_group_description': u'DESC',
                                   'active': True,
                                   '_authentication_token': self.authentication_token()})
         response.follow()
@@ -51,23 +51,23 @@
         self.checkSessionFlash(response,
                                'Created user group ')
 
-        gr = Session().query(UserGroup)\
+        gr = Session().query(UserGroup) \
             .filter(UserGroup.users_group_name == users_group_name).one()
 
-        response = self.app.post(url('users_group', id=gr.users_group_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_users_group', id=gr.users_group_id),
+            params={'_authentication_token': self.authentication_token()})
 
-        gr = Session().query(UserGroup)\
+        gr = Session().query(UserGroup) \
             .filter(UserGroup.users_group_name == users_group_name).scalar()
 
-        self.assertEqual(gr, None)
+        assert gr is None
 
     def test_default_perms_enable_repository_read_on_group(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another2'
         response = self.app.post(url('users_groups'),
                                  {'users_group_name': users_group_name,
-                                  'user_group_description': 'DESC',
+                                  'user_group_description': u'DESC',
                                   'active': True,
                                   '_authentication_token': self.authentication_token()})
         response.follow()
@@ -76,8 +76,8 @@
         self.checkSessionFlash(response,
                                'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('edit_user_group_default_perms',
-                                    id=ug.users_group_id),
+        response = self.app.post(url('edit_user_group_default_perms_update',
+                                     id=ug.users_group_id),
                                  {'create_repo_perm': True,
                                   '_authentication_token': self.authentication_token()})
         response.follow()
@@ -87,18 +87,16 @@
         p3 = Permission.get_by_key('hg.fork.none')
         # check if user has this perms, they should be here since
         # defaults are on
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group == ug).all()
 
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
+        assert sorted([[x.users_group_id, x.permission_id, ] for x in perms]) == sorted([[ug.users_group_id, p.permission_id],
                     [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]]))
+                    [ug.users_group_id, p3.permission_id]])
 
         ## DISABLE REPO CREATE ON A GROUP
-        response = self.app.put(
-            url('edit_user_group_default_perms', id=ug.users_group_id),
+        response = self.app.post(
+            url('edit_user_group_default_perms_update', id=ug.users_group_id),
             params={'_authentication_token': self.authentication_token()})
 
         response.follow()
@@ -109,38 +107,36 @@
 
         # check if user has this perms, they should be here since
         # defaults are on
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group == ug).all()
 
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
+        assert sorted([[x.users_group_id, x.permission_id, ] for x in perms]) == sorted([[ug.users_group_id, p.permission_id],
                     [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]]))
+                    [ug.users_group_id, p3.permission_id]])
 
         # DELETE !
         ug = UserGroup.get_by_group_name(users_group_name)
         ugid = ug.users_group_id
-        response = self.app.post(url('users_group', id=ug.users_group_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_users_group', id=ug.users_group_id),
+            params={'_authentication_token': self.authentication_token()})
         response = response.follow()
-        gr = Session().query(UserGroup)\
+        gr = Session().query(UserGroup) \
             .filter(UserGroup.users_group_name == users_group_name).scalar()
 
-        self.assertEqual(gr, None)
+        assert gr is None
         p = Permission.get_by_key('hg.create.repository')
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group_id == ugid).all()
         perms = [[x.users_group_id,
                   x.permission_id, ] for x in perms]
-        self.assertEqual(perms, [])
+        assert perms == []
 
     def test_default_perms_enable_repository_fork_on_group(self):
         self.log_user()
         users_group_name = TEST_USER_GROUP + 'another2'
         response = self.app.post(url('users_groups'),
                                  {'users_group_name': users_group_name,
-                                  'user_group_description': 'DESC',
+                                  'user_group_description': u'DESC',
                                   'active': True,
                                   '_authentication_token': self.authentication_token()})
         response.follow()
@@ -149,9 +145,9 @@
         self.checkSessionFlash(response,
                                'Created user group ')
         ## ENABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('edit_user_group_default_perms',
-                                    id=ug.users_group_id),
-                                {'fork_repo_perm': True, '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('edit_user_group_default_perms_update',
+                                     id=ug.users_group_id),
+                                 {'fork_repo_perm': True, '_authentication_token': self.authentication_token()})
 
         response.follow()
         ug = UserGroup.get_by_group_name(users_group_name)
@@ -160,17 +156,15 @@
         p3 = Permission.get_by_key('hg.fork.repository')
         # check if user has this perms, they should be here since
         # defaults are on
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group == ug).all()
 
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
+        assert sorted([[x.users_group_id, x.permission_id, ] for x in perms]) == sorted([[ug.users_group_id, p.permission_id],
                     [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]]))
+                    [ug.users_group_id, p3.permission_id]])
 
         ## DISABLE REPO CREATE ON A GROUP
-        response = self.app.put(url('edit_user_group_default_perms', id=ug.users_group_id),
+        response = self.app.post(url('edit_user_group_default_perms_update', id=ug.users_group_id),
             params={'_authentication_token': self.authentication_token()})
 
         response.follow()
@@ -180,51 +174,31 @@
         p3 = Permission.get_by_key('hg.fork.none')
         # check if user has this perms, they should be here since
         # defaults are on
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group == ug).all()
 
-        self.assertEqual(
-            sorted([[x.users_group_id, x.permission_id, ] for x in perms]),
-            sorted([[ug.users_group_id, p.permission_id],
+        assert sorted([[x.users_group_id, x.permission_id, ] for x in perms]) == sorted([[ug.users_group_id, p.permission_id],
                     [ug.users_group_id, p2.permission_id],
-                    [ug.users_group_id, p3.permission_id]]))
+                    [ug.users_group_id, p3.permission_id]])
 
         # DELETE !
         ug = UserGroup.get_by_group_name(users_group_name)
         ugid = ug.users_group_id
-        response = self.app.post(url('users_group', id=ug.users_group_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_users_group', id=ug.users_group_id),
+            params={'_authentication_token': self.authentication_token()})
         response = response.follow()
-        gr = Session().query(UserGroup)\
+        gr = Session().query(UserGroup) \
                            .filter(UserGroup.users_group_name ==
                                    users_group_name).scalar()
 
-        self.assertEqual(gr, None)
+        assert gr is None
         p = Permission.get_by_key('hg.fork.repository')
-        perms = UserGroupToPerm.query()\
+        perms = UserGroupToPerm.query() \
             .filter(UserGroupToPerm.users_group_id == ugid).all()
         perms = [[x.users_group_id,
                   x.permission_id, ] for x in perms]
-        self.assertEqual(
-            perms,
-            []
-        )
+        assert perms == []
 
     def test_delete_browser_fakeout(self):
-        response = self.app.post(url('users_group', id=1),
-                                 params=dict(_method='delete', _authentication_token=self.authentication_token()))
-
-    def test_show(self):
-        response = self.app.get(url('users_group', id=1))
-
-    def test_edit(self):
-        response = self.app.get(url('edit_users_group', id=1))
-
-    def test_assign_members(self):
-        pass
-
-    def test_add_create_permission(self):
-        pass
-
-    def test_revoke_members(self):
-        pass
+        response = self.app.post(url('delete_users_group', id=1),
+                                 params=dict(_authentication_token=self.authentication_token()))
--- a/kallithea/tests/functional/test_admin_users.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_admin_users.py	Sun Mar 31 21:28:56 2019 +0200
@@ -12,20 +12,40 @@
 # 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 sqlalchemy.orm.exc import NoResultFound
+from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError
 
-from kallithea.tests import *
+import pytest
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
+from kallithea.controllers.admin.users import UsersController
 from kallithea.model.db import User, Permission, UserIpMap, UserApiKeys
 from kallithea.lib.auth import check_password
 from kallithea.model.user import UserModel
 from kallithea.model import validators
 from kallithea.lib import helpers as h
 from kallithea.model.meta import Session
+from webob.exc import HTTPNotFound
+
+from tg.util.webtest import test_context
 
 fixture = Fixture()
 
 
+@pytest.fixture
+def user_and_repo_group_fail():
+    username = 'repogrouperr'
+    groupname = u'repogroup_fail'
+    user = fixture.create_user(name=username)
+    repo_group = fixture.create_repo_group(name=groupname, cur_user=username)
+    yield user, repo_group
+    # cleanup
+    try:
+        fixture.destroy_repo_group(repo_group)
+    except ObjectDeletedError:
+        # delete already succeeded in test body
+        pass
+
+
 class TestAdminUsersController(TestController):
     test_user_1 = 'testme'
 
@@ -38,18 +58,18 @@
     def test_index(self):
         self.log_user()
         response = self.app.get(url('users'))
-        # Test response...
+        # TODO: Test response...
 
     def test_create(self):
         self.log_user()
         username = 'newtestuser'
         password = 'test12'
         password_confirmation = password
-        name = 'name'
-        lastname = 'lastname'
+        name = u'name'
+        lastname = u'lastname'
         email = 'mail@example.com'
 
-        response = self.app.post(url('users'),
+        response = self.app.post(url('new_user'),
             {'username': username,
              'password': password,
              'password_confirmation': password_confirmation,
@@ -60,40 +80,42 @@
              'extern_type': 'internal',
              'email': email,
              '_authentication_token': self.authentication_token()})
+        # 302 Found
+        # The resource was found at http://localhost/_admin/users/5/edit; you should be redirected automatically.
 
-        self.checkSessionFlash(response, '''Created user <a href="/_admin/users/''')
-        self.checkSessionFlash(response, '''/edit">%s</a>''' % (username))
+        self.checkSessionFlash(response, '''Created user %s''' % username)
 
-        new_user = Session().query(User).\
+        response = response.follow()
+        response.mustcontain("""%s user settings""" % username) # in <title>
+
+        new_user = Session().query(User). \
             filter(User.username == username).one()
 
-        self.assertEqual(new_user.username, username)
-        self.assertEqual(check_password(password, new_user.password), True)
-        self.assertEqual(new_user.name, name)
-        self.assertEqual(new_user.lastname, lastname)
-        self.assertEqual(new_user.email, email)
-
-        response.follow()
-        response = response.follow()
-        response.mustcontain("""newtestuser""")
+        assert new_user.username == username
+        assert check_password(password, new_user.password) == True
+        assert new_user.name == name
+        assert new_user.lastname == lastname
+        assert new_user.email == email
 
     def test_create_err(self):
         self.log_user()
         username = 'new_user'
         password = ''
-        name = 'name'
-        lastname = 'lastname'
+        name = u'name'
+        lastname = u'lastname'
         email = 'errmail.example.com'
 
-        response = self.app.post(url('users'), {'username': username,
-                                               'password': password,
-                                               'name': name,
-                                               'active': False,
-                                               'lastname': lastname,
-                                               'email': email,
-                                               '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('new_user'),
+            {'username': username,
+             'password': password,
+             'name': name,
+             'active': False,
+             'lastname': lastname,
+             'email': email,
+             '_authentication_token': self.authentication_token()})
 
-        msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
+        with test_context(self.app):
+            msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
         msg = h.html_escape(msg % {'username': 'new_user'})
         response.mustcontain("""<span class="error-message">%s</span>""" % msg)
         response.mustcontain("""<span class="error-message">Please enter a value</span>""")
@@ -102,13 +124,14 @@
         def get_user():
             Session().query(User).filter(User.username == username).one()
 
-        self.assertRaises(NoResultFound, get_user), 'found user in database'
+        with pytest.raises(NoResultFound):
+            get_user(), 'found user in database'
 
     def test_new(self):
         self.log_user()
         response = self.app.get(url('new_user'))
 
-    @parameterized.expand(
+    @parametrize('name,attrs',
         [('firstname', {'firstname': 'new_username'}),
          ('lastname', {'lastname': 'new_username'}),
          ('admin', {'admin': True}),
@@ -138,17 +161,16 @@
         if name == 'email':
             params['emails'] = [attrs['email']]
         if name == 'extern_type':
-            #cannot update this via form, expected value is original one
+            # cannot update this via form, expected value is original one
             params['extern_type'] = "internal"
         if name == 'extern_name':
-            #cannot update this via form, expected value is original one
+            # cannot update this via form, expected value is original one
             params['extern_name'] = self.test_user_1
-            # special case since this user is not
-                                          # logged in yet his data is not filled
-                                          # so we use creation data
+            # special case since this user is not logged in yet his data is
+            # not filled so we use creation data
 
         params.update({'_authentication_token': self.authentication_token()})
-        response = self.app.put(url('user', id=usr.user_id), params)
+        response = self.app.post(url('update_user', id=usr.user_id), params)
         self.checkSessionFlash(response, 'User updated successfully')
         params.pop('_authentication_token')
 
@@ -157,7 +179,7 @@
         updated_params.update({'password_confirmation': ''})
         updated_params.update({'new_password': ''})
 
-        self.assertEqual(params, updated_params)
+        assert params == updated_params
 
     def test_delete(self):
         self.log_user()
@@ -165,50 +187,47 @@
 
         fixture.create_user(name=username)
 
-        new_user = Session().query(User)\
+        new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
 
         self.checkSessionFlash(response, 'Successfully deleted user')
 
     def test_delete_repo_err(self):
         self.log_user()
         username = 'repoerr'
-        reponame = 'repoerr_fail'
+        reponame = u'repoerr_fail'
 
         fixture.create_user(name=username)
         fixture.create_repo(name=reponame, cur_user=username)
 
-        new_user = Session().query(User)\
+        new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'User "%s" still '
                                'owns 1 repositories and cannot be removed. '
                                'Switch owners or remove those repositories: '
                                '%s' % (username, reponame))
 
         response = self.app.post(url('delete_repo', repo_name=reponame),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Deleted repository %s' % reponame)
 
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
-    def test_delete_repo_group_err(self):
+    def test_delete_repo_group_err(self, user_and_repo_group_fail):
         self.log_user()
         username = 'repogrouperr'
-        groupname = 'repogroup_fail'
-
-        fixture.create_user(name=username)
-        fixture.create_repo_group(name=groupname, cur_user=username)
+        groupname = u'repogroup_fail'
 
-        new_user = Session().query(User)\
+        new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'User "%s" still '
                                'owns 1 repository groups and cannot be removed. '
                                'Switch owners or remove those repository groups: '
@@ -219,25 +238,25 @@
         # response = self.app.get(url('repos_groups', id=rg.group_id))
 
         response = self.app.post(url('delete_repo_group', group_name=groupname),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Removed repository group %s' % groupname)
 
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
     def test_delete_user_group_err(self):
         self.log_user()
         username = 'usergrouperr'
-        groupname = 'usergroup_fail'
+        groupname = u'usergroup_fail'
 
         fixture.create_user(name=username)
         ug = fixture.create_user_group(name=groupname, cur_user=username)
 
-        new_user = Session().query(User)\
+        new_user = Session().query(User) \
             .filter(User.username == username).one()
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'User "%s" still '
                                'owns 1 user groups and cannot be removed. '
                                'Switch owners or remove those user groups: '
@@ -249,13 +268,10 @@
 
         fixture.destroy_user_group(ug.users_group_id)
 
-        response = self.app.post(url('user', id=new_user.user_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_user', id=new_user.user_id),
+            params={'_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Successfully deleted user')
 
-    def test_show(self):
-        response = self.app.get(url('user', id=1))
-
     def test_edit(self):
         self.log_user()
         user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
@@ -267,27 +283,26 @@
         perm_create = Permission.get_by_key('hg.create.repository')
 
         user = UserModel().create_or_update(username='dummy', password='qwe',
-                                            email='dummy', firstname='a',
-                                            lastname='b')
+                                            email='dummy', firstname=u'a',
+                                            lastname=u'b')
         Session().commit()
         uid = user.user_id
 
         try:
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(user, perm_none), False)
-            self.assertEqual(UserModel().has_perm(user, perm_create), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(user, perm_none) == False
+            assert UserModel().has_perm(user, perm_create) == False
 
-            response = self.app.post(url('edit_user_perms', id=uid),
-                                     params=dict(_method='put',
-                                                 create_repo_perm=True,
+            response = self.app.post(url('edit_user_perms_update', id=uid),
+                                     params=dict(create_repo_perm=True,
                                                  _authentication_token=self.authentication_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
             perm_create = Permission.get_by_key('hg.create.repository')
 
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(uid, perm_none), False)
-            self.assertEqual(UserModel().has_perm(uid, perm_create), True)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(uid, perm_none) == False
+            assert UserModel().has_perm(uid, perm_create) == True
         finally:
             UserModel().delete(uid)
             Session().commit()
@@ -298,25 +313,25 @@
         perm_create = Permission.get_by_key('hg.create.repository')
 
         user = UserModel().create_or_update(username='dummy', password='qwe',
-                                            email='dummy', firstname='a',
-                                            lastname='b')
+                                            email='dummy', firstname=u'a',
+                                            lastname=u'b')
         Session().commit()
         uid = user.user_id
 
         try:
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(user, perm_none), False)
-            self.assertEqual(UserModel().has_perm(user, perm_create), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(user, perm_none) == False
+            assert UserModel().has_perm(user, perm_create) == False
 
-            response = self.app.post(url('edit_user_perms', id=uid),
-                                     params=dict(_method='put', _authentication_token=self.authentication_token()))
+            response = self.app.post(url('edit_user_perms_update', id=uid),
+                                     params=dict(_authentication_token=self.authentication_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
             perm_create = Permission.get_by_key('hg.create.repository')
 
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(uid, perm_none), True)
-            self.assertEqual(UserModel().has_perm(uid, perm_create), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(uid, perm_none) == True
+            assert UserModel().has_perm(uid, perm_create) == False
         finally:
             UserModel().delete(uid)
             Session().commit()
@@ -327,27 +342,26 @@
         perm_fork = Permission.get_by_key('hg.fork.repository')
 
         user = UserModel().create_or_update(username='dummy', password='qwe',
-                                            email='dummy', firstname='a',
-                                            lastname='b')
+                                            email='dummy', firstname=u'a',
+                                            lastname=u'b')
         Session().commit()
         uid = user.user_id
 
         try:
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(user, perm_none), False)
-            self.assertEqual(UserModel().has_perm(user, perm_fork), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(user, perm_none) == False
+            assert UserModel().has_perm(user, perm_fork) == False
 
-            response = self.app.post(url('edit_user_perms', id=uid),
-                                     params=dict(_method='put',
-                                                 create_repo_perm=True,
+            response = self.app.post(url('edit_user_perms_update', id=uid),
+                                     params=dict(create_repo_perm=True,
                                                  _authentication_token=self.authentication_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
             perm_create = Permission.get_by_key('hg.create.repository')
 
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(uid, perm_none), False)
-            self.assertEqual(UserModel().has_perm(uid, perm_create), True)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(uid, perm_none) == False
+            assert UserModel().has_perm(uid, perm_create) == True
         finally:
             UserModel().delete(uid)
             Session().commit()
@@ -358,25 +372,25 @@
         perm_fork = Permission.get_by_key('hg.fork.repository')
 
         user = UserModel().create_or_update(username='dummy', password='qwe',
-                                            email='dummy', firstname='a',
-                                            lastname='b')
+                                            email='dummy', firstname=u'a',
+                                            lastname=u'b')
         Session().commit()
         uid = user.user_id
 
         try:
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(user, perm_none), False)
-            self.assertEqual(UserModel().has_perm(user, perm_fork), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(user, perm_none) == False
+            assert UserModel().has_perm(user, perm_fork) == False
 
-            response = self.app.post(url('edit_user_perms', id=uid),
-                                     params=dict(_method='put', _authentication_token=self.authentication_token()))
+            response = self.app.post(url('edit_user_perms_update', id=uid),
+                                     params=dict(_authentication_token=self.authentication_token()))
 
             perm_none = Permission.get_by_key('hg.create.none')
             perm_create = Permission.get_by_key('hg.create.repository')
 
-            #User should have None permission on creation repository
-            self.assertEqual(UserModel().has_perm(uid, perm_none), True)
-            self.assertEqual(UserModel().has_perm(uid, perm_create), False)
+            # User should have None permission on creation repository
+            assert UserModel().has_perm(uid, perm_none) == True
+            assert UserModel().has_perm(uid, perm_create) == False
         finally:
             UserModel().delete(uid)
             Session().commit()
@@ -387,7 +401,7 @@
         response = self.app.get(url('edit_user_ips', id=user.user_id))
         response.mustcontain('All IP addresses are allowed')
 
-    @parameterized.expand([
+    @parametrize('test_name,ip,ip_range,failure', [
         ('127/24', '127.0.0.1/24', '127.0.0.0 - 127.0.0.255', False),
         ('10/32', '10.0.0.10/32', '10.0.0.10 - 10.0.0.10', False),
         ('0/16', '0.0.0.0/16', '0.0.0.0 - 0.0.255.255', False),
@@ -395,13 +409,13 @@
         ('127_bad_mask', '127.0.0.1/99', '127.0.0.1 - 127.0.0.1', True),
         ('127_bad_ip', 'foobar', 'foobar', True),
     ])
-    def test_add_ip(self, test_name, ip, ip_range, failure):
+    def test_add_ip(self, test_name, ip, ip_range, failure, auto_clear_ip_permissions):
         self.log_user()
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.put(url('edit_user_ips', id=user_id),
-                                params=dict(new_ip=ip, _authentication_token=self.authentication_token()))
+        response = self.app.post(url('edit_user_ips_update', id=user_id),
+                                 params=dict(new_ip=ip, _authentication_token=self.authentication_token()))
 
         if failure:
             self.checkSessionFlash(response, 'Please enter a valid IPv4 or IPv6 address')
@@ -414,27 +428,23 @@
             response.mustcontain(ip)
             response.mustcontain(ip_range)
 
-        ## cleanup
-        for del_ip in UserIpMap.query().filter(UserIpMap.user_id == user_id).all():
-            Session().delete(del_ip)
-            Session().commit()
-
-    def test_delete_ip(self):
+    def test_delete_ip(self, auto_clear_ip_permissions):
         self.log_user()
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
         ip = '127.0.0.1/32'
         ip_range = '127.0.0.1 - 127.0.0.1'
-        new_ip = UserModel().add_extra_ip(user_id, ip)
-        Session().commit()
+        with test_context(self.app):
+            new_ip = UserModel().add_extra_ip(user_id, ip)
+            Session().commit()
         new_ip_id = new_ip.ip_id
 
         response = self.app.get(url('edit_user_ips', id=user_id))
         response.mustcontain(ip)
         response.mustcontain(ip_range)
 
-        self.app.post(url('edit_user_ips', id=user_id),
-                      params=dict(_method='delete', del_ip_id=new_ip_id, _authentication_token=self.authentication_token()))
+        self.app.post(url('edit_user_ips_delete', id=user_id),
+                      params=dict(del_ip_id=new_ip_id, _authentication_token=self.authentication_token()))
 
         response = self.app.get(url('edit_user_ips', id=user_id))
         response.mustcontain('All IP addresses are allowed')
@@ -449,7 +459,7 @@
         response.mustcontain(user.api_key)
         response.mustcontain('Expires: Never')
 
-    @parameterized.expand([
+    @parametrize('desc,lifetime', [
         ('forever', -1),
         ('5mins', 60*5),
         ('30days', 60*60*24*30),
@@ -459,7 +469,7 @@
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_api_keys', id=user_id),
+        response = self.app.post(url('edit_user_api_keys_update', id=user_id),
                  {'description': desc, 'lifetime': lifetime, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         try:
@@ -477,20 +487,20 @@
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         user_id = user.user_id
 
-        response = self.app.post(url('edit_user_api_keys', id=user_id),
+        response = self.app.post(url('edit_user_api_keys_update', id=user_id),
                 {'description': 'desc', 'lifetime': -1, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
 
-        #now delete our key
+        # now delete our key
         keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
-        self.assertEqual(1, len(keys))
+        assert 1 == len(keys)
 
-        response = self.app.post(url('edit_user_api_keys', id=user_id),
-                 {'_method': 'delete', 'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
+                 {'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
-        self.assertEqual(0, len(keys))
+        assert 0 == len(keys)
 
     def test_reset_main_api_key(self):
         self.log_user()
@@ -501,35 +511,29 @@
         response.mustcontain(api_key)
         response.mustcontain('Expires: Never')
 
-        response = self.app.post(url('edit_user_api_keys', id=user_id),
-                 {'_method': 'delete', 'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('edit_user_api_keys_delete', id=user_id),
+                 {'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
         response.mustcontain(no=[api_key])
 
-# TODO To be uncommented when pytest is the test runner
-#import pytest
-#from kallithea.controllers.admin.users import UsersController
-#class TestAdminUsersController_unittest(object):
-#    """
-#    Unit tests for the users controller
-#    These are in a separate class, not deriving from TestController (and thus
-#    unittest.TestCase), to be able to benefit from pytest features like
-#    monkeypatch.
-#    """
-#    def test_get_user_or_raise_if_default(self, monkeypatch):
-#        # flash complains about an unexisting session
-#        def flash_mock(*args, **kwargs):
-#            pass
-#        monkeypatch.setattr(h, 'flash', flash_mock)
-#
-#        u = UsersController()
-#        # a regular user should work correctly
-#        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
-#        assert u._get_user_or_raise_if_default(user.user_id) == user
-#        # the default user should raise
-#        with pytest.raises(HTTPNotFound):
-#            u._get_user_or_raise_if_default(User.get_default_user().user_id)
+
+class TestAdminUsersController_unittest(TestController):
+    """ Unit tests for the users controller """
+
+    def test_get_user_or_raise_if_default(self, monkeypatch, test_context_fixture):
+        # flash complains about an non-existing session
+        def flash_mock(*args, **kwargs):
+            pass
+        monkeypatch.setattr(h, 'flash', flash_mock)
+
+        u = UsersController()
+        # a regular user should work correctly
+        user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        assert u._get_user_or_raise_if_default(user.user_id) == user
+        # the default user should raise
+        with pytest.raises(HTTPNotFound):
+            u._get_user_or_raise_if_default(User.get_default_user().user_id)
 
 
 class TestAdminUsersControllerForDefaultUser(TestController):
@@ -556,14 +560,14 @@
     def test_add_api_keys_default_user(self):
         self.log_user()
         user = User.get_default_user()
-        response = self.app.post(url('edit_user_api_keys', id=user.user_id),
-                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+        response = self.app.post(url('edit_user_api_keys_update', id=user.user_id),
+                 {'_authentication_token': self.authentication_token()}, status=404)
 
     def test_delete_api_keys_default_user(self):
         self.log_user()
         user = User.get_default_user()
-        response = self.app.post(url('edit_user_api_keys', id=user.user_id),
-                 {'_method': 'delete', '_authentication_token': self.authentication_token()}, status=404)
+        response = self.app.post(url('edit_user_api_keys_delete', id=user.user_id),
+                 {'_authentication_token': self.authentication_token()}, status=404)
 
     # Permissions
     def test_edit_perms_default_user(self):
@@ -574,8 +578,8 @@
     def test_update_perms_default_user(self):
         self.log_user()
         user = User.get_default_user()
-        response = self.app.post(url('edit_user_perms', id=user.user_id),
-                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+        response = self.app.post(url('edit_user_perms_update', id=user.user_id),
+                 {'_authentication_token': self.authentication_token()}, status=404)
 
     # Emails
     def test_edit_emails_default_user(self):
@@ -586,14 +590,14 @@
     def test_add_emails_default_user(self):
         self.log_user()
         user = User.get_default_user()
-        response = self.app.post(url('edit_user_emails', id=user.user_id),
-                 {'_method': 'put', '_authentication_token': self.authentication_token()}, status=404)
+        response = self.app.post(url('edit_user_emails_update', id=user.user_id),
+                 {'_authentication_token': self.authentication_token()}, status=404)
 
     def test_delete_emails_default_user(self):
         self.log_user()
         user = User.get_default_user()
-        response = self.app.post(url('edit_user_emails', id=user.user_id),
-                 {'_method': 'delete', '_authentication_token': self.authentication_token()}, status=404)
+        response = self.app.post(url('edit_user_emails_delete', id=user.user_id),
+                 {'_authentication_token': self.authentication_token()}, status=404)
 
     # IP addresses
     # Add/delete of IP addresses for the default user is used to maintain
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/functional/test_basecontroller.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,12 @@
+from kallithea.tests.base import TestController, url
+
+
+class TestBaseController(TestController):
+
+    def test_banned_http_methods(self):
+        self.app.request(url(controller='login', action='index'), method='PUT', status=405)
+        self.app.request(url(controller='login', action='index'), method='DELETE', status=405)
+
+    def test_banned_http_method_override(self):
+        self.app.get(url(controller='login', action='index'), {'_method': 'POST'}, status=405)
+        self.app.post(url(controller='login', action='index'), {'_method': 'PUT'}, status=405)
--- a/kallithea/tests/functional/test_branches.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-from kallithea.tests import *
-
-
-class TestBranchesController(TestController):
-
-    def test_index_hg(self):
-        self.log_user()
-        response = self.app.get(url(controller='branches',
-                                    action='index', repo_name=HG_REPO))
-        response.mustcontain("""<a href="/%s/changelog?branch=default">default</a>""" % HG_REPO)
-
-        # closed branches
-        response.mustcontain("""<a href="/%s/changelog?branch=git">git [closed]</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changelog?branch=web">web [closed]</a>""" % HG_REPO)
-
-    def test_index_git(self):
-        self.log_user()
-        response = self.app.get(url(controller='branches',
-                                    action='index', repo_name=GIT_REPO))
-        response.mustcontain("""<a href="/%s/changelog?branch=master">master</a>""" % GIT_REPO)
--- a/kallithea/tests/functional/test_changelog.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_changelog.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 
 class TestChangelogController(TestController):
@@ -8,22 +8,24 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     repo_name=HG_REPO))
 
-        response.mustcontain('''id="chg_20" class="container mergerow"''')
+        response.mustcontain('''id="chg_20" class="mergerow"''')
         response.mustcontain(
             """<input class="changeset_range" """
             """id="7b22a518347bb9bc19679f6af07cd0a61bfe16e7" """
             """name="7b22a518347bb9bc19679f6af07cd0a61bfe16e7" """
             """type="checkbox" value="1" />"""
         )
-        #rev 640: code garden
+        # rev 640: code garden
         response.mustcontain(
-            """<span class="changeset_hash">r640:0a4e54a44604</span>"""
+            """<a class="changeset_hash" href="/%s/changeset/0a4e54a4460401d6dbbd6a3604b17cd2b3606b82">r640:0a4e54a44604</a>""" % HG_REPO
         )
         response.mustcontain("""code garden""")
 
+        response.mustcontain("""var jsdata = ([[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 5, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 6, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 1, 6, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 7, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 8, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 9, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 10, 0], [0, 0, 2, 0], [1, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 2, 9, 0]], 0, 0, 0, 0, 0, 0], [[2, 9], [[0, 0, 2, 0], [1, 1, 10, 0], [2, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 1, 10, 0]], 0, 0, 0, 0, 0, 0], [[1, 10], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 2, 0], [1, 1, 11, 0]], 1, 0, 0, 0, 0, 0], [[2, 12], [[0, 0, 2, 0], [2, 1, 12, 0]], 1, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 13, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 13, 0]], 0, 0, 0, 0, 0, 0], [[1, 13], [[0, 0, 2, 0], [1, 1, 13, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 14, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0]], 0, 0, 0, 0, 0, 0]]);""")
+
     def test_index_pagination_hg(self):
         self.log_user()
-        #pagination
+        # pagination
         self.app.get(url(controller='changelog', action='index',
                                     repo_name=HG_REPO), {'page': 1})
         self.app.get(url(controller='changelog', action='index',
@@ -46,7 +48,7 @@
         )
 
         response.mustcontain(
-            """<span class="changeset_hash">r539:22baf968d547</span>"""
+            """<a class="changeset_hash" href="/vcs_test_hg/changeset/22baf968d547386b9516965ce89d189665003a31">r539:22baf968d547</a>"""
         )
 
     def test_index_git(self):
@@ -54,7 +56,7 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     repo_name=GIT_REPO))
 
-        response.mustcontain('''id="chg_20" class="container "''') # why no mergerow for git?
+        response.mustcontain('''id="chg_20" class=""''') # why no mergerow for git?
         response.mustcontain(
             """<input class="changeset_range" """
             """id="95f9a91d775b0084b2368ae7779e44931c849c0e" """
@@ -63,21 +65,23 @@
         )
 
         response.mustcontain(
-            """<span class="changeset_hash">r613:95f9a91d775b</span>"""
+            """<a class="changeset_hash" href="/vcs_test_git/changeset/95f9a91d775b0084b2368ae7779e44931c849c0e">r613:95f9a91d775b</a>"""
         )
 
         response.mustcontain("""fixing stupid typo in context for mercurial""")
 
+        response.mustcontain("""var jsdata = ([[[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 1, 2, 0], [0, 0, 1, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 1, 0], [1, 1, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 1], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 3, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 3, 0]], 0, 0, 0, 0, 0, 0], [[1, 3], [[0, 0, 2, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 1, 4, 0], [0, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0], [1, 0, 2, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[1, 4], [[0, 0, 2, 0], [1, 1, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 2], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 5, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 1, 5, 0]], 0, 0, 0, 0, 0, 0], [[1, 5], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 6, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 1, 6, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 6, 0]], 0, 0, 0, 0, 0, 0], [[1, 6], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 7, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 7, 0]], 0, 0, 0, 0, 0, 0], [[1, 7], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 8, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 8, 0]], 0, 0, 0, 0, 0, 0], [[1, 8], [[0, 0, 4, 0], [1, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 1, 9, 0], [0, 0, 4, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[1, 9], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 4, 0], [1, 1, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 4], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 10, 0], [0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 2, 10, 0]], 0, 0, 0, 0, 0, 0], [[2, 10], [[0, 0, 9, 0], [1, 1, 11, 0], [2, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 1, 11, 0], [0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0], [1, 1, 11, 0]], 0, 0, 0, 0, 0, 0], [[1, 11], [[0, 0, 9, 0], [1, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0], [[0, 9], [[0, 0, 9, 0]], 0, 0, 0, 0, 0, 0]]);""")
+
 #        response.mustcontain(
 #            """<div id="changed_total_5e204e7583b9c8e7b93a020bd036564b1e731dae" """
-#            """style="float:right;" class="changed_total tooltip" """
+#            """style="float:right;" class="changed_total" data-toggle="tooltip" """
 #            """title="Affected number of files, click to show """
 #            """more details">3</div>"""
 #        )
 
     def test_index_pagination_git(self):
         self.log_user()
-        #pagination
+        # pagination
         self.app.get(url(controller='changelog', action='index',
                                     repo_name=GIT_REPO), {'page': 1})
         self.app.get(url(controller='changelog', action='index',
@@ -100,7 +104,7 @@
         )
 
         response.mustcontain(
-            """<span class="changeset_hash">r515:636ed213f2f1</span>"""
+            """<a class="changeset_hash" href="/vcs_test_git/changeset/636ed213f2f11ef91071b9c24f2d5e6bd01a6ed5">r515:636ed213f2f1</a>"""
         )
 
     def test_index_hg_with_filenode(self):
@@ -108,7 +112,7 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/vcs/exceptions.py',
                                     repo_name=HG_REPO))
-        #history commits messages
+        # history commits messages
         response.mustcontain('Added exceptions module, this time for real')
         response.mustcontain('Added not implemented hg backend test case')
         response.mustcontain('Added BaseChangeset class')
@@ -119,7 +123,7 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/vcs/exceptions.py',
                                     repo_name=GIT_REPO))
-        #history commits messages
+        # history commits messages
         response.mustcontain('Added exceptions module, this time for real')
         response.mustcontain('Added not implemented hg backend test case')
         response.mustcontain('Added BaseChangeset class')
@@ -129,25 +133,25 @@
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/tests',
                                     repo_name=HG_REPO))
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
 
     def test_index_git_with_filenode_that_is_dirnode(self):
         self.log_user()
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/tests',
                                     repo_name=GIT_REPO))
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
 
     def test_index_hg_with_filenode_not_existing(self):
         self.log_user()
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/wrong_path',
                                     repo_name=HG_REPO))
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
 
     def test_index_git_with_filenode_not_existing(self):
         self.log_user()
         response = self.app.get(url(controller='changelog', action='index',
                                     revision='tip', f_path='/wrong_path',
                                     repo_name=GIT_REPO))
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
--- a/kallithea/tests/functional/test_changeset.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_changeset.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 class TestChangesetController(TestController):
 
--- a/kallithea/tests/functional/test_changeset_comments.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-from kallithea.tests import *
-from kallithea.model.db import ChangesetComment, Notification, \
-    UserNotification
-from kallithea.model.meta import Session
-
-
-class TestChangeSetCommentsController(TestController):
-
-    def setUp(self):
-        for x in ChangesetComment.query().all():
-            Session().delete(x)
-        Session().commit()
-
-        self.remove_all_notifications()
-
-    def test_create(self):
-        self.log_user()
-        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'CommentOnRevision'
-
-        params = {'text': text, '_authentication_token': self.authentication_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
-                                     params=params)
-        # Test response...
-        self.assertEqual(response.status, '302 Found')
-        response.follow()
-
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
-        # test DB
-        self.assertEqual(ChangesetComment.query().count(), 1)
-        response.mustcontain(
-            '''<div class="comments-number">'''
-            ''' 1 comment (0 inline, 1 general)'''
-        )
-
-        self.assertEqual(Notification.query().count(), 1)
-        self.assertEqual(ChangesetComment.query().count(), 1)
-
-        notification = Notification.query().all()[0]
-
-        ID = ChangesetComment.query().first().comment_id
-        self.assertEqual(notification.type_,
-                         Notification.TYPE_CHANGESET_COMMENT)
-        sbj = (u'/%s/changeset/'
-               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % (HG_REPO, ID))
-        print "%s vs %s" % (sbj, notification.subject)
-        self.assertTrue(sbj in notification.subject)
-
-    def test_create_inline(self):
-        self.log_user()
-        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'CommentOnRevision'
-        f_path = 'vcs/web/simplevcs/views/repository.py'
-        line = 'n1'
-
-        params = {'text': text, 'f_path': f_path, 'line': line, '_authentication_token': self.authentication_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
-                                     params=params)
-        # Test response...
-        self.assertEqual(response.status, '302 Found')
-        response.follow()
-
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
-        #test DB
-        self.assertEqual(ChangesetComment.query().count(), 1)
-        response.mustcontain(
-            '''<div class="comments-number">'''
-            ''' 1 comment (1 inline, 0 general)'''
-        )
-        response.mustcontain(
-            '''<div class="comments-list-chunk" '''
-            '''data-f_path="vcs/web/simplevcs/views/repository.py" '''
-            '''data-line_no="n1" data-target-id="vcswebsimplevcsviewsrepositorypy_n1">'''
-        )
-
-        self.assertEqual(Notification.query().count(), 1)
-        self.assertEqual(ChangesetComment.query().count(), 1)
-
-        notification = Notification.query().all()[0]
-        ID = ChangesetComment.query().first().comment_id
-        self.assertEqual(notification.type_,
-                         Notification.TYPE_CHANGESET_COMMENT)
-        sbj = (u'/%s/changeset/'
-               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % (HG_REPO, ID))
-        print "%s vs %s" % (sbj, notification.subject)
-        self.assertTrue(sbj in notification.subject)
-
-    def test_create_with_mention(self):
-        self.log_user()
-
-        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
-
-        params = {'text': text, '_authentication_token': self.authentication_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
-                                     params=params)
-        # Test response...
-        self.assertEqual(response.status, '302 Found')
-        response.follow()
-
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
-        # test DB
-        self.assertEqual(ChangesetComment.query().count(), 1)
-        response.mustcontain(
-            '''<div class="comments-number">'''
-            ''' 1 comment (0 inline, 1 general)'''
-        )
-
-        self.assertEqual(Notification.query().count(), 2)
-        users = [x.user.username for x in UserNotification.query().all()]
-
-        # test_regular gets notification by @mention
-        self.assertEqual(sorted(users), [TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN])
-
-    def test_delete(self):
-        self.log_user()
-        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
-        text = u'CommentOnRevision'
-
-        params = {'text': text, '_authentication_token': self.authentication_token()}
-        response = self.app.post(url(controller='changeset', action='comment',
-                                     repo_name=HG_REPO, revision=rev),
-                                     params=params)
-
-        comments = ChangesetComment.query().all()
-        self.assertEqual(len(comments), 1)
-        comment_id = comments[0].comment_id
-
-        self.app.post(url(controller='changeset',
-                                    action='delete_comment',
-                                    repo_name=HG_REPO,
-                                    comment_id=comment_id),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
-
-        comments = ChangesetComment.query().all()
-        self.assertEqual(len(comments), 0)
-
-        response = self.app.get(url(controller='changeset', action='index',
-                                repo_name=HG_REPO, revision=rev))
-        response.mustcontain(
-            '''<div class="comments-number">'''
-            ''' 0 comments (0 inline, 0 general)'''
-        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/functional/test_changeset_pullrequests_comments.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,377 @@
+import re
+
+from kallithea.tests.base import *
+from kallithea.model.changeset_status import ChangesetStatusModel
+from kallithea.model.db import ChangesetComment, PullRequest
+from kallithea.model.meta import Session
+
+
+class TestChangeSetCommentsController(TestController):
+
+    def setup_method(self, method):
+        for x in ChangesetComment.query().all():
+            Session().delete(x)
+        Session().commit()
+
+    def test_create(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'general comment on changeset'
+
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 1 comment (0 inline, 1 general)'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 1
+
+    def test_create_inline(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'inline comment on changeset'
+        f_path = 'vcs/web/simplevcs/views/repository.py'
+        line = 'n1'
+
+        params = {'text': text, 'f_path': f_path, 'line': line, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 1 comment (1 inline, 0 general)'''
+        )
+        response.mustcontain(
+            '''<div class="comments-list-chunk" '''
+            '''data-f_path="vcs/web/simplevcs/views/repository.py" '''
+            '''data-line_no="n1" data-target-id="vcswebsimplevcsviewsrepositorypy_n1">'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 1
+
+    def test_create_with_mention(self):
+        self.log_user()
+
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
+
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 1 comment (0 inline, 1 general)'''
+        )
+        response.mustcontain('<b>@%s</b> check CommentOnRevision' % TEST_USER_REGULAR_LOGIN)
+
+        # test DB
+        assert ChangesetComment.query().count() == 1
+
+    def test_create_status_change(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'general comment on changeset'
+
+        params = {'text': text, 'changeset_status': 'rejected',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 1 comment (0 inline, 1 general)'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 1
+
+        # check status
+        status = ChangesetStatusModel().get_status(repo=HG_REPO, revision=rev)
+        assert status == 'rejected'
+
+    def test_delete(self):
+        self.log_user()
+        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
+        text = u'general comment on changeset to be deleted'
+
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='changeset', action='comment',
+                                     repo_name=HG_REPO, revision=rev),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+
+        comments = ChangesetComment.query().all()
+        assert len(comments) == 1
+        comment_id = comments[0].comment_id
+
+        self.app.post(url("changeset_comment_delete",
+                                    repo_name=HG_REPO,
+                                    comment_id=comment_id),
+            params={'_authentication_token': self.authentication_token()})
+
+        comments = ChangesetComment.query().all()
+        assert len(comments) == 0
+
+        response = self.app.get(url(controller='changeset', action='index',
+                                repo_name=HG_REPO, revision=rev))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 0 comments (0 inline, 0 general)'''
+        )
+        response.mustcontain(no=text)
+
+
+class TestPullrequestsCommentsController(TestController):
+
+    def setup_method(self, method):
+        for x in ChangesetComment.query().all():
+            Session().delete(x)
+        Session().commit()
+
+    def _create_pr(self):
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {'org_repo': HG_REPO,
+                                  'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 },
+                                 status=302)
+        pr_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        return pr_id
+
+    def test_create(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'general comment on pullrequest'
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        # PRs currently always have an initial 'Under Review' status change
+        # that counts as a general comment, hence '2' in the test below. That
+        # could be counted as a misfeature, to be reworked later.
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 2 comments (0 inline, 2 general)'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 2
+
+    def test_create_inline(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'inline comment on changeset'
+        f_path = 'vcs/web/simplevcs/views/repository.py'
+        line = 'n1'
+        params = {'text': text, 'f_path': f_path, 'line': line, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 2 comments (1 inline, 1 general)'''
+        )
+        response.mustcontain(
+            '''<div class="comments-list-chunk" '''
+            '''data-f_path="vcs/web/simplevcs/views/repository.py" '''
+            '''data-line_no="n1" data-target-id="vcswebsimplevcsviewsrepositorypy_n1">'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 2
+
+    def test_create_with_mention(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'@%s check CommentOnRevision' % TEST_USER_REGULAR_LOGIN
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 2 comments (0 inline, 2 general)'''
+        )
+        response.mustcontain('<b>@%s</b> check CommentOnRevision' % TEST_USER_REGULAR_LOGIN)
+
+        # test DB
+        assert ChangesetComment.query().count() == 2
+
+    def test_create_status_change(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'general comment on pullrequest'
+        params = {'text': text, 'changeset_status': 'rejected',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        # PRs currently always have an initial 'Under Review' status change
+        # that counts as a general comment, hence '2' in the test below. That
+        # could be counted as a misfeature, to be reworked later.
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 2 comments (0 inline, 2 general)'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert ChangesetComment.query().count() == 2
+
+        # check status
+        status = ChangesetStatusModel().get_status(repo=HG_REPO, pull_request=pr_id)
+        assert status == 'rejected'
+
+    def test_delete(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'general comment on changeset to be deleted'
+        params = {'text': text, '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+
+        comments = ChangesetComment.query().all()
+        assert len(comments) == 2
+        comment_id = comments[-1].comment_id
+
+        self.app.post(url("pullrequest_comment_delete",
+                                    repo_name=HG_REPO,
+                                    comment_id=comment_id),
+            params={'_authentication_token': self.authentication_token()})
+
+        comments = ChangesetComment.query().all()
+        assert len(comments) == 1
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response.mustcontain(
+            '''<div class="comments-number">'''
+            ''' 1 comment (0 inline, 1 general)'''
+        )
+        response.mustcontain(no=text)
+
+    def test_close_pr(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'general comment on pullrequest'
+        params = {'text': text, 'save_close': 'close',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''))
+        response.mustcontain(
+            '''title (Closed)'''
+        )
+        response.mustcontain(text)
+
+        # test DB
+        assert PullRequest.get(pr_id).status == PullRequest.STATUS_CLOSED
+
+    def test_delete_pr(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        text = u'general comment on pullrequest'
+        params = {'text': text, 'save_delete': 'delete',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        # Test response...
+        assert response.status == '200 OK'
+
+        response = self.app.get(url(controller='pullrequests', action='show',
+                                repo_name=HG_REPO, pull_request_id=pr_id, extra=''), status=404)
+
+        # test DB
+        assert PullRequest.get(pr_id) is None
+
+    def test_delete_closed_pr(self):
+        self.log_user()
+        pr_id = self._create_pr()
+
+        # first close
+        text = u'general comment on pullrequest'
+        params = {'text': text, 'save_close': 'close',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
+        assert response.status == '200 OK'
+
+        # attempt delete, should fail
+        params = {'text': text, 'save_delete': 'delete',
+                '_authentication_token': self.authentication_token()}
+        response = self.app.post(url(controller='pullrequests', action='comment',
+                                     repo_name=HG_REPO, pull_request_id=pr_id),
+                                     params=params, extra_environ={'HTTP_X_PARTIAL_XHR': '1'}, status=403)
+
+        # verify that PR still exists, in closed state
+        assert PullRequest.get(pr_id).status == PullRequest.STATUS_CLOSED
--- a/kallithea/tests/functional/test_compare.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_compare.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,22 +1,23 @@
 # -*- coding: utf-8 -*-
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.repo import RepoModel
 from kallithea.model.meta import Session
 from kallithea.tests.fixture import Fixture
 
 fixture = Fixture()
 
-def _commit_div(sha, msg):
-    return """<div id="C-%s" class="message">%s</div>""" % (sha, msg)
+
+def _commit_ref(repo_name, sha, msg):
+    return '''<div class="message-firstline"><a class="message-link" href="/%s/changeset/%s">%s</a></div>''' % (repo_name, sha, msg)
 
 
 class TestCompareController(TestController):
 
-    def setUp(self):
+    def setup_method(self, method):
         self.r1_id = None
         self.r2_id = None
 
-    def tearDown(self):
+    def teardown_method(self, method):
         if self.r2_id:
             RepoModel().delete(self.r2_id)
         if self.r1_id:
@@ -26,20 +27,20 @@
 
     def test_compare_forks_on_branch_extra_commits_hg(self):
         self.log_user()
-        repo1 = fixture.create_repo('one', repo_type='hg',
+        repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
 
-        #fork this repo
-        repo2 = fixture.create_fork('one', 'one-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'one', u'one-fork')
         self.r2_id = repo2.repo_id
 
-        #add two extra commit into fork
+        # add two extra commit into fork
         cs1 = fixture.commit_change(repo2.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='hg',
                 parent=cs0)
@@ -65,32 +66,32 @@
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
-        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs2.raw_id, 'commit3'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
         response.mustcontain("""<a href="#C--826e8142e6ba">file1</a>""")
-        #swap
-        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        # swap
+        response.mustcontain("""<a class="btn btn-default btn-sm" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i>Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_forks_on_branch_extra_commits_git(self):
         self.log_user()
-        repo1 = fixture.create_repo('one-git', repo_type='git',
+        repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='git',
                 parent=None, newfile=True)
 
-        #fork this repo
-        repo2 = fixture.create_fork('one-git', 'one-git-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'one-git', u'one-git-fork')
         self.r2_id = repo2.repo_id
 
-        #add two extra commit into fork
+        # add two extra commit into fork
         cs1 = fixture.commit_change(repo2.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='git',
                 parent=cs0)
@@ -116,40 +117,40 @@
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
-        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs2.raw_id, 'commit3'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
         response.mustcontain("""<a href="#C--826e8142e6ba">file1</a>""")
-        #swap
-        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        # swap
+        response.mustcontain("""<a class="btn btn-default btn-sm" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i>Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
-    def test_compare_forks_on_branch_extra_commits_origin_has_incomming_hg(self):
+    def test_compare_forks_on_branch_extra_commits_origin_has_incoming_hg(self):
         self.log_user()
 
-        repo1 = fixture.create_repo('one', repo_type='hg',
+        repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
 
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
 
-        #fork this repo
-        repo2 = fixture.create_fork('one', 'one-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'one', u'one-fork')
         self.r2_id = repo2.repo_id
 
-        #now commit something to origin repo
+        # now commit something to origin repo
         cs1_prim = fixture.commit_change(repo1.repo_name, filename='file2',
                 content='line1file2\n', message='commit2', vcs_type='hg',
                 parent=cs0, newfile=True)
 
-        #add two extra commit into fork
+        # add two extra commit into fork
         cs1 = fixture.commit_change(repo2.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='hg',
                 parent=cs0)
@@ -175,40 +176,40 @@
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
-        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs2.raw_id, 'commit3'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
         response.mustcontain("""<a href="#C--826e8142e6ba">file1</a>""")
-        #swap
-        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        # swap
+        response.mustcontain("""<a class="btn btn-default btn-sm" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i>Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
-    def test_compare_forks_on_branch_extra_commits_origin_has_incomming_git(self):
+    def test_compare_forks_on_branch_extra_commits_origin_has_incoming_git(self):
         self.log_user()
 
-        repo1 = fixture.create_repo('one-git', repo_type='git',
+        repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
 
         self.r1_id = repo1.repo_id
 
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='git',
                 parent=None, newfile=True)
 
-        #fork this repo
-        repo2 = fixture.create_fork('one-git', 'one-git-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'one-git', u'one-git-fork')
         self.r2_id = repo2.repo_id
 
-        #now commit something to origin repo
+        # now commit something to origin repo
         cs1_prim = fixture.commit_change(repo1.repo_name, filename='file2',
                 content='line1file2\n', message='commit2', vcs_type='git',
                 parent=cs0, newfile=True)
 
-        #add two extra commit into fork
+        # add two extra commit into fork
         cs1 = fixture.commit_change(repo2.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='git',
                 parent=cs0)
@@ -234,18 +235,18 @@
         response.mustcontain("""Showing 2 commits""")
         response.mustcontain("""1 file changed with 2 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs1.raw_id, 'commit2'))
-        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs1.raw_id, 'commit2'))
+        response.mustcontain(_commit_ref(repo2.repo_name, cs2.raw_id, 'commit3'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r1:%s</a>""" % (repo2.repo_name, cs1.raw_id, cs1.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r2:%s</a>""" % (repo2.repo_name, cs2.raw_id, cs2.short_id))
         ## files
         response.mustcontain("""<a href="#C--826e8142e6ba">file1</a>""")
-        #swap
-        response.mustcontain("""<a class="btn btn-small" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i> Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
+        # swap
+        response.mustcontain("""<a class="btn btn-default btn-sm" href="/%s/compare/branch@%s...branch@%s?other_repo=%s&amp;merge=True"><i class="icon-arrows-cw"></i>Swap</a>""" % (repo2.repo_name, rev1, rev2, repo1.repo_name))
 
     def test_compare_cherry_pick_changesets_from_bottom(self):
-
+        pass
 #        repo1:
 #            cs0:
 #            cs1:
@@ -256,25 +257,25 @@
 #            cs3: x
 #            cs4: x
 #            cs5:
-        #make repo1, and cs1+cs2
+        # make repo1, and cs1+cs2
         self.log_user()
 
-        repo1 = fixture.create_repo('repo1', repo_type='hg',
+        repo1 = fixture.create_repo(u'repo1', repo_type='hg',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
 
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
         cs1 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='hg',
                 parent=cs0)
-        #fork this repo
-        repo2 = fixture.create_fork('repo1', 'repo1-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'repo1', u'repo1-fork')
         self.r2_id = repo2.repo_id
-        #now make cs3-6
+        # now make cs3-6
         cs2 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\nline2\nline3\n', message='commit3',
                 vcs_type='hg', parent=cs1)
@@ -302,17 +303,18 @@
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs2.raw_id, 'commit3'))
-        response.mustcontain(_commit_div(cs3.raw_id, 'commit4'))
-        response.mustcontain(_commit_div(cs4.raw_id, 'commit5'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs2.raw_id, 'commit3'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs3.raw_id, 'commit4'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs4.raw_id, 'commit5'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r2:%s</a>""" % (repo1.repo_name, cs2.raw_id, cs2.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
         ## files
         response.mustcontain("""#C--826e8142e6ba">file1</a>""")
 
     def test_compare_cherry_pick_changesets_from_top(self):
+        pass
 #        repo1:
 #            cs0:
 #            cs1:
@@ -324,24 +326,24 @@
 #            cs4: x
 #            cs5: x
 #
-        #make repo1, and cs1+cs2
+        # make repo1, and cs1+cs2
         self.log_user()
-        repo1 = fixture.create_repo('repo1', repo_type='hg',
+        repo1 = fixture.create_repo(u'repo1', repo_type='hg',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         self.r1_id = repo1.repo_id
 
-        #commit something !
+        # commit something !
         cs0 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
         cs1 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\nline2\n', message='commit2', vcs_type='hg',
                 parent=cs0)
-        #fork this repo
-        repo2 = fixture.create_fork('repo1', 'repo1-fork')
+        # fork this repo
+        repo2 = fixture.create_fork(u'repo1', u'repo1-fork')
         self.r2_id = repo2.repo_id
-        #now make cs3-6
+        # now make cs3-6
         cs2 = fixture.commit_change(repo1.repo_name, filename='file1',
                 content='line1\nline2\nline3\n', message='commit3',
                 vcs_type='hg', parent=cs1)
@@ -368,18 +370,18 @@
         response.mustcontain("""Showing 3 commits""")
         response.mustcontain("""1 file changed with 3 insertions and 0 deletions""")
 
-        response.mustcontain(_commit_div(cs3.raw_id, 'commit4'))
-        response.mustcontain(_commit_div(cs4.raw_id, 'commit5'))
-        response.mustcontain(_commit_div(cs5.raw_id, 'commit6'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs3.raw_id, 'commit4'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs4.raw_id, 'commit5'))
+        response.mustcontain(_commit_ref(repo1.repo_name, cs5.raw_id, 'commit6'))
 
-        response.mustcontain("""<a href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
-        response.mustcontain("""<a href="/%s/changeset/%s">r5:%s</a>""" % (repo1.repo_name, cs5.raw_id, cs5.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r3:%s</a>""" % (repo1.repo_name, cs3.raw_id, cs3.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r4:%s</a>""" % (repo1.repo_name, cs4.raw_id, cs4.short_id))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/%s">r5:%s</a>""" % (repo1.repo_name, cs5.raw_id, cs5.short_id))
         ## files
         response.mustcontain("""#C--826e8142e6ba">file1</a>""")
 
     def test_compare_cherry_pick_changeset_mixed_branches(self):
-        #TODO: write this
+        # TODO: write this
         assert 1
 
     def test_compare_remote_branches_hg(self):
@@ -403,9 +405,9 @@
         response.mustcontain('%s@%s' % (HG_FORK, rev2))
         ## outgoing changesets between those revisions
 
-        response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
-        response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
-        response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_FORK, rev2))
 
         ## files
         response.mustcontain("""<a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>""")
@@ -433,9 +435,9 @@
         response.mustcontain('%s@%s' % (GIT_FORK, rev2))
         ## outgoing changesets between those revisions
 
-        response.mustcontain("""<a href="/%s/changeset/49d3fd156b6f7db46313fac355dca1a0b94a0017">r4:49d3fd156b6f</a>""" % (GIT_FORK))
-        response.mustcontain("""<a href="/%s/changeset/2d1028c054665b962fa3d307adfc923ddd528038">r5:2d1028c05466</a>""" % (GIT_FORK))
-        response.mustcontain("""<a href="/%s/changeset/d7e0d30fbcae12c90680eb095a4f5f02505ce501">r6:%s</a>""" % (GIT_FORK, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/49d3fd156b6f7db46313fac355dca1a0b94a0017">r4:49d3fd156b6f</a>""" % (GIT_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/2d1028c054665b962fa3d307adfc923ddd528038">r5:2d1028c05466</a>""" % (GIT_FORK))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/d7e0d30fbcae12c90680eb095a4f5f02505ce501">r6:%s</a>""" % (GIT_FORK, rev2[:12]))
 
         ## files
         response.mustcontain("""<a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>""")
@@ -445,7 +447,7 @@
     def test_org_repo_new_commits_after_forking_simple_diff_hg(self):
         self.log_user()
 
-        repo1 = fixture.create_repo('one', repo_type='hg',
+        repo1 = fixture.create_repo(u'one', repo_type='hg',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
 
@@ -455,12 +457,12 @@
         cs0 = fixture.commit_change(repo=r1_name, filename='file1',
                 content='line1', message='commit1', vcs_type='hg', newfile=True)
         Session().commit()
-        self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
-        #fork the repo1
-        repo2 = fixture.create_fork(r1_name, 'one-fork',
+        assert repo1.scm_instance.revisions == [cs0.raw_id]
+        # fork the repo1
+        repo2 = fixture.create_fork(r1_name, u'one-fork',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
-        self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
+        assert repo2.scm_instance.revisions == [cs0.raw_id]
         self.r2_id = repo2.repo_id
         r2_name = repo2.repo_name
 
@@ -475,37 +477,34 @@
         cs3 = fixture.commit_change(repo=r2_name, filename='file3-fork',
                 content='file3-line1-from-fork', message='commit3-fork',
                 vcs_type='hg', parent=cs2, newfile=True)
-        #compare !
+        # compare !
         rev1 = 'default'
         rev2 = 'default'
 
         response = self.app.get(url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
-                                    org_ref_name=rev1,
+                                    org_ref_name=rev2,
                                     other_ref_type="branch",
-                                    other_ref_name=rev2,
+                                    other_ref_name=rev1,
                                     other_repo=r1_name,
-                                    merge='1',))
+                                    merge='1',), status=404)
 
-        response.mustcontain('%s@%s' % (r2_name, rev1))
-        response.mustcontain('%s@%s' % (r1_name, rev2))
-        response.mustcontain('No files')
-        response.mustcontain('No changesets')
+        response.mustcontain('Cannot show empty diff')
 
         cs0 = fixture.commit_change(repo=r1_name, filename='file2',
                 content='line1-added-after-fork', message='commit2-parent',
                 vcs_type='hg', parent=None, newfile=True)
 
-        #compare !
+        # compare !
         rev1 = 'default'
         rev2 = 'default'
         response = self.app.get(url('compare_url',
                                     repo_name=r2_name,
                                     org_ref_type="branch",
-                                    org_ref_name=rev1,
+                                    org_ref_name=rev2,
                                     other_ref_type="branch",
-                                    other_ref_name=rev2,
+                                    other_ref_name=rev1,
                                     other_repo=r1_name,
                                     merge='1',
                                     ))
@@ -520,7 +519,7 @@
     def test_org_repo_new_commits_after_forking_simple_diff_git(self):
         self.log_user()
 
-        repo1 = fixture.create_repo('one-git', repo_type='git',
+        repo1 = fixture.create_repo(u'one-git', repo_type='git',
                                     repo_description='diff-test',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
 
@@ -531,12 +530,12 @@
                 content='line1', message='commit1', vcs_type='git',
                 newfile=True)
         Session().commit()
-        self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
-        #fork the repo1
-        repo2 = fixture.create_fork(r1_name, 'one-git-fork',
+        assert repo1.scm_instance.revisions == [cs0.raw_id]
+        # fork the repo1
+        repo2 = fixture.create_fork(r1_name, u'one-git-fork',
                                     cur_user=TEST_USER_ADMIN_LOGIN)
         Session().commit()
-        self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
+        assert repo2.scm_instance.revisions == [cs0.raw_id]
         self.r2_id = repo2.repo_id
         r2_name = repo2.repo_name
 
@@ -552,7 +551,7 @@
         cs3 = fixture.commit_change(repo=r2_name, filename='file3-fork',
                 content='file3-line1-from-fork', message='commit3-fork',
                 vcs_type='git', parent=cs2, newfile=True)
-        #compare !
+        # compare !
         rev1 = 'master'
         rev2 = 'master'
 
@@ -563,18 +562,15 @@
                                     other_ref_type="branch",
                                     other_ref_name=rev2,
                                     other_repo=r1_name,
-                                    merge='1',))
+                                    merge='1',), status=404)
 
-        response.mustcontain('%s@%s' % (r2_name, rev1))
-        response.mustcontain('%s@%s' % (r1_name, rev2))
-        response.mustcontain('No files')
-        response.mustcontain('No changesets')
+        response.mustcontain('Cannot show empty diff')
 
         cs0 = fixture.commit_change(repo=r1_name, filename='file2',
                 content='line1-added-after-fork', message='commit2-parent',
                 vcs_type='git', parent=None, newfile=True)
 
-        #compare !
+        # compare !
         rev1 = 'master'
         rev2 = 'master'
         response = self.app.get(url('compare_url',
--- a/kallithea/tests/functional/test_compare_local.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_compare_local.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 class TestCompareController(TestController):
 
@@ -18,50 +18,62 @@
         response.mustcontain('%s@%s' % (HG_REPO, tag2))
 
         ## outgoing changesets between tags
-        response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
-        response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
 
         response.mustcontain('11 files changed with 94 insertions and 64 deletions')
 
         ## files diff
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-A"></i>
-                             <a href="#C--1c5cf9e91c12">docs/api/utils/index.rst</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-A"></i>
-                             <a href="#C--e3305437df55">test_and_report.sh</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--c8e92ef85cd1">.hgignore</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--6e08b694d687">.hgtags</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--2c14b00f3393">docs/api/index.rst</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--430ccbc82bdf">vcs/__init__.py</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--ebb592c595c0">vcs/utils/__init__.py</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--7abc741b5052">vcs/utils/annotate.py</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--2ef0ef106c56">vcs/utils/diffs.py</a>''')
-        response.mustcontain('''<div class="node">
-                             <i class="icon-diff-M"></i>
-                             <a href="#C--3150cb87d4b7">vcs/utils/lazy.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-added"></i>
+                          <a href="#C--1c5cf9e91c12">docs/api/utils/index.rst</a>
+                      </span>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-added"></i>
+                          <a href="#C--e3305437df55">test_and_report.sh</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--c8e92ef85cd1">.hgignore</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--6e08b694d687">.hgtags</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--2c14b00f3393">docs/api/index.rst</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--430ccbc82bdf">vcs/__init__.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--9c390eb52cd6">vcs/backends/hg.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--ebb592c595c0">vcs/utils/__init__.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--7abc741b5052">vcs/utils/annotate.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--2ef0ef106c56">vcs/utils/diffs.py</a>''')
+        response.mustcontain(
+                   '''<span class="node">
+                          <i class="icon-diff-modified"></i>
+                          <a href="#C--3150cb87d4b7">vcs/utils/lazy.py</a>''')
 
     def test_compare_tag_git(self):
         self.log_user()
@@ -78,17 +90,17 @@
         response.mustcontain('%s@%s' % (GIT_REPO, tag2))
 
         ## outgoing changesets between tags
-        response.mustcontain('''<a href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
-        response.mustcontain('''<a href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/794bbdd31545c199f74912709ea350dedcd189a2">r113:794bbdd31545</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/e36d8c5025329bdd4212bd53d4ed8a70ff44985f">r115:e36d8c502532</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5c9ff4f6d7508db0e72b1d2991c357d0d8e07af2">r116:5c9ff4f6d750</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/b7187fa2b8c1d773ec35e9dee12f01f74808c879">r117:b7187fa2b8c1</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5f3b74262014a8de2dc7dade1152de9fd0c8efef">r118:5f3b74262014</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/17438a11f72b93f56d0e08e7d1fa79a378578a82">r119:17438a11f72b</a>''' % GIT_REPO)
+        response.mustcontain('''<a class="changeset_hash" href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">r120:5a3a8fb00555</a>''' % GIT_REPO)
 
         response.mustcontain('11 files changed with 94 insertions and 64 deletions')
 
-        #files
+        # files
         response.mustcontain('''<a href="#C--1c5cf9e91c12">docs/api/utils/index.rst</a>''')
         response.mustcontain('''<a href="#C--e3305437df55">test_and_report.sh</a>''')
         response.mustcontain('''<a href="#C--c8e92ef85cd1">.hgignore</a>''')
@@ -114,8 +126,8 @@
         response.mustcontain('%s@default' % (HG_REPO))
         response.mustcontain('%s@default' % (HG_REPO))
         # branch are equal
-        response.mustcontain('<span class="empty_data">No files</span>')
-        response.mustcontain('<span class="empty_data">No changesets</span>')
+        response.mustcontain('<span class="text-muted">No files</span>')
+        response.mustcontain('<span class="text-muted">No changesets</span>')
 
     def test_index_branch_git(self):
         self.log_user()
@@ -130,8 +142,8 @@
         response.mustcontain('%s@master' % (GIT_REPO))
         response.mustcontain('%s@master' % (GIT_REPO))
         # branch are equal
-        response.mustcontain('<span class="empty_data">No files</span>')
-        response.mustcontain('<span class="empty_data">No changesets</span>')
+        response.mustcontain('<span class="text-muted">No files</span>')
+        response.mustcontain('<span class="text-muted">No changesets</span>')
 
     def test_compare_revisions_hg(self):
         self.log_user()
@@ -149,7 +161,7 @@
         response.mustcontain('%s@%s' % (HG_REPO, rev2))
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
 
         response.mustcontain('1 file changed with 7 insertions and 0 deletions')
         ## files
@@ -171,13 +183,13 @@
         response.mustcontain('%s@%s' % (GIT_REPO, rev2))
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
         response.mustcontain('1 file changed with 7 insertions and 0 deletions')
 
         ## files
         response.mustcontain("""<a href="#C--c8e92ef85cd1">.hgignore</a>""")
 
-    def test_compare_revisions_hg_as_form(self):
+    def test_compare_revisions_hg_is_ajax_preview(self):
         self.log_user()
         rev1 = 'b986218ba1c9'
         rev2 = '3d8f361e72ab'
@@ -188,17 +200,17 @@
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
-                                    as_form=True,
+                                    is_ajax_preview=True,
                                     ),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
 
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev2))
 
-        response.mustcontain('Common ancestor')
-        response.mustcontain("""<a href="/%s/changeset/b986218ba1c9b0d6a259fac9b050b1724ed8e545">%s</a>""" % (HG_REPO, rev1))
+        response.mustcontain('Merge Ancestor')
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/b986218ba1c9b0d6a259fac9b050b1724ed8e545">%s</a>""" % (HG_REPO, rev1))
 
-    def test_compare_revisions_git_as_form(self):
+    def test_compare_revisions_git_is_ajax_preview(self):
         self.log_user()
         rev1 = 'c1214f7e79e02fc37156ff215cd71275450cffc3'
         rev2 = '38b5fe81f109cb111f549bfe9bb6b267e10bc557'
@@ -209,11 +221,11 @@
                                     org_ref_name=rev1,
                                     other_ref_type="rev",
                                     other_ref_name=rev2,
-                                    as_form=True,
+                                    is_ajax_preview=True,
                                     ),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
         ## outgoing changesets between those revisions
-        response.mustcontain("""<a href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/38b5fe81f109cb111f549bfe9bb6b267e10bc557">r1:%s</a>""" % (GIT_REPO, rev2[:12]))
 
-        response.mustcontain('Common ancestor')
-        response.mustcontain("""<a href="/%s/changeset/c1214f7e79e02fc37156ff215cd71275450cffc3">%s</a>""" % (GIT_REPO, rev1[:12]))
+        response.mustcontain('Merge Ancestor')
+        response.mustcontain("""<a class="changeset_hash" href="/%s/changeset/c1214f7e79e02fc37156ff215cd71275450cffc3">%s</a>""" % (GIT_REPO, rev1[:12]))
--- a/kallithea/tests/functional/test_feed.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_feed.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 class TestFeedController(TestController):
 
@@ -7,8 +7,6 @@
         response = self.app.get(url(controller='feed', action='rss',
                                     repo_name=HG_REPO))
 
-
-
         assert response.content_type == "application/rss+xml"
         assert """<rss version="2.0">""" in response
 
--- a/kallithea/tests/functional/test_files.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_files.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
 import os
-from kallithea.tests import *
+import posixpath
+import mimetypes
+from kallithea.tests.base import *
 from kallithea.model.db import Repository
 from kallithea.model.meta import Session
 from kallithea.tests.fixture import Fixture
@@ -20,7 +22,6 @@
 def _set_downloads(repo_name, set_to):
     repo = Repository.get_by_repo_name(repo_name)
     repo.enable_downloads = set_to
-    Session().add(repo)
     Session().commit()
 
 
@@ -57,7 +58,7 @@
                 f_path='/')
         )
 
-        #Test response...
+        # Test response...
 
         response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs"><i class="icon-folder-open"></i><span>docs</span></a>' % HG_REPO)
         response.mustcontain('<a class="browser-dir ypjax-link" href="/%s/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests"><i class="icon-folder-open"></i><span>tests</span></a>' % HG_REPO)
@@ -91,17 +92,21 @@
             response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
 
     def test_file_source(self):
+        # Force the global cache to be populated now when we know the right .ini has been loaded.
+        # (Without this, the test would fail.)
+        import kallithea.lib.helpers
+        kallithea.lib.helpers._urlify_issues_f = None
         self.log_user()
         response = self.app.get(url(controller='files', action='index',
                                     repo_name=HG_REPO,
                                     revision='8911406ad776fdd3d0b9932a2e89677e57405a48',
                                     f_path='vcs/nodes.py'))
 
-        response.mustcontain("""<div class="commit">Partially implemented <a class="issue-tracker-link" href="https://issues.example.com/vcs_test_hg/issue/16">#16</a>. filecontent/commit message/author/node name are safe_unicode now.
-In addition some other __str__ are unicode as well
-Added test for unicode
-Improved test to clone into uniq repository.
-removed extra unicode conversion in diff.</div>
+        response.mustcontain("""<div class="formatted-fixed">Partially implemented <a class="issue-tracker-link" href="https://issues.example.com/vcs_test_hg/issue/16">#16</a>. filecontent/commit message/author/node name are safe_unicode now.<br/>"""
+"""In addition some other __str__ are unicode as well<br/>"""
+"""Added test for unicode<br/>"""
+"""Improved test to clone into uniq repository.<br/>"""
+"""removed extra unicode conversion in diff.</div>
 """)
 
         response.mustcontain("""<option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">default at 8911406ad776</option>""")
@@ -113,7 +118,7 @@
                                     revision='tip',
                                     f_path='vcs/nodes.py'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
-        self.assertEqual(response.body, HG_NODE_HISTORY)
+        assert response.body == HG_NODE_HISTORY
 
     def test_file_source_history_git(self):
         self.log_user()
@@ -122,7 +127,7 @@
                                     revision='master',
                                     f_path='vcs/nodes.py'),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'},)
-        self.assertEqual(response.body, GIT_NODE_HISTORY)
+        assert response.body == GIT_NODE_HISTORY
 
     def test_file_annotation(self):
         self.log_user()
@@ -152,7 +157,7 @@
                                     annotate=True),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
-        self.assertEqual(response.body, HG_NODE_HISTORY)
+        assert response.body == HG_NODE_HISTORY
 
     def test_file_annotation_history_git(self):
         self.log_user()
@@ -163,7 +168,7 @@
                                     annotate=True),
                                 extra_environ={'HTTP_X_PARTIAL_XHR': '1'})
 
-        self.assertEqual(response.body, GIT_NODE_HISTORY)
+        assert response.body == GIT_NODE_HISTORY
 
     def test_file_authors(self):
         self.log_user()
@@ -197,14 +202,14 @@
                                         repo_name=HG_REPO,
                                         fname=fname))
 
-            self.assertEqual(response.status, '200 OK')
+            assert response.status == '200 OK'
             heads = [
                 ('Pragma', 'no-cache'),
                 ('Cache-Control', 'no-cache'),
                 ('Content-Disposition', 'attachment; filename=%s' % filename),
-                ('Content-Type', '%s; charset=utf-8' % info[0]),
+                ('Content-Type', info[0]),
             ]
-            self.assertEqual(response.response._headers.items(), heads)
+            assert response.response._headers.items() == heads
 
     def test_archival_wrong_ext(self):
         self.log_user()
@@ -240,8 +245,8 @@
                                     revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
                                     f_path='vcs/nodes.py'))
 
-        self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
-        self.assertEqual(response.content_type, "text/x-python")
+        assert response.content_disposition == "attachment; filename=nodes.py"
+        assert response.content_type == mimetypes.guess_type("nodes.py")[0]
 
     def test_raw_file_wrong_cs(self):
         self.log_user()
@@ -278,7 +283,7 @@
                                     revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
                                     f_path='vcs/nodes.py'))
 
-        self.assertEqual(response.content_type, "text/plain")
+        assert response.content_type == "text/plain"
 
     def test_raw_wrong_cs(self):
         self.log_user()
@@ -347,7 +352,7 @@
 
         self.checkSessionFlash(response, 'No filename')
 
-    @parameterized.expand([
+    @parametrize('location,filename', [
         ('/abs', 'foo'),
         ('../rel', 'foo'),
         ('file/../foo', 'foo'),
@@ -367,14 +372,14 @@
 
         self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
 
-    @parameterized.expand([
+    @parametrize('cnt,location,filename', [
         (1, '', 'foo.txt'),
         (2, 'dir', 'foo.rst'),
         (3, 'rel/dir', 'foo.bar'),
     ])
     def test_add_file_into_hg(self, cnt, location, filename):
         self.log_user()
-        repo = fixture.create_repo('commit-test-%s' % cnt, repo_type='hg')
+        repo = fixture.create_repo(u'commit-test-%s' % cnt, repo_type='hg')
         response = self.app.post(url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
@@ -387,7 +392,7 @@
                                  status=302)
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
 
@@ -423,7 +428,7 @@
 
         self.checkSessionFlash(response, 'No filename')
 
-    @parameterized.expand([
+    @parametrize('location,filename', [
         ('/abs', 'foo'),
         ('../rel', 'foo'),
         ('file/../foo', 'foo'),
@@ -443,14 +448,14 @@
 
         self.checkSessionFlash(response, 'Location must be relative path and must not contain .. in path')
 
-    @parameterized.expand([
+    @parametrize('cnt,location,filename', [
         (1, '', 'foo.txt'),
         (2, 'dir', 'foo.rst'),
         (3, 'rel/dir', 'foo.bar'),
     ])
     def test_add_file_into_git(self, cnt, location, filename):
         self.log_user()
-        repo = fixture.create_repo('commit-test-%s' % cnt, repo_type='git')
+        repo = fixture.create_repo(u'commit-test-%s' % cnt, repo_type='git')
         response = self.app.post(url('files_add_home',
                                       repo_name=repo.repo_name,
                                       revision='tip', f_path='/'),
@@ -463,7 +468,7 @@
                                  status=302)
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
 
@@ -476,7 +481,7 @@
 
     def test_edit_file_view_not_on_branch_hg(self):
         self.log_user()
-        repo = fixture.create_repo('test-edit-repo', repo_type='hg')
+        repo = fixture.create_repo(u'test-edit-repo', repo_type='hg')
 
         ## add file
         location = 'vcs'
@@ -494,10 +499,10 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.get(url('files_edit_home',
                                           repo_name=repo.repo_name,
-                                          revision='tip', f_path='vcs/nodes.py'),
+                                          revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
             self.checkSessionFlash(response,
                 'You can only edit files with revision being a valid branch')
@@ -506,7 +511,7 @@
 
     def test_edit_file_view_commit_changes_hg(self):
         self.log_user()
-        repo = fixture.create_repo('test-edit-repo', repo_type='hg')
+        repo = fixture.create_repo(u'test-edit-repo', repo_type='hg')
 
         ## add file
         location = 'vcs'
@@ -525,19 +530,19 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.post(url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
-                                          f_path='vcs/nodes.py'),
+                                          f_path=posixpath.join(location, filename)),
                                      params={
                                         'content': "def py():\n print 'hello world'\n",
-                                        'message': 'i commited',
+                                        'message': 'i committed',
                                         '_authentication_token': self.authentication_token(),
                                      },
                                     status=302)
-            self.checkSessionFlash(response,
-                                   'Successfully committed to vcs/nodes.py')
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
 
@@ -550,7 +555,7 @@
 
     def test_edit_file_view_not_on_branch_git(self):
         self.log_user()
-        repo = fixture.create_repo('test-edit-repo', repo_type='git')
+        repo = fixture.create_repo(u'test-edit-repo', repo_type='git')
 
         ## add file
         location = 'vcs'
@@ -568,10 +573,10 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.get(url('files_edit_home',
                                           repo_name=repo.repo_name,
-                                          revision='tip', f_path='vcs/nodes.py'),
+                                          revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
             self.checkSessionFlash(response,
                 'You can only edit files with revision being a valid branch')
@@ -580,7 +585,7 @@
 
     def test_edit_file_view_commit_changes_git(self):
         self.log_user()
-        repo = fixture.create_repo('test-edit-repo', repo_type='git')
+        repo = fixture.create_repo(u'test-edit-repo', repo_type='git')
 
         ## add file
         location = 'vcs'
@@ -599,19 +604,19 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.post(url('files_edit_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
-                                          f_path='vcs/nodes.py'),
+                                          f_path=posixpath.join(location, filename)),
                                      params={
                                         'content': "def py():\n print 'hello world'\n",
-                                        'message': 'i commited',
+                                        'message': 'i committed',
                                         '_authentication_token': self.authentication_token(),
                                      },
                                     status=302)
-            self.checkSessionFlash(response,
-                                   'Successfully committed to vcs/nodes.py')
+            self.checkSessionFlash(response, 'Successfully committed to %s'
+                                   % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
 
@@ -624,7 +629,7 @@
 
     def test_delete_file_view_not_on_branch_hg(self):
         self.log_user()
-        repo = fixture.create_repo('test-delete-repo', repo_type='hg')
+        repo = fixture.create_repo(u'test-delete-repo', repo_type='hg')
 
         ## add file
         location = 'vcs'
@@ -642,10 +647,10 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.get(url('files_delete_home',
                                           repo_name=repo.repo_name,
-                                          revision='tip', f_path='vcs/nodes.py'),
+                                          revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
             self.checkSessionFlash(response,
                 'You can only delete files with revision being a valid branch')
@@ -654,7 +659,7 @@
 
     def test_delete_file_view_commit_changes_hg(self):
         self.log_user()
-        repo = fixture.create_repo('test-delete-repo', repo_type='hg')
+        repo = fixture.create_repo(u'test-delete-repo', repo_type='hg')
 
         ## add file
         location = 'vcs'
@@ -673,18 +678,18 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.post(url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
-                                          f_path='vcs/nodes.py'),
+                                          f_path=posixpath.join(location, filename)),
                                      params={
-                                        'message': 'i commited',
+                                        'message': 'i committed',
                                         '_authentication_token': self.authentication_token(),
                                      },
                                     status=302)
             self.checkSessionFlash(response,
-                                   'Successfully deleted file vcs/nodes.py')
+                                   'Successfully deleted file %s' % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
 
@@ -697,7 +702,7 @@
 
     def test_delete_file_view_not_on_branch_git(self):
         self.log_user()
-        repo = fixture.create_repo('test-delete-repo', repo_type='git')
+        repo = fixture.create_repo(u'test-delete-repo', repo_type='git')
 
         ## add file
         location = 'vcs'
@@ -715,10 +720,10 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.get(url('files_delete_home',
                                           repo_name=repo.repo_name,
-                                          revision='tip', f_path='vcs/nodes.py'),
+                                          revision='tip', f_path=posixpath.join(location, filename)),
                                     status=302)
             self.checkSessionFlash(response,
                 'You can only delete files with revision being a valid branch')
@@ -727,7 +732,7 @@
 
     def test_delete_file_view_commit_changes_git(self):
         self.log_user()
-        repo = fixture.create_repo('test-delete-repo', repo_type='git')
+        repo = fixture.create_repo(u'test-delete-repo', repo_type='git')
 
         ## add file
         location = 'vcs'
@@ -746,17 +751,33 @@
         response.follow()
         try:
             self.checkSessionFlash(response, 'Successfully committed to %s'
-                                   % os.path.join(location, filename))
+                                   % posixpath.join(location, filename))
             response = self.app.post(url('files_delete_home',
                                           repo_name=repo.repo_name,
                                           revision=repo.scm_instance.DEFAULT_BRANCH_NAME,
-                                          f_path='vcs/nodes.py'),
+                                          f_path=posixpath.join(location, filename)),
                                      params={
-                                        'message': 'i commited',
+                                        'message': 'i committed',
                                         '_authentication_token': self.authentication_token(),
                                      },
                                     status=302)
             self.checkSessionFlash(response,
-                                   'Successfully deleted file vcs/nodes.py')
+                                   'Successfully deleted file %s' % posixpath.join(location, filename))
         finally:
             fixture.destroy_repo(repo.repo_name)
+
+    def test_png_diff_no_crash_hg(self):
+        self.log_user()
+        response = self.app.get(url('files_diff_home',
+                                    repo_name=HG_REPO,
+                                    f_path='docs/theme/ADC/static/documentation.png',
+                                    diff1='tip', diff2='tip'))
+        response.mustcontain("""<pre>Binary file</pre>""")
+
+    def test_png_diff_no_crash_git(self):
+        self.log_user()
+        response = self.app.get(url('files_diff_home',
+                                    repo_name=GIT_REPO,
+                                    f_path='docs/theme/ADC/static/documentation.png',
+                                    diff1='master', diff2='master'))
+        response.mustcontain("""<pre>Binary file</pre>""")
--- a/kallithea/tests/functional/test_followers.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_followers.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 
 class TestFollowersController(TestController):
--- a/kallithea/tests/functional/test_forks.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_forks.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,10 +1,12 @@
 # -*- coding: utf-8 -*-
 
+import urllib
 import unittest
 
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 
+from kallithea.lib.utils2 import safe_str, safe_unicode
 from kallithea.model.db import Repository
 from kallithea.model.repo import RepoModel
 from kallithea.model.user import UserModel
@@ -12,31 +14,8 @@
 
 fixture = Fixture()
 
-from kallithea.tests import *
 
-
-class _BaseFixture(unittest.TestCase):
-    @classmethod
-    def setup_class(cls):
-        pass
-
-    @classmethod
-    def teardown_class(cls):
-        pass
-
-    def setUp(self):
-        self.username = u'forkuser'
-        self.password = u'qweqwe'
-        self.u1 = fixture.create_user(self.username, password=self.password,
-                                      email=u'fork_king@example.com')
-        Session().commit()
-
-    def tearDown(self):
-        Session().delete(self.u1)
-        Session().commit()
-
-
-class _BaseTestCase(object):
+class _BaseTestCase(TestController):
     """
     Write all tests here
     """
@@ -45,6 +24,17 @@
     NEW_REPO = None
     REPO_FORK = None
 
+    def setup_method(self, method):
+        self.username = u'forkuser'
+        self.password = u'qweqwe'
+        self.u1 = fixture.create_user(self.username, password=self.password,
+                                      email=u'fork_king@example.com')
+        Session().commit()
+
+    def teardown_method(self, method):
+        Session().delete(self.u1)
+        Session().commit()
+
     def test_index(self):
         self.log_user()
         repo_name = self.REPO
@@ -97,11 +87,11 @@
 
         # remove this fork
         response = self.app.post(url('delete_repo', repo_name=fork_name),
-            params={'_method': 'delete', '_authentication_token': self.authentication_token()})
+            params={'_authentication_token': self.authentication_token()})
 
     def test_fork_create_into_group(self):
         self.log_user()
-        group = fixture.create_repo_group('vc')
+        group = fixture.create_repo_group(u'vc')
         group_id = group.group_id
         fork_name = self.REPO_FORK
         fork_name_full = 'vc/%s' % fork_name
@@ -124,17 +114,17 @@
 
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=fork_name_full))
-        #test if we have a message that fork is ok
+        # test if we have a message that fork is ok
         self.checkSessionFlash(response,
                 'Forked repository %s as <a href="/%s">%s</a>'
                 % (repo_name, fork_name_full, fork_name_full))
 
-        #test if the fork was created in the database
-        fork_repo = Session().query(Repository)\
+        # test if the fork was created in the database
+        fork_repo = Session().query(Repository) \
             .filter(Repository.repo_name == fork_name_full).one()
 
-        self.assertEqual(fork_repo.repo_name, fork_name_full)
-        self.assertEqual(fork_repo.fork.repo_name, repo_name)
+        assert fork_repo.repo_name == fork_name_full
+        assert fork_repo.fork.repo_name == repo_name
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=fork_name_full))
@@ -145,7 +135,58 @@
         fixture.destroy_repo(fork_name_full)
         fixture.destroy_repo_group(group_id)
 
-    def test_z_fork_create(self):
+    def test_fork_unicode(self):
+        self.log_user()
+
+        # create a fork
+        repo_name = self.REPO
+        org_repo = Repository.get_by_repo_name(repo_name)
+        fork_name = safe_str(self.REPO_FORK + u'-rødgrød')
+        creation_args = {
+            'repo_name': fork_name,
+            'repo_group': u'-1',
+            'fork_parent_id': org_repo.repo_id,
+            'repo_type': self.REPO_TYPE,
+            'description': 'unicode repo 1',
+            'private': 'False',
+            'landing_rev': 'rev:tip',
+            '_authentication_token': self.authentication_token()}
+        self.app.post(url(controller='forks', action='fork_create',
+                          repo_name=repo_name), creation_args)
+        response = self.app.get(url(controller='forks', action='forks',
+                                    repo_name=repo_name))
+        response.mustcontain(
+            """<a href="/%s">%s</a>""" % (urllib.quote(fork_name), fork_name)
+        )
+        fork_repo = Repository.get_by_repo_name(safe_unicode(fork_name))
+        assert fork_repo
+
+        # fork the fork
+        fork_name_2 = safe_str(self.REPO_FORK + u'-blåbærgrød')
+        creation_args = {
+            'repo_name': fork_name_2,
+            'repo_group': u'-1',
+            'fork_parent_id': fork_repo.repo_id,
+            'repo_type': self.REPO_TYPE,
+            'description': 'unicode repo 2',
+            'private': 'False',
+            'landing_rev': 'rev:tip',
+            '_authentication_token': self.authentication_token()}
+        self.app.post(url(controller='forks', action='fork_create',
+                          repo_name=fork_name), creation_args)
+        response = self.app.get(url(controller='forks', action='forks',
+                                    repo_name=fork_name))
+        response.mustcontain(
+            """<a href="/%s">%s</a>""" % (urllib.quote(fork_name_2), fork_name_2)
+        )
+
+        # remove these forks
+        response = self.app.post(url('delete_repo', repo_name=fork_name_2),
+            params={'_authentication_token': self.authentication_token()})
+        response = self.app.post(url('delete_repo', repo_name=fork_name),
+            params={'_authentication_token': self.authentication_token()})
+
+    def test_fork_create_and_permissions(self):
         self.log_user()
         fork_name = self.REPO_FORK
         description = 'fork of vcs test'
@@ -167,17 +208,17 @@
 
         ## run the check page that triggers the flash message
         response = self.app.get(url('repo_check_home', repo_name=fork_name))
-        #test if we have a message that fork is ok
+        # test if we have a message that fork is ok
         self.checkSessionFlash(response,
                 'Forked repository %s as <a href="/%s">%s</a>'
                 % (repo_name, fork_name, fork_name))
 
-        #test if the fork was created in the database
-        fork_repo = Session().query(Repository)\
+        # test if the fork was created in the database
+        fork_repo = Session().query(Repository) \
             .filter(Repository.repo_name == fork_name).one()
 
-        self.assertEqual(fork_repo.repo_name, fork_name)
-        self.assertEqual(fork_repo.fork.repo_name, repo_name)
+        assert fork_repo.repo_name == fork_name
+        assert fork_repo.fork.repo_name == repo_name
 
         # test if the repository is visible in the list ?
         response = self.app.get(url('summary_home', repo_name=fork_name))
@@ -185,14 +226,12 @@
         response.mustcontain(self.REPO_TYPE)
         response.mustcontain('Fork of "<a href="/%s">%s</a>"' % (repo_name, repo_name))
 
-    def test_zz_fork_permission_page(self):
         usr = self.log_user(self.username, self.password)['user_id']
-        repo_name = self.REPO
 
-        forks = Repository.query()\
-            .filter(Repository.repo_type == self.REPO_TYPE)\
+        forks = Repository.query() \
+            .filter(Repository.repo_type == self.REPO_TYPE) \
             .filter(Repository.fork_id != None).all()
-        self.assertEqual(1, len(forks))
+        assert 1 == len(forks)
 
         # set read permissions for this
         RepoModel().grant_user_permission(repo=forks[0],
@@ -203,35 +242,33 @@
         response = self.app.get(url(controller='forks', action='forks',
                                     repo_name=repo_name))
 
-        response.mustcontain('<div style="padding:5px 3px 3px 42px;">fork of vcs test</div>')
+        response.mustcontain('<div>fork of vcs test</div>')
 
-    def test_zzz_fork_permission_page(self):
-        usr = self.log_user(self.username, self.password)['user_id']
-        repo_name = self.REPO
+        # remove permissions
+        try:
+            RepoModel().grant_user_permission(repo=forks[0],
+                                              user=usr, perm='repository.none')
+            Session().commit()
 
-        forks = Repository.query()\
-            .filter(Repository.repo_type == self.REPO_TYPE)\
-            .filter(Repository.fork_id != None).all()
-        self.assertEqual(1, len(forks))
+            # fork shouldn't be visible
+            response = self.app.get(url(controller='forks', action='forks',
+                                        repo_name=repo_name))
+            response.mustcontain('There are no forks yet')
 
-        # set none
-        RepoModel().grant_user_permission(repo=forks[0],
-                                          user=usr, perm='repository.none')
-        Session().commit()
-        # fork shouldn't be there
-        response = self.app.get(url(controller='forks', action='forks',
-                                    repo_name=repo_name))
-        response.mustcontain('There are no forks yet')
+        finally:
+            RepoModel().grant_user_permission(repo=forks[0],
+                                              user=usr, perm='repository.read')
+            RepoModel().delete(repo=forks[0])
 
 
-class TestGIT(TestController, _BaseTestCase, _BaseFixture):
+class TestGIT(_BaseTestCase):
     REPO = GIT_REPO
     NEW_REPO = NEW_GIT_REPO
     REPO_TYPE = 'git'
     REPO_FORK = GIT_FORK
 
 
-class TestHG(TestController, _BaseTestCase, _BaseFixture):
+class TestHG(_BaseTestCase):
     REPO = HG_REPO
     NEW_REPO = NEW_HG_REPO
     REPO_TYPE = 'hg'
--- a/kallithea/tests/functional/test_home.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_home.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,7 @@
-from kallithea.tests import *
+# -*- coding: utf-8 -*-
+import json
+
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.model.meta import Session
 from kallithea.model.db import Repository
@@ -14,20 +17,27 @@
     def test_index(self):
         self.log_user()
         response = self.app.get(url(controller='home', action='index'))
-        #if global permission is set
+        # if global permission is set
         response.mustcontain('Add Repository')
+
+        response.mustcontain(
+            """<span class="label label-repo" title="Git repository">git"""
+        )
+
+        response.mustcontain(
+            """<span class="label label-repo" title="Mercurial repository">hg"""
+        )
+
         # html in javascript variable:
-        response.mustcontain('var data = {"totalRecords": %s' % len(Repository.getAll()))
         response.mustcontain(r'href=\"/%s\"' % HG_REPO)
 
-        response.mustcontain(r'<span class="repotag">git')
-        response.mustcontain(r'<i class=\"icon-globe\"')
+        response.mustcontain(r'\x3ci class=\"icon-globe\"')
 
-        response.mustcontain("""fixes issue with having custom format for git-log""")
-        response.mustcontain("""/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3""" % GIT_REPO)
+        response.mustcontain(r'\"fixes issue with having custom format for git-log\n\"')
+        response.mustcontain(r'\"/%s/changeset/5f2c6ee195929b0be80749243c18121c9864a3b3\"' % GIT_REPO)
 
-        response.mustcontain("""disable security checks on hg clone for travis""")
-        response.mustcontain("""/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e""" % HG_REPO)
+        response.mustcontain(r'\"disable security checks on hg clone for travis\"')
+        response.mustcontain(r'\"/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e\"' % HG_REPO)
 
     def test_repo_summary_with_anonymous_access_disabled(self):
         with fixture.anon_access(False):
@@ -44,13 +54,35 @@
 
     def test_index_page_on_groups(self):
         self.log_user()
-        gr = fixture.create_repo_group('gr1')
-        fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
-        response = self.app.get(url('repos_group_home', group_name='gr1'))
+        gr = fixture.create_repo_group(u'gr1')
+        fixture.create_repo(name=u'gr1/repo_in_group', repo_group=gr)
+        response = self.app.get(url('repos_group_home', group_name=u'gr1'))
 
         try:
-            response.mustcontain("gr1/repo_in_group")
+            response.mustcontain(u"gr1/repo_in_group")
         finally:
-            RepoModel().delete('gr1/repo_in_group')
-            RepoGroupModel().delete(repo_group='gr1', force_delete=True)
+            RepoModel().delete(u'gr1/repo_in_group')
+            RepoGroupModel().delete(repo_group=u'gr1', force_delete=True)
             Session().commit()
+
+    def test_users_and_groups_data(self):
+        fixture.create_user('evil', firstname=u'D\'o\'ct"o"r', lastname=u'Évíl')
+        fixture.create_user_group(u'grrrr', user_group_description=u"Groüp")
+        response = self.app.get(url('users_and_groups_data', query=u'evi'))
+        assert response.status_code == 302
+        assert url('login_home') in response.location
+        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        response = self.app.get(url('users_and_groups_data', query=u'evi'))
+        result = json.loads(response.body)['results']
+        assert result[0].get('fname') == u'D\'o\'ct"o"r'
+        assert result[0].get('lname') == u'Évíl'
+        response = self.app.get(url('users_and_groups_data', key=u'evil'))
+        result = json.loads(response.body)['results']
+        assert result[0].get('fname') == u'D\'o\'ct"o"r'
+        assert result[0].get('lname') == u'Évíl'
+        response = self.app.get(url('users_and_groups_data', query=u'rrrr'))
+        result = json.loads(response.body)['results']
+        assert not result
+        response = self.app.get(url('users_and_groups_data', types='users,groups', query=u'rrrr'))
+        result = json.loads(response.body)['results']
+        assert result[0].get('grname') == u'grrrr'
--- a/kallithea/tests/functional/test_journal.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_journal.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 import datetime
 
 
@@ -8,22 +8,22 @@
         self.log_user()
         response = self.app.get(url(controller='journal', action='index'))
 
-        response.mustcontain("""<div class="journal_day">%s</div>""" % datetime.date.today())
+        response.mustcontain("""<h4>%s</h4>""" % datetime.date.today())
 
     def test_stop_following_repository(self):
         session = self.log_user()
 #        usr = Session().query(User).filter(User.username == TEST_USER_ADMIN_LOGIN).one()
 #        repo = Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
 #
-#        followings = Session().query(UserFollowing)\
-#            .filter(UserFollowing.user == usr)\
+#        followings = Session().query(UserFollowing) \
+#            .filter(UserFollowing.user == usr) \
 #            .filter(UserFollowing.follows_repository == repo).all()
 #
 #        assert len(followings) == 1, 'Not following any repository'
 #
 #        response = self.app.post(url(controller='journal',
 #                                     action='toggle_following'),
-#                                     {'follows_repo_id':repo.repo_id})
+#                                     {'follows_repository_id':repo.repo_id})
 
     def test_start_following_repository(self):
         self.log_user()
--- a/kallithea/tests/functional/test_login.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_login.py	Sun Mar 31 21:28:56 2019 +0200
@@ -5,35 +5,34 @@
 
 import mock
 
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.lib.utils2 import generate_api_key
 from kallithea.lib.auth import check_password
 from kallithea.lib import helpers as h
 from kallithea.model.api_key import ApiKeyModel
 from kallithea.model import validators
-from kallithea.model.db import User, Notification
+from kallithea.model.db import User
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
 
+from tg.util.webtest import test_context
+
 fixture = Fixture()
 
 
 class TestLoginController(TestController):
-    def setUp(self):
-        self.remove_all_notifications()
-        self.assertEqual(Notification.query().all(), [])
 
     def test_index(self):
         response = self.app.get(url(controller='login', action='index'))
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         # Test response...
 
     def test_login_admin_ok(self):
         response = self.app.post(url(controller='login', action='index'),
                                  {'username': TEST_USER_ADMIN_LOGIN,
                                   'password': TEST_USER_ADMIN_PASS})
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
         self.assert_authenticated_user(response, TEST_USER_ADMIN_LOGIN)
 
         response = response.follow()
@@ -44,7 +43,18 @@
                                  {'username': TEST_USER_REGULAR_LOGIN,
                                   'password': TEST_USER_REGULAR_PASS})
 
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
+        self.assert_authenticated_user(response, TEST_USER_REGULAR_LOGIN)
+
+        response = response.follow()
+        response.mustcontain('/%s' % HG_REPO)
+
+    def test_login_regular_email_ok(self):
+        response = self.app.post(url(controller='login', action='index'),
+                                 {'username': TEST_USER_REGULAR_EMAIL,
+                                  'password': TEST_USER_REGULAR_PASS})
+
+        assert response.status == '302 Found'
         self.assert_authenticated_user(response, TEST_USER_REGULAR_LOGIN)
 
         response = response.follow()
@@ -56,10 +66,10 @@
                                      came_from=test_came_from),
                                  {'username': TEST_USER_ADMIN_LOGIN,
                                   'password': TEST_USER_ADMIN_PASS})
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
         response = response.follow()
 
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         response.mustcontain('Users Administration')
 
     def test_login_do_not_remember(self):
@@ -68,10 +78,9 @@
                                   'password': TEST_USER_REGULAR_PASS,
                                   'remember': False})
 
-        self.assertIn('Set-Cookie', response.headers)
+        assert 'Set-Cookie' in response.headers
         for cookie in response.headers.getall('Set-Cookie'):
-            self.assertFalse(re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE),
-                'Cookie %r has expiration date, but should be a session cookie' % cookie)
+            assert not re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE), 'Cookie %r has expiration date, but should be a session cookie' % cookie
 
     def test_login_remember(self):
         response = self.app.post(url(controller='login', action='index'),
@@ -79,10 +88,9 @@
                                   'password': TEST_USER_REGULAR_PASS,
                                   'remember': True})
 
-        self.assertIn('Set-Cookie', response.headers)
+        assert 'Set-Cookie' in response.headers
         for cookie in response.headers.getall('Set-Cookie'):
-            self.assertTrue(re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE),
-                'Cookie %r should have expiration date, but is a session cookie' % cookie)
+            assert re.search(r';\s+(Max-Age|Expires)=', cookie, re.IGNORECASE), 'Cookie %r should have expiration date, but is a session cookie' % cookie
 
     def test_logout(self):
         response = self.app.post(url(controller='login', action='index'),
@@ -92,15 +100,15 @@
         # Verify that a login session has been established.
         response = self.app.get(url(controller='login', action='index'))
         response = response.follow()
-        self.assertIn('authuser', response.session)
+        assert 'authuser' in response.session
 
         response.click('Log Out')
 
         # Verify that the login session has been terminated.
         response = self.app.get(url(controller='login', action='index'))
-        self.assertNotIn('authuser', response.session)
+        assert 'authuser' not in response.session
 
-    @parameterized.expand([
+    @parametrize('url_came_from', [
           ('data:text/html,<script>window.alert("xss")</script>',),
           ('mailto:test@example.com',),
           ('file:///etc/passwd',),
@@ -122,7 +130,7 @@
         response = self.app.post(url(controller='login', action='index'),
                                  {'username': TEST_USER_ADMIN_LOGIN,
                                   'password': 'as'})
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
 
         response.mustcontain('Enter 3 characters or more')
 
@@ -142,7 +150,7 @@
 
     # verify that get arguments are correctly passed along login redirection
 
-    @parameterized.expand([
+    @parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, (('foo', 'one'), ('bar', 'two'))),
         ({'blue': u'blå'.encode('utf-8'), 'green':u'grøn'},
              (('blue', u'blå'.encode('utf-8')), ('green', u'grøn'.encode('utf-8')))),
@@ -152,13 +160,13 @@
             response = self.app.get(url(controller='summary', action='index',
                                         repo_name=HG_REPO,
                                         **args))
-            self.assertEqual(response.status, '302 Found')
+            assert response.status == '302 Found'
             came_from = urlparse.parse_qs(urlparse.urlparse(response.location).query)['came_from'][0]
             came_from_qs = urlparse.parse_qsl(urlparse.urlparse(came_from).query)
             for encoded in args_encoded:
-                self.assertIn(encoded, came_from_qs)
+                assert encoded in came_from_qs
 
-    @parameterized.expand([
+    @parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', 'green':u'grøn'},
              ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
@@ -168,23 +176,23 @@
                                     came_from=url('/_admin/users', **args)))
         came_from = urlparse.parse_qs(urlparse.urlparse(response.form.action).query)['came_from'][0]
         for encoded in args_encoded:
-            self.assertIn(encoded, came_from)
+            assert encoded in came_from
 
-    @parameterized.expand([
+    @parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', 'green':u'grøn'},
              ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
     ])
     def test_redirection_after_successful_login_preserves_get_args(self, args, args_encoded):
         response = self.app.post(url(controller='login', action='index',
-                                     came_from = url('/_admin/users', **args)),
+                                     came_from=url('/_admin/users', **args)),
                                  {'username': TEST_USER_ADMIN_LOGIN,
                                   'password': TEST_USER_ADMIN_PASS})
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
         for encoded in args_encoded:
-            self.assertIn(encoded, response.location)
+            assert encoded in response.location
 
-    @parameterized.expand([
+    @parametrize('args,args_encoded', [
         ({'foo':'one', 'bar':'two'}, ('foo=one', 'bar=two')),
         ({'blue': u'blå', 'green':u'grøn'},
              ('blue=bl%C3%A5', 'green=gr%C3%B8n')),
@@ -198,7 +206,7 @@
         response.mustcontain('Invalid username or password')
         came_from = urlparse.parse_qs(urlparse.urlparse(response.form.action).query)['came_from'][0]
         for encoded in args_encoded:
-            self.assertIn(encoded, came_from)
+            assert encoded in came_from
 
     #==========================================================================
     # REGISTRATIONS
@@ -217,7 +225,8 @@
                                              'firstname': 'test',
                                              'lastname': 'test'})
 
-        msg = validators.ValidUsername()._messages['username_exists']
+        with test_context(self.app):
+            msg = validators.ValidUsername()._messages['username_exists']
         msg = h.html_escape(msg % {'username': uname})
         response.mustcontain(msg)
 
@@ -230,7 +239,8 @@
                                              'firstname': 'test',
                                              'lastname': 'test'})
 
-        msg = validators.UniqSystemEmail()()._messages['email_taken']
+        with test_context(self.app):
+            msg = validators.UniqSystemEmail()()._messages['email_taken']
         response.mustcontain(msg)
 
     def test_register_err_same_email_case_sensitive(self):
@@ -241,7 +251,8 @@
                                              'email': TEST_USER_ADMIN_EMAIL.title(),
                                              'firstname': 'test',
                                              'lastname': 'test'})
-        msg = validators.UniqSystemEmail()()._messages['email_taken']
+        with test_context(self.app):
+            msg = validators.UniqSystemEmail()()._messages['email_taken']
         response.mustcontain(msg)
 
     def test_register_err_wrong_data(self):
@@ -252,7 +263,7 @@
                                              'email': 'goodmailm',
                                              'firstname': 'test',
                                              'lastname': 'test'})
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         response.mustcontain('An email address must contain a single @')
         response.mustcontain('Enter a value 6 characters long or more')
 
@@ -282,7 +293,8 @@
                                              'lastname': 'test'})
 
         response.mustcontain('An email address must contain a single @')
-        msg = validators.ValidUsername()._messages['username_exists']
+        with test_context(self.app):
+            msg = validators.ValidUsername()._messages['username_exists']
         msg = h.html_escape(msg % {'username': usr})
         response.mustcontain(msg)
 
@@ -295,7 +307,8 @@
                                          'firstname': 'test',
                                          'lastname': 'test'})
 
-        msg = validators.ValidPassword()._messages['invalid_password']
+        with test_context(self.app):
+            msg = validators.ValidPassword()._messages['invalid_password']
         response.mustcontain(msg)
 
     def test_register_password_mismatch(self):
@@ -306,7 +319,8 @@
                                              'email': 'goodmailm@test.plxa',
                                              'firstname': 'test',
                                              'lastname': 'test'})
-        msg = validators.ValidPasswordsMatch('password', 'password_confirmation')._messages['password_mismatch']
+        with test_context(self.app):
+            msg = validators.ValidPasswordsMatch('password', 'password_confirmation')._messages['password_mismatch']
         response.mustcontain(msg)
 
     def test_register_ok(self):
@@ -323,18 +337,18 @@
                                              'email': email,
                                              'firstname': name,
                                              'lastname': lastname,
-                                             'admin': True})  # This should be overriden
-        self.assertEqual(response.status, '302 Found')
-        self.checkSessionFlash(response, 'You have successfully registered into Kallithea')
+                                             'admin': True})  # This should be overridden
+        assert response.status == '302 Found'
+        self.checkSessionFlash(response, 'You have successfully registered with Kallithea')
 
         ret = Session().query(User).filter(User.username == 'test_regular4').one()
-        self.assertEqual(ret.username, username)
-        self.assertEqual(check_password(password, ret.password), True)
-        self.assertEqual(ret.email, email)
-        self.assertEqual(ret.name, name)
-        self.assertEqual(ret.lastname, lastname)
-        self.assertNotEqual(ret.api_key, None)
-        self.assertEqual(ret.admin, False)
+        assert ret.username == username
+        assert check_password(password, ret.password) == True
+        assert ret.email == email
+        assert ret.name == name
+        assert ret.lastname == lastname
+        assert ret.api_key is not None
+        assert ret.admin == False
 
     #==========================================================================
     # PASSWORD RESET
@@ -352,13 +366,13 @@
     def test_forgot_password(self):
         response = self.app.get(url(controller='login',
                                     action='password_reset'))
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
 
         username = 'test_password_reset_1'
         password = 'qweqwe'
         email = 'username@example.com'
-        name = 'passwd'
-        lastname = 'reset'
+        name = u'passwd'
+        lastname = u'reset'
         timestamp = int(time.time())
 
         new = User()
@@ -391,7 +405,7 @@
                                   'password_confirm': "p@ssw0rd",
                                   'token': token,
                                  })
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         response.mustcontain('Invalid password reset token')
 
         # GOOD TOKEN
@@ -407,7 +421,7 @@
                                     email=email,
                                     timestamp=timestamp,
                                     token=token))
-        self.assertEqual(response.status, '200 OK')
+        assert response.status == '200 OK'
         response.mustcontain("You are about to set a new password for the email address %s" % email)
 
         response = self.app.post(url(controller='login',
@@ -418,7 +432,7 @@
                                   'password_confirm': "p@ssw0rd",
                                   'token': token,
                                  })
-        self.assertEqual(response.status, '302 Found')
+        assert response.status == '302 Found'
         self.checkSessionFlash(response, 'Successfully updated password')
 
         response = response.follow()
@@ -431,78 +445,76 @@
         config = {'api_access_controllers_whitelist': values or []}
         return config
 
-    @parameterized.expand([
-        ('none', None),
-        ('empty_string', ''),
-        ('fake_number', '123456'),
-        ('proper_api_key', None)
+    def _api_key_test(self, api_key, status):
+        """Verifies HTTP status code for accessing an auth-requiring page,
+        using the given api_key URL parameter as well as using the API key
+        with bearer authentication.
+
+        If api_key is None, no api_key is passed at all. If api_key is True,
+        a real, working API key is used.
+        """
+        with fixture.anon_access(False):
+            if api_key is None:
+                params = {}
+                headers = {}
+            else:
+                if api_key is True:
+                    api_key = User.get_first_admin().api_key
+                params = {'api_key': api_key}
+                headers = {'Authorization': 'Bearer ' + str(api_key)}
+
+            self.app.get(url(controller='changeset', action='changeset_raw',
+                             repo_name=HG_REPO, revision='tip', **params),
+                         status=status)
+
+            self.app.get(url(controller='changeset', action='changeset_raw',
+                             repo_name=HG_REPO, revision='tip'),
+                         headers=headers,
+                         status=status)
+
+    @parametrize('test_name,api_key,code', [
+        ('none', None, 302),
+        ('empty_string', '', 403),
+        ('fake_number', '123456', 403),
+        ('proper_api_key', True, 403)
     ])
-    def test_access_not_whitelisted_page_via_api_key(self, test_name, api_key):
+    def test_access_not_whitelisted_page_via_api_key(self, test_name, api_key, code):
         whitelist = self._get_api_whitelist([])
         with mock.patch('kallithea.CONFIG', whitelist):
-            self.assertEqual([],
-                             whitelist['api_access_controllers_whitelist'])
-            if test_name == 'proper_api_key':
-                #use builtin if api_key is None
-                api_key = User.get_first_admin().api_key
+            assert [] == whitelist['api_access_controllers_whitelist']
+            self._api_key_test(api_key, code)
 
-            with fixture.anon_access(False):
-                self.app.get(url(controller='changeset',
-                                 action='changeset_raw',
-                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
-                             status=403)
-
-    @parameterized.expand([
+    @parametrize('test_name,api_key,code', [
         ('none', None, 302),
-        ('empty_string', '', 302),
-        ('fake_number', '123456', 302),
-        ('fake_not_alnum', 'a-z', 302),
-        ('fake_api_key', '0123456789abcdef0123456789ABCDEF01234567', 302),
-        ('proper_api_key', None, 200)
+        ('empty_string', '', 403),
+        ('fake_number', '123456', 403),
+        ('fake_not_alnum', 'a-z', 403),
+        ('fake_api_key', '0123456789abcdef0123456789ABCDEF01234567', 403),
+        ('proper_api_key', True, 200)
     ])
     def test_access_whitelisted_page_via_api_key(self, test_name, api_key, code):
         whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
         with mock.patch('kallithea.CONFIG', whitelist):
-            self.assertEqual(['ChangesetController:changeset_raw'],
-                             whitelist['api_access_controllers_whitelist'])
-            if test_name == 'proper_api_key':
-                api_key = User.get_first_admin().api_key
-
-            with fixture.anon_access(False):
-                self.app.get(url(controller='changeset',
-                                 action='changeset_raw',
-                                 repo_name=HG_REPO, revision='tip', api_key=api_key),
-                             status=code)
+            assert ['ChangesetController:changeset_raw'] == whitelist['api_access_controllers_whitelist']
+            self._api_key_test(api_key, code)
 
     def test_access_page_via_extra_api_key(self):
         whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
         with mock.patch('kallithea.CONFIG', whitelist):
-            self.assertEqual(['ChangesetController:changeset_raw'],
-                             whitelist['api_access_controllers_whitelist'])
+            assert ['ChangesetController:changeset_raw'] == whitelist['api_access_controllers_whitelist']
 
             new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
             Session().commit()
-            with fixture.anon_access(False):
-                self.app.get(url(controller='changeset',
-                                 action='changeset_raw',
-                                 repo_name=HG_REPO, revision='tip', api_key=new_api_key.api_key),
-                             status=200)
+            self._api_key_test(new_api_key.api_key, status=200)
 
     def test_access_page_via_expired_api_key(self):
         whitelist = self._get_api_whitelist(['ChangesetController:changeset_raw'])
         with mock.patch('kallithea.CONFIG', whitelist):
-            self.assertEqual(['ChangesetController:changeset_raw'],
-                             whitelist['api_access_controllers_whitelist'])
+            assert ['ChangesetController:changeset_raw'] == whitelist['api_access_controllers_whitelist']
 
             new_api_key = ApiKeyModel().create(TEST_USER_ADMIN_LOGIN, u'test')
             Session().commit()
-            #patch the API key and make it expired
+            # patch the API key and make it expired
             new_api_key.expires = 0
-            Session().add(new_api_key)
             Session().commit()
-            with fixture.anon_access(False):
-                self.app.get(url(controller='changeset',
-                                 action='changeset_raw',
-                                 repo_name=HG_REPO, revision='tip',
-                                 api_key=new_api_key.api_key),
-                             status=302)
+            self._api_key_test(new_api_key.api_key, status=403)
--- a/kallithea/tests/functional/test_my_account.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_my_account.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,12 +1,14 @@
 # -*- coding: utf-8 -*-
 
 from kallithea.model.db import User, UserFollowing, Repository, UserApiKeys
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.lib import helpers as h
 from kallithea.model.user import UserModel
 from kallithea.model.meta import Session
 
+from tg.util.webtest import test_context
+
 fixture = Fixture()
 
 
@@ -28,9 +30,10 @@
     def test_my_account_my_repos(self):
         self.log_user()
         response = self.app.get(url('my_account_repos'))
-        cnt = Repository.query().filter(Repository.user ==
+        cnt = Repository.query().filter(Repository.owner ==
                            User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
-        response.mustcontain('"totalRecords": %s' % cnt)
+        response.mustcontain('"raw_name": "%s"' % HG_REPO)
+        response.mustcontain('"just_name": "%s"' % GIT_REPO)
 
     def test_my_account_my_watched(self):
         self.log_user()
@@ -38,7 +41,8 @@
 
         cnt = UserFollowing.query().filter(UserFollowing.user ==
                             User.get_by_username(TEST_USER_ADMIN_LOGIN)).count()
-        response.mustcontain('"totalRecords": %s' % cnt)
+        response.mustcontain('"raw_name": "%s"' % HG_REPO)
+        response.mustcontain('"just_name": "%s"' % GIT_REPO)
 
     def test_my_account_my_emails(self):
         self.log_user()
@@ -53,7 +57,7 @@
                                  {'new_email': TEST_USER_REGULAR_EMAIL, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'This email address is already in use')
 
-    def test_my_account_my_emails_add_mising_email_in_form(self):
+    def test_my_account_my_emails_add_missing_email_in_form(self):
         self.log_user()
         response = self.app.get(url('my_account_emails'))
         response.mustcontain('No additional emails specified')
@@ -72,21 +76,21 @@
         response = self.app.get(url('my_account_emails'))
 
         from kallithea.model.db import UserEmailMap
-        email_id = UserEmailMap.query()\
-            .filter(UserEmailMap.user == User.get_by_username(TEST_USER_ADMIN_LOGIN))\
+        email_id = UserEmailMap.query() \
+            .filter(UserEmailMap.user == User.get_by_username(TEST_USER_ADMIN_LOGIN)) \
             .filter(UserEmailMap.email == 'barz@example.com').one().email_id
 
         response.mustcontain('barz@example.com')
         response.mustcontain('<input id="del_email_id" name="del_email_id" type="hidden" value="%s" />' % email_id)
 
-        response = self.app.post(url('my_account_emails'),
-                                 {'del_email_id': email_id, '_method': 'delete', '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('my_account_emails_delete'),
+                                 {'del_email_id': email_id, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'Removed email from user')
         response = self.app.get(url('my_account_emails'))
         response.mustcontain('No additional emails specified')
 
 
-    @parameterized.expand(
+    @parametrize('name,attrs',
         [('firstname', {'firstname': 'new_username'}),
          ('lastname', {'lastname': 'new_username'}),
          ('admin', {'admin': True}),
@@ -132,20 +136,20 @@
         if name == 'email':
             params['emails'] = [attrs['email']]
         if name == 'extern_type':
-            #cannot update this via form, expected value is original one
+            # cannot update this via form, expected value is original one
             params['extern_type'] = "internal"
         if name == 'extern_name':
-            #cannot update this via form, expected value is original one
+            # cannot update this via form, expected value is original one
             params['extern_name'] = str(user_id)
         if name == 'active':
-            #my account cannot deactivate account
+            # my account cannot deactivate account
             params['active'] = True
         if name == 'admin':
-            #my account cannot make you an admin !
+            # my account cannot make you an admin !
             params['admin'] = False
 
         params.pop('_authentication_token')
-        self.assertEqual(params, updated_params)
+        assert params == updated_params
 
     def test_my_account_update_err_email_exists(self):
         self.log_user()
@@ -156,8 +160,8 @@
                                     username=TEST_USER_ADMIN_LOGIN,
                                     new_password=TEST_USER_ADMIN_PASS,
                                     password_confirmation='test122',
-                                    firstname='NewName',
-                                    lastname='NewLastname',
+                                    firstname=u'NewName',
+                                    lastname=u'NewLastname',
                                     email=new_email,
                                     _authentication_token=self.authentication_token())
                                 )
@@ -173,15 +177,16 @@
                                             username=TEST_USER_ADMIN_LOGIN,
                                             new_password=TEST_USER_ADMIN_PASS,
                                             password_confirmation='test122',
-                                            firstname='NewName',
-                                            lastname='NewLastname',
+                                            firstname=u'NewName',
+                                            lastname=u'NewLastname',
                                             email=new_email,
                                             _authentication_token=self.authentication_token()))
 
         response.mustcontain('An email address must contain a single @')
         from kallithea.model import validators
-        msg = validators.ValidUsername(edit=False, old_data={})\
-                ._messages['username_exists']
+        with test_context(self.app):
+            msg = validators.ValidUsername(edit=False, old_data={}) \
+                    ._messages['username_exists']
         msg = h.html_escape(msg % {'username': TEST_USER_ADMIN_LOGIN})
         response.mustcontain(msg)
 
@@ -192,7 +197,7 @@
         response.mustcontain(user.api_key)
         response.mustcontain('Expires: Never')
 
-    @parameterized.expand([
+    @parametrize('desc,lifetime', [
         ('forever', -1),
         ('5mins', 60*5),
         ('30days', 60*60*24*30),
@@ -221,16 +226,15 @@
         self.checkSessionFlash(response, 'API key successfully created')
         response = response.follow()
 
-        #now delete our key
+        # now delete our key
         keys = UserApiKeys.query().all()
-        self.assertEqual(1, len(keys))
+        assert 1 == len(keys)
 
-        response = self.app.post(url('my_account_api_keys'),
-                 {'_method': 'delete', 'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('my_account_api_keys_delete'),
+                 {'del_api_key': keys[0].api_key, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully deleted')
         keys = UserApiKeys.query().all()
-        self.assertEqual(0, len(keys))
-
+        assert 0 == len(keys)
 
     def test_my_account_reset_main_api_key(self):
         usr = self.log_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS)
@@ -240,8 +244,8 @@
         response.mustcontain(api_key)
         response.mustcontain('Expires: Never')
 
-        response = self.app.post(url('my_account_api_keys'),
-                 {'_method': 'delete', 'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
+        response = self.app.post(url('my_account_api_keys_delete'),
+                 {'del_api_key_builtin': api_key, '_authentication_token': self.authentication_token()})
         self.checkSessionFlash(response, 'API key successfully reset')
         response = response.follow()
         response.mustcontain(no=[api_key])
--- a/kallithea/tests/functional/test_pullrequests.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_pullrequests.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,18 @@
 import re
+import pytest
 
-from kallithea.tests import *
+from tg.util.webtest import test_context
+
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
+from kallithea.model.db import PullRequest, User
 from kallithea.model.meta import Session
 
 from kallithea.controllers.pullrequests import PullrequestsController
 
 fixture = Fixture()
 
+
 class TestPullrequestsController(TestController):
 
     def test_index(self):
@@ -20,55 +25,118 @@
         response = self.app.post(url(controller='pullrequests', action='create',
                                      repo_name=HG_REPO),
                                  {'org_repo': HG_REPO,
-                                  'org_ref': 'branch:default:default',
+                                  'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'other_repo': HG_REPO,
-                                  'other_ref': 'branch:default:default',
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_authentication_token': self.authentication_token(),
-                                 }
-                                )
-        self.assertEqual(response.status, '302 Found')
+                                 },
+                                 status=302)
         response = response.follow()
-        self.assertEqual(response.status, '200 OK')
-        response.mustcontain('This pull request has already been merged to default.')
+        assert response.status == '200 OK'
+        response.mustcontain('Successfully opened new pull request')
+        response.mustcontain('No additional changesets found for iterating on this pull request')
+        response.mustcontain('href="/vcs_test_hg/changeset/4f7e2131323e0749a740c0a56ab68ae9269c562a"')
 
-    def test_create_with_existing_reviewer(self):
+    def test_available(self):
+        self.log_user()
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {'org_repo': HG_REPO,
+                                  'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 },
+                                 status=302)
+        response = response.follow()
+        assert response.status == '200 OK'
+        response.mustcontain(no='No additional changesets found for iterating on this pull request')
+        response.mustcontain('The following additional changes are available on stable:')
+        response.mustcontain('<input id="updaterev_4f7e2131323e0749a740c0a56ab68ae9269c562a" name="updaterev" type="radio" value="4f7e2131323e0749a740c0a56ab68ae9269c562a" />')
+        response.mustcontain('href="/vcs_test_hg/changeset/4f7e2131323e0749a740c0a56ab68ae9269c562a"') # as update
+
+    def test_range(self):
         self.log_user()
         response = self.app.post(url(controller='pullrequests', action='create',
                                      repo_name=HG_REPO),
                                  {'org_repo': HG_REPO,
-                                  'org_ref': 'branch:default:default',
+                                  'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'other_repo': HG_REPO,
-                                  'other_ref': 'branch:default:default',
+                                  'other_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
+                                  'pullrequest_title': 'title',
+                                  'pullrequest_desc': 'description',
+                                  '_authentication_token': self.authentication_token(),
+                                 },
+                                 status=302)
+        response = response.follow()
+        assert response.status == '200 OK'
+        response.mustcontain('No additional changesets found for iterating on this pull request')
+        response.mustcontain('href="/vcs_test_hg/changeset/4f7e2131323e0749a740c0a56ab68ae9269c562a"')
+
+    def test_update_reviewers(self):
+        self.log_user()
+        regular_user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
+        regular_user2 = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
+        admin_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+
+        # create initial PR
+        response = self.app.post(url(controller='pullrequests', action='create',
+                                     repo_name=HG_REPO),
+                                 {'org_repo': HG_REPO,
+                                  'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
+                                  'other_repo': HG_REPO,
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_authentication_token': self.authentication_token(),
-                                  'review_members': TEST_USER_ADMIN_LOGIN,
-                                 }
-                                )
-        self.assertEqual(response.status, '302 Found')
-        response = response.follow()
-        self.assertEqual(response.status, '200 OK')
-        response.mustcontain('This pull request has already been merged to default.')
+                                 },
+                                 status=302)
+        pull_request1_id = re.search('/pull-request/(\d+)/', response.location).group(1)
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request1_id)
 
-    def test_create_with_invalid_reviewer(self):
-        invalid_user_name = 'invalid_user'
-        self.log_user()
-        response = self.app.post(url(controller='pullrequests', action='create',
-                                     repo_name=HG_REPO),
+        # create new iteration
+        response = self.app.post(url(controller='pullrequests', action='post',
+                                     repo_name=HG_REPO, pull_request_id=pull_request1_id),
                                  {
-                                  'org_repo': HG_REPO,
-                                  'org_ref': 'branch:default:default',
-                                  'other_repo': HG_REPO,
-                                  'other_ref': 'branch:default:default',
+                                  'updaterev': '4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
+                                  'owner': TEST_USER_ADMIN_LOGIN,
                                   '_authentication_token': self.authentication_token(),
-                                  'review_members': invalid_user_name,
+                                  'review_members': [regular_user.user_id],
                                  },
-                                 status=400)
-        response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_name)
+                                 status=302)
+        pull_request2_id = re.search('/pull-request/(\d+)/', response.location).group(1)
+        assert pull_request2_id != pull_request1_id
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request2_id)
+        response = response.follow()
+        # verify reviewer was added
+        response.mustcontain('<input type="hidden" value="%s" name="review_members" />' % regular_user.user_id)
+
+        # update without creating new iteration
+        response = self.app.post(url(controller='pullrequests', action='post',
+                                     repo_name=HG_REPO, pull_request_id=pull_request2_id),
+                                 {
+                                  'pullrequest_title': 'Title',
+                                  'pullrequest_desc': 'description',
+                                  'owner': TEST_USER_ADMIN_LOGIN,
+                                  '_authentication_token': self.authentication_token(),
+                                  'org_review_members': [admin_user.user_id], # fake - just to get some 'meanwhile' warning ... but it is also added ...
+                                  'review_members': [regular_user2.user_id, admin_user.user_id],
+                                 },
+                                 status=302)
+        assert response.location == 'http://localhost/%s/pull-request/%s/_/stable' % (HG_REPO, pull_request2_id)
+        response = response.follow()
+        # verify reviewers were added / removed
+        response.mustcontain('Meanwhile, the following reviewers have been added: test_regular')
+        response.mustcontain('Meanwhile, the following reviewers have been removed: test_admin')
+        response.mustcontain('<input type="hidden" value="%s" name="review_members" />' % regular_user.user_id)
+        response.mustcontain('<input type="hidden" value="%s" name="review_members" />' % regular_user2.user_id)
+        response.mustcontain(no='<input type="hidden" value="%s" name="review_members" />' % admin_user.user_id)
 
     def test_update_with_invalid_reviewer(self):
         invalid_user_id = 99999
@@ -78,31 +146,30 @@
                                      repo_name=HG_REPO),
                                  {
                                   'org_repo': HG_REPO,
-                                  'org_ref': 'branch:default:default',
+                                  'org_ref': 'rev:94f45ed825a1:94f45ed825a113e61af7e141f44ca578374abef0',
                                   'other_repo': HG_REPO,
-                                  'other_ref': 'branch:default:default',
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_authentication_token': self.authentication_token(),
-                                 }
-                                )
-        self.assertEqual(response.status, '302 Found')
+                                 },
+                                status=302)
         # location is of the form:
         # http://localhost/vcs_test_hg/pull-request/54/_/title
         m = re.search('/pull-request/(\d+)/', response.location)
-        self.assertNotEqual(m, None)
+        assert m is not None
         pull_request_id = m.group(1)
 
         # update it
         response = self.app.post(url(controller='pullrequests', action='post',
                                      repo_name=HG_REPO, pull_request_id=pull_request_id),
                                  {
-                                  'updaterev': 'default',
+                                  'updaterev': '4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   'owner': TEST_USER_ADMIN_LOGIN,
                                   '_authentication_token': self.authentication_token(),
-                                  'review_members': invalid_user_id,
+                                  'review_members': [str(invalid_user_id)],
                                  },
                                  status=400)
         response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
@@ -115,19 +182,18 @@
                                      repo_name=HG_REPO),
                                  {
                                   'org_repo': HG_REPO,
-                                  'org_ref': 'branch:default:default',
+                                  'org_ref': 'branch:stable:4f7e2131323e0749a740c0a56ab68ae9269c562a',
                                   'other_repo': HG_REPO,
-                                  'other_ref': 'branch:default:default',
+                                  'other_ref': 'branch:default:96507bd11ecc815ebc6270fdf6db110928c09c1e',
                                   'pullrequest_title': 'title',
                                   'pullrequest_desc': 'description',
                                   '_authentication_token': self.authentication_token(),
-                                 }
-                                )
-        self.assertEqual(response.status, '302 Found')
+                                 },
+                                status=302)
         # location is of the form:
         # http://localhost/vcs_test_hg/pull-request/54/_/title
         m = re.search('/pull-request/(\d+)/', response.location)
-        self.assertNotEqual(m, None)
+        assert m is not None
         pull_request_id = m.group(1)
 
         # edit it
@@ -138,96 +204,178 @@
                                   'pullrequest_desc': 'description',
                                   'owner': TEST_USER_ADMIN_LOGIN,
                                   '_authentication_token': self.authentication_token(),
-                                  'review_members': invalid_user_id,
+                                  'review_members': [str(invalid_user_id)],
                                  },
                                  status=400)
         response.mustcontain('Invalid reviewer &#34;%s&#34; specified' % invalid_user_id)
 
+    def test_iteration_refs(self):
+        # Repo graph excerpt:
+        #   o   fb95b340e0d0 webvcs
+        #  /:
+        # o :   41d2568309a0 default
+        # : :
+        # : o   5ec21f21aafe webvcs
+        # : :
+        # : o   9e6119747791 webvcs
+        # : :
+        # o :   3d1091ee5a53 default
+        # :/
+        # o     948da46b29c1 default
+
+        self.log_user()
+
+        # create initial PR
+        response = self.app.post(
+            url(controller='pullrequests', action='create', repo_name=HG_REPO),
+            {
+                'org_repo': HG_REPO,
+                'org_ref': 'rev:9e6119747791:9e6119747791ff886a5abe1193a730b6bf874e1c',
+                'other_repo': HG_REPO,
+                'other_ref': 'branch:default:3d1091ee5a533b1f4577ec7d8a226bb315fb1336',
+                'pullrequest_title': 'title',
+                'pullrequest_desc': 'description',
+                '_authentication_token': self.authentication_token(),
+            },
+            status=302)
+        pr1_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr1 = PullRequest.get(pr1_id)
+
+        assert pr1.org_ref == 'branch:webvcs:9e6119747791ff886a5abe1193a730b6bf874e1c'
+        assert pr1.other_ref == 'branch:default:948da46b29c125838a717f6a8496eb409717078d'
+
+        Session().rollback() # invalidate loaded PR objects before issuing next request.
+
+        # create PR 2 (new iteration with same ancestor)
+        response = self.app.post(
+            url(controller='pullrequests', action='post', repo_name=HG_REPO, pull_request_id=pr1_id),
+            {
+                'updaterev': '5ec21f21aafe95220f1fc4843a4a57c378498b71',
+                'pullrequest_title': 'title',
+                'pullrequest_desc': 'description',
+                'owner': TEST_USER_REGULAR_LOGIN,
+                '_authentication_token': self.authentication_token(),
+             },
+             status=302)
+        pr2_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr1 = PullRequest.get(pr1_id)
+        pr2 = PullRequest.get(pr2_id)
+
+        assert pr2_id != pr1_id
+        assert pr1.status == PullRequest.STATUS_CLOSED
+        assert pr2.org_ref == 'branch:webvcs:5ec21f21aafe95220f1fc4843a4a57c378498b71'
+        assert pr2.other_ref == pr1.other_ref
+
+        Session().rollback() # invalidate loaded PR objects before issuing next request.
+
+        # create PR 3 (new iteration with new ancestor)
+        response = self.app.post(
+            url(controller='pullrequests', action='post', repo_name=HG_REPO, pull_request_id=pr2_id),
+            {
+                'updaterev': 'fb95b340e0d03fa51f33c56c991c08077c99303e',
+                'pullrequest_title': 'title',
+                'pullrequest_desc': 'description',
+                'owner': TEST_USER_REGULAR_LOGIN,
+                '_authentication_token': self.authentication_token(),
+             },
+             status=302)
+        pr3_id = int(re.search('/pull-request/(\d+)/', response.location).group(1))
+        pr2 = PullRequest.get(pr2_id)
+        pr3 = PullRequest.get(pr3_id)
+
+        assert pr3_id != pr2_id
+        assert pr2.status == PullRequest.STATUS_CLOSED
+        assert pr3.org_ref == 'branch:webvcs:fb95b340e0d03fa51f33c56c991c08077c99303e'
+        assert pr3.other_ref == 'branch:default:41d2568309a05f422cffb8008e599d385f8af439'
+
+
+@pytest.mark.usefixtures("test_context_fixture") # apply fixture for all test methods
 class TestPullrequestsGetRepoRefs(TestController):
 
-    def setUp(self):
-        self.main = fixture.create_repo('main', repo_type='hg')
-        Session.add(self.main)
-        Session.commit()
+    def setup_method(self, method):
+        self.repo_name = u'main'
+        repo = fixture.create_repo(self.repo_name, repo_type='hg')
+        self.repo_scm_instance = repo.scm_instance
+        Session().commit()
         self.c = PullrequestsController()
 
-    def tearDown(self):
-        fixture.destroy_repo('main')
-        Session.commit()
+    def teardown_method(self, method):
+        fixture.destroy_repo(u'main')
+        Session().commit()
         Session.remove()
 
     def test_repo_refs_empty_repo(self):
         # empty repo with no commits, no branches, no bookmarks, just one tag
-        refs, default = self.c._get_repo_refs(self.main.scm_instance)
-        self.assertEqual(default, 'tag:null:0000000000000000000000000000000000000000')
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance)
+        assert default == 'tag:null:0000000000000000000000000000000000000000'
 
     def test_repo_refs_one_commit_no_hints(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance)
-        self.assertEqual(default, 'branch:default:%s' % cs0.raw_id)
-        self.assertIn(([('branch:default:%s' % cs0.raw_id, 'default (current tip)')],
-                'Branches'), refs)
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance)
+        assert default == 'branch:default:%s' % cs0.raw_id
+        assert ([('branch:default:%s' % cs0.raw_id, 'default (current tip)')],
+                'Branches') in refs
 
     def test_repo_refs_one_commit_rev_hint(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance, rev=cs0.raw_id)
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance, rev=cs0.raw_id)
         expected = 'branch:default:%s' % cs0.raw_id
-        self.assertEqual(default, expected)
-        self.assertIn(([(expected, 'default (current tip)')], 'Branches'), refs)
+        assert default == expected
+        assert ([(expected, 'default (current tip)')], 'Branches') in refs
 
     def test_repo_refs_two_commits_no_hints(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
-        cs1 = fixture.commit_change(self.main.repo_name, filename='file2',
+        cs1 = fixture.commit_change(self.repo_name, filename='file2',
                 content='line2\n', message='commit2', vcs_type='hg',
                 parent=None, newfile=True)
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance)
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance)
         expected = 'branch:default:%s' % cs1.raw_id
-        self.assertEqual(default, expected)
-        self.assertIn(([(expected, 'default (current tip)')], 'Branches'), refs)
+        assert default == expected
+        assert ([(expected, 'default (current tip)')], 'Branches') in refs
 
     def test_repo_refs_two_commits_rev_hints(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
-        cs1 = fixture.commit_change(self.main.repo_name, filename='file2',
+        cs1 = fixture.commit_change(self.repo_name, filename='file2',
                 content='line2\n', message='commit2', vcs_type='hg',
                 parent=None, newfile=True)
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance, rev=cs0.raw_id)
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance, rev=cs0.raw_id)
         expected = 'rev:%s:%s' % (cs0.raw_id, cs0.raw_id)
-        self.assertEqual(default, expected)
-        self.assertIn(([(expected, 'Changeset: %s' % cs0.raw_id[0:12])], 'Special'), refs)
-        self.assertIn(([('branch:default:%s' % cs1.raw_id, 'default (current tip)')], 'Branches'), refs)
+        assert default == expected
+        assert ([(expected, 'Changeset: %s' % cs0.raw_id[0:12])], 'Special') in refs
+        assert ([('branch:default:%s' % cs1.raw_id, 'default (current tip)')], 'Branches') in refs
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance, rev=cs1.raw_id)
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance, rev=cs1.raw_id)
         expected = 'branch:default:%s' % cs1.raw_id
-        self.assertEqual(default, expected)
-        self.assertIn(([(expected, 'default (current tip)')], 'Branches'), refs)
+        assert default == expected
+        assert ([(expected, 'default (current tip)')], 'Branches') in refs
 
     def test_repo_refs_two_commits_branch_hint(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
-        cs1 = fixture.commit_change(self.main.repo_name, filename='file2',
+        cs1 = fixture.commit_change(self.repo_name, filename='file2',
                 content='line2\n', message='commit2', vcs_type='hg',
                 parent=None, newfile=True)
 
-        refs, default = self.c._get_repo_refs(self.main.scm_instance, branch='default')
+        refs, default = self.c._get_repo_refs(self.repo_scm_instance, branch='default')
         expected = 'branch:default:%s' % cs1.raw_id
-        self.assertEqual(default, expected)
-        self.assertIn(([(expected, 'default (current tip)')], 'Branches'), refs)
+        assert default == expected
+        assert ([(expected, 'default (current tip)')], 'Branches') in refs
 
     def test_repo_refs_one_branch_no_hints(self):
-        cs0 = fixture.commit_change(self.main.repo_name, filename='file1',
+        cs0 = fixture.commit_change(self.repo_name, filename='file1',
                 content='line1\n', message='commit1', vcs_type='hg',
                 parent=None, newfile=True)
         # TODO
--- a/kallithea/tests/functional/test_repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 
 class TestRepoGroupsController(TestController):
@@ -6,28 +6,72 @@
     def test_index(self):
         self.log_user()
         response = self.app.get(url('repos_groups'))
-        response.mustcontain('{"totalRecords": 0, "sort": null, "startIndex": 0, "dir": "asc", "records": []};')
-
-#    def test_create(self):
-#        response = self.app.post(url('repos_groups'))
+        response.mustcontain('"records": []')
 
     def test_new(self):
         self.log_user()
         response = self.app.get(url('new_repos_group'))
 
+    def test_create(self):
+        self.log_user()
+
+        group_name = 'foo'
+
+        # creation with form error
+        response = self.app.post(url('repos_groups'),
+                                         {'group_name': group_name,
+                                          '_authentication_token': self.authentication_token()})
+        response.mustcontain('name="group_name" type="text" value="%s"' % group_name)
+        response.mustcontain('<!-- for: group_description -->')
+
+        # creation
+        response = self.app.post(url('repos_groups'),
+                                         {'group_name': group_name,
+                                         'group_description': 'lala',
+                                         'parent_group_id': '-1',
+                                         'group_copy_permissions': 'True',
+                                          '_authentication_token': self.authentication_token()})
+        self.checkSessionFlash(response, 'Created repository group %s' % group_name)
+
+        # edit form
+        response = self.app.get(url('edit_repo_group', group_name=group_name))
+        response.mustcontain('>lala<')
+
+        # edit with form error
+        response = self.app.post(url('update_repos_group', group_name=group_name),
+                                         {'group_name': group_name,
+                                          '_authentication_token': self.authentication_token()})
+        response.mustcontain('name="group_name" type="text" value="%s"' % group_name)
+        response.mustcontain('<!-- for: group_description -->')
+
+        # edit
+        response = self.app.post(url('update_repos_group', group_name=group_name),
+                                         {'group_name': group_name,
+                                         'group_description': 'lolo',
+                                          '_authentication_token': self.authentication_token()})
+        self.checkSessionFlash(response, 'Updated repository group %s' % group_name)
+        response = response.follow()
+        response.mustcontain('name="group_name" type="text" value="%s"' % group_name)
+        response.mustcontain(no='<!-- for: group_description -->')
+        response.mustcontain('>lolo<')
+
+        # listing
+        response = self.app.get(url('repos_groups'))
+        response.mustcontain('raw_name": "%s"' % group_name)
+
+        # show
+        response = self.app.get(url('repos_group', group_name=group_name))
+        response.mustcontain('href="/_admin/repo_groups/%s/edit"' % group_name)
+
+        # show ignores extra trailing slashes in the URL
+        response = self.app.get(url('repos_group', group_name='%s//' % group_name))
+        response.mustcontain('href="/_admin/repo_groups/%s/edit"' % group_name)
+
+        # delete
+        response = self.app.post(url('delete_repo_group', group_name=group_name),
+                                 {'_authentication_token': self.authentication_token()})
+        self.checkSessionFlash(response, 'Removed repository group %s' % group_name)
+
     def test_new_by_regular_user(self):
         self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
         response = self.app.get(url('new_repos_group'), status=403)
-#
-#    def test_update(self):
-#        response = self.app.put(url('repos_group', group_name=1))
-#
-#    def test_delete(self):
-#        self.log_user()
-#        response = self.app.delete(url('repos_group', group_name=1))
-#
-#    def test_show(self):
-#        response = self.app.get(url('repos_group', group_name=1))
-#
-#    def test_edit(self):
-#        response = self.app.get(url('edit_repo_group', group_name=1))
--- a/kallithea/tests/functional/test_search.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_search.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,5 +1,6 @@
+import mock
 import os
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 
 class TestSearchController(TestController):
@@ -8,18 +9,22 @@
         self.log_user()
         response = self.app.get(url(controller='search', action='index'))
 
-        response.mustcontain('class="small" id="q" name="q" type="text"')
+        response.mustcontain('class="form-control" id="q" name="q" type="text"')
         # Test response...
 
-    def test_empty_search(self):
-        if os.path.isdir(self.index_location):
-            raise SkipTest('skipped due to existing index')
-        else:
-            self.log_user()
+    def test_empty_search(self, tmpdir):
+        self.log_user()
+
+        config_mock = {
+            'app_conf': {
+                # can be any existing dir that does not contain an actual index
+                'index_dir': str(tmpdir),
+            }
+        }
+        with mock.patch('kallithea.controllers.search.config', config_mock):
             response = self.app.get(url(controller='search', action='index'),
                                     {'q': HG_REPO})
-            response.mustcontain('There is no index to search in. '
-                                 'Please run whoosh indexer')
+            response.mustcontain('The server has no search index.')
 
     def test_normal_search(self):
         self.log_user()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/functional/test_search_indexing.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,187 @@
+import mock
+
+from kallithea import CONFIG
+from kallithea.config.conf import INDEX_FILENAMES
+from kallithea.model.meta import Session
+from kallithea.model.repo import RepoModel
+from kallithea.model.repo_group import RepoGroupModel
+from kallithea.tests.base import *
+from kallithea.tests.fixture import create_test_index, Fixture
+
+fixture = Fixture()
+
+
+def init_indexing_test(repo):
+    prev = fixture.commit_change(repo.repo_name,
+                                 filename='this_should_be_unique_filename.txt',
+                                 content='this_should_be_unique_content\n',
+                                 message='this_should_be_unique_commit_log',
+                                 vcs_type='hg',
+                                 newfile=True)
+
+def init_stopword_test(repo):
+    prev = fixture.commit_change(repo.repo_name,
+                                 filename='this/is/it',
+                                 content='def test\n',
+                                 message='bother to ask where - in folder',
+                                 vcs_type='hg',
+                                 newfile=True)
+    prev = fixture.commit_change(repo.repo_name,
+                                 filename='join.us',
+                                 content='def test\n',
+                                 message='bother to ask where - top level',
+                                 author='this is it <this-is-it@foo.bar.com>',
+                                 vcs_type='hg',
+                                 parent=prev,
+                                 newfile=True)
+
+
+repos = [
+    # reponame,              init func or fork base, groupname
+    (u'indexing_test',       init_indexing_test,     None),
+    (u'indexing_test-fork',  u'indexing_test',       None),
+    (u'group/indexing_test', u'indexing_test',       u'group'),
+    (u'this-is-it',          u'indexing_test',       None),
+    (u'indexing_test-foo',   u'indexing_test',       None),
+    (u'stopword_test',       init_stopword_test,     None),
+]
+
+
+# map: name => id
+repoids = {}
+groupids = {}
+
+
+def rebuild_index(full_index):
+    with mock.patch('kallithea.lib.indexers.daemon.log.debug',
+                    lambda *args, **kwargs: None):
+        # The more revisions managed repositories have, the more
+        # memory capturing "log.debug()" output in "indexers.daemon"
+        # requires. This may cause unintentional failure of subsequent
+        # tests, if ENOMEM at forking "git" prevents from rebuilding
+        # index for search.
+        # Therefore, "log.debug()" is disabled regardless of logging
+        # level while rebuilding index.
+        # (FYI, ENOMEM occurs at forking "git" with python 2.7.3,
+        # Linux 3.2.78-1 x86_64, 3GB memory, and no ulimit
+        # configuration for memory)
+        create_test_index(TESTS_TMP_PATH, CONFIG, full_index=full_index)
+
+
+class TestSearchControllerIndexing(TestController):
+    @classmethod
+    def setup_class(cls):
+        for reponame, init_or_fork, groupname in repos:
+            if groupname and groupname not in groupids:
+                group = fixture.create_repo_group(groupname)
+                groupids[groupname] = group.group_id
+            if callable(init_or_fork):
+                repo = fixture.create_repo(reponame,
+                                           repo_group=groupname)
+                init_or_fork(repo)
+            else:
+                repo = fixture.create_fork(init_or_fork, reponame,
+                                           repo_group=groupname)
+            repoids[reponame] = repo.repo_id
+
+        # treat "it" as indexable filename
+        filenames_mock = list(INDEX_FILENAMES)
+        filenames_mock.append('it')
+        with mock.patch('kallithea.lib.indexers.daemon.INDEX_FILENAMES',
+                        filenames_mock):
+            rebuild_index(full_index=False) # only for newly added repos
+
+    @classmethod
+    def teardown_class(cls):
+        # delete in reversed order, to delete fork destination at first
+        for reponame, init_or_fork, groupname in reversed(repos):
+            RepoModel().delete(repoids[reponame])
+
+        for reponame, init_or_fork, groupname in reversed(repos):
+            if groupname in groupids:
+                RepoGroupModel().delete(groupids.pop(groupname),
+                                        force_delete=True)
+
+        Session().commit()
+        Session.remove()
+
+        rebuild_index(full_index=True) # rebuild fully for subsequent tests
+
+    @parametrize('reponame', [
+        (u'indexing_test'),
+        (u'indexing_test-fork'),
+        (u'group/indexing_test'),
+        (u'this-is-it'),
+        (u'*-fork'),
+        (u'group/*'),
+    ])
+    @parametrize('searchtype,query,hit', [
+        ('content', 'this_should_be_unique_content', 1),
+        ('commit', 'this_should_be_unique_commit_log', 1),
+        ('path', 'this_should_be_unique_filename.txt', 1),
+    ])
+    def test_repository_tokenization(self, reponame, searchtype, query, hit):
+        self.log_user()
+
+        q = 'repository:%s %s' % (reponame, query)
+        response = self.app.get(url(controller='search', action='index'),
+                                {'q': q, 'type': searchtype})
+        response.mustcontain('>%d results' % hit)
+
+    @parametrize('reponame', [
+        (u'indexing_test'),
+        (u'indexing_test-fork'),
+        (u'group/indexing_test'),
+        (u'this-is-it'),
+    ])
+    @parametrize('searchtype,query,hit', [
+        ('content', 'this_should_be_unique_content', 1),
+        ('commit', 'this_should_be_unique_commit_log', 1),
+        ('path', 'this_should_be_unique_filename.txt', 1),
+    ])
+    def test_searching_under_repository(self, reponame, searchtype, query, hit):
+        self.log_user()
+
+        response = self.app.get(url(controller='search', action='index',
+                                    repo_name=reponame),
+                                {'q': query, 'type': searchtype})
+        response.mustcontain('>%d results' % hit)
+
+    @parametrize('searchtype,query,hit', [
+        ('content', 'path:this/is/it def test', 1),
+        ('commit', 'added:this/is/it bother to ask where', 1),
+        # this condition matches against files below, because
+        # "path:" condition is also applied on "repository path".
+        # - "this/is/it" in "stopword_test" repo
+        # - "this_should_be_unique_filename.txt" in "this-is-it" repo
+        ('path', 'this/is/it', 2),
+
+        ('content', 'extension:us', 1),
+        ('path', 'extension:us', 1),
+    ])
+    def test_filename_stopword(self, searchtype, query, hit):
+        response = self.app.get(url(controller='search', action='index'),
+                                {'q': query, 'type': searchtype})
+
+        response.mustcontain('>%d results' % hit)
+
+    @parametrize('searchtype,query,hit', [
+        # matching against both 2 files
+        ('content', 'owner:"this is it"', 0),
+        ('content', 'owner:this-is-it', 0),
+        ('path', 'owner:"this is it"', 0),
+        ('path', 'owner:this-is-it', 0),
+
+        # matching against both 2 revisions
+        ('commit', 'owner:"this is it"', 0),
+        ('commit', 'owner:"this-is-it"', 0),
+
+        # matching against only 1 revision
+        ('commit', 'author:"this is it"', 1),
+        ('commit', 'author:"this-is-it"', 1),
+    ])
+    def test_mailaddr_stopword(self, searchtype, query, hit):
+        response = self.app.get(url(controller='search', action='index'),
+                                {'q': query, 'type': searchtype})
+
+        response.mustcontain('>%d results' % hit)
--- a/kallithea/tests/functional/test_summary.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/functional/test_summary.py	Sun Mar 31 21:28:56 2019 +0200
@@ -12,7 +12,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.model.db import Repository
 from kallithea.model.repo import RepoModel
@@ -24,25 +24,31 @@
 
 class TestSummaryController(TestController):
 
-    def test_index(self):
+    def test_index_hg(self):
         self.log_user()
         ID = Repository.get_by_repo_name(HG_REPO).repo_id
         response = self.app.get(url(controller='summary',
                                     action='index',
                                     repo_name=HG_REPO))
 
-        #repo type
+        # repo type
         response.mustcontain(
-            """<span class="repotag">hg"""
+            """<span class="label label-repo" title="Mercurial repository">hg"""
         )
-        #public/private
+        # public/private
         response.mustcontain(
             """<i class="icon-globe">"""
         )
 
         # clone url...
-        response.mustcontain('''id="clone_url" readonly="readonly" value="http://%s@localhost:80/%s"''' % (TEST_USER_ADMIN_LOGIN, HG_REPO))
-        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://%s@localhost:80/_%s"''' % (TEST_USER_ADMIN_LOGIN, ID))
+        response.mustcontain(
+            '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/%s"/>''' %
+            (TEST_USER_ADMIN_LOGIN, HG_REPO)
+        )
+        response.mustcontain(
+            '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/_%s"/>''' %
+            (TEST_USER_ADMIN_LOGIN, ID)
+        )
 
     def test_index_git(self):
         self.log_user()
@@ -51,18 +57,23 @@
                                     action='index',
                                     repo_name=GIT_REPO))
 
-        #repo type
+        # repo type
         response.mustcontain(
-            """<span class="repotag">git"""
+            """<span class="label label-repo" title="Git repository">git"""
         )
-        #public/private
+        # public/private
         response.mustcontain(
             """<i class="icon-globe">"""
         )
 
         # clone url...
-        response.mustcontain('''id="clone_url" readonly="readonly" value="http://%s@localhost:80/%s"''' % (TEST_USER_ADMIN_LOGIN, GIT_REPO))
-        response.mustcontain('''id="clone_url_id" readonly="readonly" value="http://%s@localhost:80/_%s"''' % (TEST_USER_ADMIN_LOGIN, ID))
+        response.mustcontain(
+            '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/%s"/>''' %
+            (TEST_USER_ADMIN_LOGIN, GIT_REPO))
+        response.mustcontain(
+            '''<input class="form-control" size="80" readonly="readonly" value="http://%s@localhost:80/_%s"/>''' %
+            (TEST_USER_ADMIN_LOGIN, ID)
+        )
 
     def test_index_by_id_hg(self):
         self.log_user()
@@ -71,18 +82,18 @@
                                     action='index',
                                     repo_name='_%s' % ID))
 
-        #repo type
+        # repo type
         response.mustcontain(
-            """<span class="repotag">hg"""
+            """<span class="label label-repo" title="Mercurial repository">hg"""
         )
-        #public/private
+        # public/private
         response.mustcontain(
             """<i class="icon-globe">"""
         )
 
     def test_index_by_repo_having_id_path_in_name_hg(self):
         self.log_user()
-        fixture.create_repo(name='repo_1')
+        fixture.create_repo(name=u'repo_1')
         response = self.app.get(url(controller='summary',
                                     action='index',
                                     repo_name='repo_1'))
@@ -90,7 +101,7 @@
         try:
             response.mustcontain("repo_1")
         finally:
-            RepoModel().delete(Repository.get_by_repo_name('repo_1'))
+            RepoModel().delete(Repository.get_by_repo_name(u'repo_1'))
             Session().commit()
 
     def test_index_by_id_git(self):
@@ -100,11 +111,11 @@
                                     action='index',
                                     repo_name='_%s' % ID))
 
-        #repo type
+        # repo type
         response.mustcontain(
-            """<span class="repotag">git"""
+            """<span class="label label-repo" title="Git repository">git"""
         )
-        #public/private
+        # public/private
         response.mustcontain(
             """<i class="icon-globe">"""
         )
@@ -112,15 +123,17 @@
     def _enable_stats(self, repo):
         r = Repository.get_by_repo_name(repo)
         r.enable_statistics = True
-        Session().add(r)
         Session().commit()
 
     def test_index_trending(self):
         self.log_user()
-        #codes stats
+        # codes stats
         self._enable_stats(HG_REPO)
 
         ScmModel().mark_for_invalidation(HG_REPO)
+        # generate statistics first
+        response = self.app.get(url(controller='summary', action='statistics',
+                                    repo_name=HG_REPO))
         response = self.app.get(url(controller='summary', action='index',
                                     repo_name=HG_REPO))
         response.mustcontain(
@@ -133,12 +146,12 @@
             '["js", {"count": 1, "desc": ["Javascript"]}], '
             '["cfg", {"count": 1, "desc": ["Ini"]}], '
             '["ini", {"count": 1, "desc": ["Ini"]}], '
-            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]];'
+            '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}]]'
         )
 
     def test_index_statistics(self):
         self.log_user()
-        #codes stats
+        # codes stats
         self._enable_stats(HG_REPO)
 
         ScmModel().mark_for_invalidation(HG_REPO)
@@ -147,10 +160,13 @@
 
     def test_index_trending_git(self):
         self.log_user()
-        #codes stats
+        # codes stats
         self._enable_stats(GIT_REPO)
 
         ScmModel().mark_for_invalidation(GIT_REPO)
+        # generate statistics first
+        response = self.app.get(url(controller='summary', action='statistics',
+                                    repo_name=GIT_REPO))
         response = self.app.get(url(controller='summary', action='index',
                                     repo_name=GIT_REPO))
         response.mustcontain(
@@ -163,12 +179,12 @@
             '["cfg", {"count": 1, "desc": ["Ini"]}], '
             '["ini", {"count": 1, "desc": ["Ini"]}], '
             '["html", {"count": 1, "desc": ["EvoqueHtml", "Html"]}], '
-            '["bat", {"count": 1, "desc": ["Batch"]}]];'
+            '["bat", {"count": 1, "desc": ["Batch"]}]]'
         )
 
     def test_index_statistics_git(self):
         self.log_user()
-        #codes stats
+        # codes stats
         self._enable_stats(GIT_REPO)
 
         ScmModel().mark_for_invalidation(GIT_REPO)
--- a/kallithea/tests/functional/test_tags.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-from kallithea.tests import *
-
-
-class TestTagsController(TestController):
-
-    def test_index_hg(self):
-        self.log_user()
-        response = self.app.get(url(controller='tags', action='index', repo_name=HG_REPO))
-        response.mustcontain("""<a href="/%s/changeset/96507bd11ecc815ebc6270fdf6db110928c09c1e">tip</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/2c96c02def9a7c997f33047761a53943e6254396">v0.2.0</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/fef5bfe1dc17611d5fb59a7f6f95c55c3606f933">v0.1.11</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/92831aebf2f8dd4879e897024b89d09af214df1c">v0.1.10</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/8680b1d1cee3aa3c1ab3734b76ee164bbedbc5c9">v0.1.9</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/ecb25ba9c96faf1e65a0bc3fd914918420a2f116">v0.1.8</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/f67633a2894edaf28513706d558205fa93df9209">v0.1.7</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/02b38c0eb6f982174750c0e309ff9faddc0c7e12">v0.1.6</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/a6664e18181c6fc81b751a8d01474e7e1a3fe7fc">v0.1.5</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">v0.1.4</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">v0.1.3</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">v0.1.2</a>""" % HG_REPO)
-        response.mustcontain("""<a href="/%s/changeset/eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">v0.1.1</a>""" % HG_REPO)
-
-    def test_index_git(self):
-        self.log_user()
-        response = self.app.get(url(controller='tags', action='index', repo_name=GIT_REPO))
-
-        response.mustcontain("""<a href="/%s/changeset/137fea89f304a42321d40488091ee2ed419a3686">v0.2.2</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/5051d0fa344d4408a2659d9a0348eb2d41868ecf">v0.2.1</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/599ba911aa24d2981225f3966eb659dfae9e9f30">v0.2.0</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/c60f01b77c42dce653d6b1d3b04689862c261929">v0.1.11</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/10cddef6b794696066fb346434014f0a56810218">v0.1.10</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/341d28f0eec5ddf0b6b77871e13c2bbd6bec685c">v0.1.9</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/74ebce002c088b8a5ecf40073db09375515ecd68">v0.1.8</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/4d78bf73b5c22c82b68f902f138f7881b4fffa2c">v0.1.7</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/0205cb3f44223fb3099d12a77a69c81b798772d9">v0.1.6</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/6c0ce52b229aa978889e91b38777f800e85f330b">v0.1.5</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/7d735150934cd7645ac3051903add952390324a5">v0.1.4</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/5a3a8fb005554692b16e21dee62bf02667d8dc3e">v0.1.3</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/0ba5f8a4660034ff25c0cac2a5baabf5d2791d63">v0.1.2</a>""" % GIT_REPO)
-        response.mustcontain("""<a href="/%s/changeset/e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0">v0.1.1</a>""" % GIT_REPO)
--- a/kallithea/tests/models/common.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/common.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 
 from kallithea.model.repo_group import RepoGroupModel
@@ -15,7 +15,7 @@
 
 def _destroy_project_tree(test_u1_id):
     Session.remove()
-    repo_group = RepoGroup.get_by_group_name(group_name='g0')
+    repo_group = RepoGroup.get_by_group_name(group_name=u'g0')
     for el in reversed(repo_group.recursive_groups_and_repos()):
         if isinstance(el, Repository):
             RepoModel().delete(el)
@@ -56,18 +56,18 @@
         username=u'test_u1', password=u'qweqwe',
         email=u'test_u1@example.com', firstname=u'test_u1', lastname=u'test_u1'
     )
-    g0 = fixture.create_repo_group('g0')
-    g0_1 = fixture.create_repo_group('g0_1', group_parent_id=g0)
-    g0_1_1 = fixture.create_repo_group('g0_1_1', group_parent_id=g0_1)
-    g0_1_1_r1 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repo_group=g0_1_1)
-    g0_1_1_r2 = fixture.create_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repo_group=g0_1_1)
-    g0_1_r1 = fixture.create_repo('g0/g0_1/g0_1_r1', repo_group=g0_1)
-    g0_2 = fixture.create_repo_group('g0_2', group_parent_id=g0)
-    g0_2_r1 = fixture.create_repo('g0/g0_2/g0_2_r1', repo_group=g0_2)
-    g0_2_r2 = fixture.create_repo('g0/g0_2/g0_2_r2', repo_group=g0_2)
-    g0_3 = fixture.create_repo_group('g0_3', group_parent_id=g0)
-    g0_3_r1 = fixture.create_repo('g0/g0_3/g0_3_r1', repo_group=g0_3)
-    g0_3_r2_private = fixture.create_repo('g0/g0_3/g0_3_r1_private',
+    g0 = fixture.create_repo_group(u'g0')
+    g0_1 = fixture.create_repo_group(u'g0_1', parent_group_id=g0)
+    g0_1_1 = fixture.create_repo_group(u'g0_1_1', parent_group_id=g0_1)
+    g0_1_1_r1 = fixture.create_repo(u'g0/g0_1/g0_1_1/g0_1_1_r1', repo_group=g0_1_1)
+    g0_1_1_r2 = fixture.create_repo(u'g0/g0_1/g0_1_1/g0_1_1_r2', repo_group=g0_1_1)
+    g0_1_r1 = fixture.create_repo(u'g0/g0_1/g0_1_r1', repo_group=g0_1)
+    g0_2 = fixture.create_repo_group(u'g0_2', parent_group_id=g0)
+    g0_2_r1 = fixture.create_repo(u'g0/g0_2/g0_2_r1', repo_group=g0_2)
+    g0_2_r2 = fixture.create_repo(u'g0/g0_2/g0_2_r2', repo_group=g0_2)
+    g0_3 = fixture.create_repo_group(u'g0_3', parent_group_id=g0)
+    g0_3_r1 = fixture.create_repo(u'g0/g0_3/g0_3_r1', repo_group=g0_3)
+    g0_3_r2_private = fixture.create_repo(u'g0/g0_3/g0_3_r1_private',
                                           repo_group=g0_3, repo_private=True)
     return test_u1
 
--- a/kallithea/tests/models/test_changeset_status.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_changeset_status.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,18 +1,20 @@
-from kallithea.tests import *
+from kallithea.tests.base 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):
+class TestChangesetStatusCalculation(TestController):
+
+    def setup_method(self, method):
         self.m = ChangesetStatusModel()
 
-    @parameterized.expand([
+    @parametrize('name,expected_result,statuses', [
         ('empty list', CS.STATUS_UNDER_REVIEW, []),
         ('approve', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED)]),
         ('approve2', CS.STATUS_APPROVED, [CSM(CS.STATUS_APPROVED), CSM(CS.STATUS_APPROVED)]),
@@ -37,4 +39,4 @@
     ])
     def test_result(self, name, expected_result, statuses):
         result = self.m._calculate_status(statuses)
-        self.assertEqual(result, expected_result)
+        assert result == expected_result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/models/test_comments.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,230 @@
+from kallithea.tests.base import *
+from kallithea.model.comment import ChangesetCommentsModel
+from kallithea.model.db import Repository
+
+import pytest
+from tg.util.webtest import test_context
+
+class TestComments(TestController):
+
+    def _check_comment_count(self, repo_id, revision,
+            expected_len_comments, expected_len_inline_comments,
+            f_path=None, line_no=None
+    ):
+        comments = ChangesetCommentsModel().get_comments(repo_id,
+                revision=revision)
+        assert len(comments) == expected_len_comments
+        inline_comments = ChangesetCommentsModel().get_inline_comments(repo_id,
+                revision=revision, f_path=f_path, line_no=line_no)
+        assert len(inline_comments) == expected_len_inline_comments
+
+        return comments, inline_comments
+
+    def test_create_delete_general_comment(self):
+        with test_context(self.app):
+            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+            text = u'a comment'
+            new_comment = ChangesetCommentsModel().create(
+                    text=text,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    send_email=False)
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=1, expected_len_inline_comments=0)
+
+            ChangesetCommentsModel().delete(new_comment)
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+    def test_create_delete_inline_comment(self):
+        with test_context(self.app):
+            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+            text = u'an inline comment'
+            f_path = u'vcs/tests/base.py'
+            line_no = u'n50'
+            new_comment = ChangesetCommentsModel().create(
+                    text=text,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path,
+                    line_no=line_no,
+                    send_email=False)
+
+            comments, inline_comments = self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=1)
+            # inline_comments is a list of tuples (file_path, dict)
+            # where the dict keys are line numbers and values are lists of comments
+            assert inline_comments[0][0] == f_path
+            assert len(inline_comments[0][1]) == 1
+            assert line_no in inline_comments[0][1]
+            assert inline_comments[0][1][line_no][0].text == text
+
+            ChangesetCommentsModel().delete(new_comment)
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+    def test_create_delete_multiple_inline_comments(self):
+        with test_context(self.app):
+            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+            text = u'an inline comment'
+            f_path = u'vcs/tests/base.py'
+            line_no = u'n50'
+            new_comment = ChangesetCommentsModel().create(
+                    text=text,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path,
+                    line_no=line_no,
+                    send_email=False)
+
+            text2 = u'another inline comment, same file'
+            line_no2 = u'o41'
+            new_comment2 = ChangesetCommentsModel().create(
+                    text=text2,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path,
+                    line_no=line_no2,
+                    send_email=False)
+
+            text3 = u'another inline comment, same file'
+            f_path3 = u'vcs/tests/test_hg.py'
+            line_no3 = u'n159'
+            new_comment3 = ChangesetCommentsModel().create(
+                    text=text3,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path3,
+                    line_no=line_no3,
+                    send_email=False)
+
+            comments, inline_comments = self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=2)
+            # inline_comments is a list of tuples (file_path, dict)
+            # where the dict keys are line numbers and values are lists of comments
+            assert inline_comments[1][0] == f_path
+            assert len(inline_comments[1][1]) == 2
+            assert inline_comments[1][1][line_no][0].text == text
+            assert inline_comments[1][1][line_no2][0].text == text2
+
+            assert inline_comments[0][0] == f_path3
+            assert len(inline_comments[0][1]) == 1
+            assert line_no3 in inline_comments[0][1]
+            assert inline_comments[0][1][line_no3][0].text == text3
+
+            # now delete only one comment
+            ChangesetCommentsModel().delete(new_comment2)
+
+            comments, inline_comments = self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=2)
+            # inline_comments is a list of tuples (file_path, dict)
+            # where the dict keys are line numbers and values are lists of comments
+            assert inline_comments[1][0] == f_path
+            assert len(inline_comments[1][1]) == 1
+            assert inline_comments[1][1][line_no][0].text == text
+
+            assert inline_comments[0][0] == f_path3
+            assert len(inline_comments[0][1]) == 1
+            assert line_no3 in inline_comments[0][1]
+            assert inline_comments[0][1][line_no3][0].text == text3
+
+            # now delete all others
+            ChangesetCommentsModel().delete(new_comment)
+            ChangesetCommentsModel().delete(new_comment3)
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+    def test_selective_retrieval_of_inline_comments(self):
+        with test_context(self.app):
+            repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
+            revision = '9a7b4ff9e8b40bbda72fc75f162325b9baa45cda'
+
+            self._check_comment_count(repo_id, revision,
+                    expected_len_comments=0, expected_len_inline_comments=0)
+
+            text = u'an inline comment'
+            f_path = u'vcs/tests/base.py'
+            line_no = u'n50'
+            new_comment = ChangesetCommentsModel().create(
+                    text=text,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path,
+                    line_no=line_no,
+                    send_email=False)
+
+            text2 = u'another inline comment, same file'
+            line_no2 = u'o41'
+            new_comment2 = ChangesetCommentsModel().create(
+                    text=text2,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path,
+                    line_no=line_no2,
+                    send_email=False)
+
+            text3 = u'another inline comment, same file'
+            f_path3 = u'vcs/tests/test_hg.py'
+            line_no3 = u'n159'
+            new_comment3 = ChangesetCommentsModel().create(
+                    text=text3,
+                    repo=HG_REPO,
+                    author=TEST_USER_REGULAR_LOGIN,
+                    revision=revision,
+                    f_path=f_path3,
+                    line_no=line_no3,
+                    send_email=False)
+
+            # now selectively retrieve comments of one file
+            comments, inline_comments = self._check_comment_count(repo_id, revision,
+                    f_path=f_path,
+                    expected_len_comments=0, expected_len_inline_comments=1)
+            # inline_comments is a list of tuples (file_path, dict)
+            # where the dict keys are line numbers and values are lists of comments
+            assert inline_comments[0][0] == f_path
+            assert len(inline_comments[0][1]) == 2
+            assert inline_comments[0][1][line_no][0].text == text
+            assert inline_comments[0][1][line_no2][0].text == text2
+
+            # now selectively retrieve comments of one file, one line
+            comments, inline_comments = self._check_comment_count(repo_id, revision,
+                    f_path=f_path, line_no=line_no2,
+                    expected_len_comments=0, expected_len_inline_comments=1)
+            # inline_comments is a list of tuples (file_path, dict)
+            # where the dict keys are line numbers and values are lists of comments
+            assert inline_comments[0][0] == f_path
+            assert len(inline_comments[0][1]) == 1
+            assert inline_comments[0][1][line_no2][0].text == text2
+
+            # verify that retrieval based on line_no but no f_path fails
+            with pytest.raises(Exception) as excinfo:
+                self._check_comment_count(repo_id, revision,
+                        f_path=None, line_no=line_no2,
+                        expected_len_comments=0, expected_len_inline_comments=0)
+            assert 'line_no only makes sense if f_path is given' in str(excinfo.value)
--- a/kallithea/tests/models/test_diff_parsers.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_diff_parsers.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.lib.diffs import DiffProcessor, NEW_FILENODE, DEL_FILENODE, \
     MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
 from kallithea.tests.fixture import Fixture
@@ -8,7 +8,7 @@
 
 DIFF_FIXTURES = {
     'hg_diff_add_single_binary_file.diff': [
-        ('US Warszawa.jpg', 'A',
+        ('US Warszawa.jpg', 'added',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -16,7 +16,7 @@
                   BIN_FILENODE: 'binary diff not shown'}}),
     ],
     'hg_diff_mod_single_binary_file.diff': [
-        ('US Warszawa.jpg', 'M',
+        ('US Warszawa.jpg', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -25,7 +25,7 @@
     ],
 
     'hg_diff_mod_single_file_and_rename_and_chmod.diff': [
-        ('README', 'R',
+        ('README', 'renamed',
          {'added': 3,
           'deleted': 0,
           'binary': False,
@@ -33,14 +33,14 @@
                   CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
     ],
     'hg_diff_mod_file_and_rename.diff': [
-        ('README.rst', 'R',
+        ('README.rst', 'renamed',
          {'added': 3,
           'deleted': 0,
           'binary': False,
           'ops': {RENAMED_FILENODE: 'file renamed from README to README.rst'}}),
     ],
     'hg_diff_del_single_binary_file.diff': [
-        ('US Warszawa.jpg', 'D',
+        ('US Warszawa.jpg', 'removed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -48,7 +48,7 @@
                   BIN_FILENODE: 'binary diff not shown'}}),
     ],
     'hg_diff_chmod_and_mod_single_binary_file.diff': [
-        ('gravatar.png', 'M',
+        ('gravatar.png', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -56,21 +56,21 @@
                   BIN_FILENODE: 'binary diff not shown'}}),
     ],
     'hg_diff_chmod.diff': [
-        ('file', 'M',
+        ('file', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
     ],
     'hg_diff_rename_file.diff': [
-        ('file_renamed', 'R',
+        ('file_renamed', 'renamed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {RENAMED_FILENODE: 'file renamed from file to file_renamed'}}),
     ],
     'hg_diff_rename_and_chmod_file.diff': [
-        ('README', 'R',
+        ('README', 'renamed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -78,64 +78,64 @@
                   RENAMED_FILENODE: 'file renamed from README.rst to README'}}),
     ],
     'hg_diff_binary_and_normal.diff': [
-        ('img/baseline-10px.png', 'A',
+        ('img/baseline-10px.png', 'added',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {NEW_FILENODE: 'new file 100644',
                   BIN_FILENODE: 'binary diff not shown'}}),
-        ('img/baseline-20px.png', 'D',
+        ('img/baseline-20px.png', 'removed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {DEL_FILENODE: 'deleted file',
                   BIN_FILENODE: 'binary diff not shown'}}),
-        ('index.html', 'M',
+        ('index.html', 'modified',
          {'added': 3,
           'deleted': 2,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('js/global.js', 'D',
+        ('js/global.js', 'removed',
          {'added': 0,
           'deleted': 75,
           'binary': False,
           'ops': {DEL_FILENODE: 'deleted file'}}),
-        ('js/jquery/hashgrid.js', 'A',
+        ('js/jquery/hashgrid.js', 'added',
          {'added': 340,
           'deleted': 0,
           'binary': False,
           'ops': {NEW_FILENODE: 'new file 100755'}}),
-        ('less/docs.less', 'M',
+        ('less/docs.less', 'modified',
          {'added': 34,
           'deleted': 0,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('less/scaffolding.less', 'M',
+        ('less/scaffolding.less', 'modified',
          {'added': 1,
           'deleted': 3,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('readme.markdown', 'M',
+        ('readme.markdown', 'modified',
          {'added': 1,
           'deleted': 10,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
     ],
     'git_diff_chmod.diff': [
-        ('work-horus.xls', 'M',
+        ('work-horus.xls', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755'}})
     ],
     'git_diff_rename_file.diff': [
-        ('file.xls', 'R',
+        ('file.xls', 'renamed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {RENAMED_FILENODE: 'file renamed from work-horus.xls to file.xls'}}),
         ('files/var/www/favicon.ico/DEFAULT',
-         'R',
+         'renamed',
          {'added': 0,
           'binary': True,
           'deleted': 0,
@@ -143,7 +143,7 @@
                   6: 'modified file chmod 100644 => 100755'}})
     ],
     'git_diff_mod_single_binary_file.diff': [
-        ('US Warszawa.jpg', 'M',
+        ('US Warszawa.jpg', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -151,78 +151,78 @@
                   BIN_FILENODE: 'binary diff not shown'}})
     ],
     'git_diff_binary_and_normal.diff': [
-        ('img/baseline-10px.png', 'A',
+        ('img/baseline-10px.png', 'added',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {NEW_FILENODE: 'new file 100644',
                   BIN_FILENODE: 'binary diff not shown'}}),
-        ('img/baseline-20px.png', 'D',
+        ('img/baseline-20px.png', 'removed',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {DEL_FILENODE: 'deleted file',
                   BIN_FILENODE: 'binary diff not shown'}}),
-        ('index.html', 'M',
+        ('index.html', 'modified',
          {'added': 3,
           'deleted': 2,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('js/global.js', 'D',
+        ('js/global.js', 'removed',
          {'added': 0,
           'deleted': 75,
           'binary': False,
           'ops': {DEL_FILENODE: 'deleted file'}}),
-        ('js/jquery/hashgrid.js', 'A',
+        ('js/jquery/hashgrid.js', 'added',
          {'added': 340,
           'deleted': 0,
           'binary': False,
           'ops': {NEW_FILENODE: 'new file 100755'}}),
-        ('less/docs.less', 'M',
+        ('less/docs.less', 'modified',
          {'added': 34,
           'deleted': 0,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('less/scaffolding.less', 'M',
+        ('less/scaffolding.less', 'modified',
          {'added': 1,
           'deleted': 3,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('readme.markdown', 'M',
+        ('readme.markdown', 'modified',
          {'added': 1,
           'deleted': 10,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
     ],
     'diff_with_diff_data.diff': [
-        ('vcs/backends/base.py', 'M',
+        ('vcs/backends/base.py', 'modified',
          {'added': 18,
           'deleted': 2,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('vcs/backends/git/repository.py', 'M',
+        ('vcs/backends/git/repository.py', 'modified',
          {'added': 46,
           'deleted': 15,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('vcs/backends/hg.py', 'M',
+        ('vcs/backends/hg.py', 'modified',
          {'added': 22,
           'deleted': 3,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('vcs/tests/test_git.py', 'M',
+        ('vcs/tests/test_git.py', 'modified',
          {'added': 5,
           'deleted': 5,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
-        ('vcs/tests/test_repository.py', 'M',
+        ('vcs/tests/test_repository.py', 'modified',
          {'added': 174,
           'deleted': 2,
           'binary': False,
           'ops': {MOD_FILENODE: 'modified file'}}),
     ],
     'git_diff_modify_binary_file.diff': [
-        ('file.name', 'M',
+        ('file.name', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -230,14 +230,14 @@
                   BIN_FILENODE: 'binary diff not shown'}})
     ],
     'hg_diff_copy_file.diff': [
-        ('file2', 'M',
+        ('file2', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
           'ops': {COPIED_FILENODE: 'file copied from file1 to file2'}}),
     ],
     'hg_diff_copy_and_modify_file.diff': [
-        ('file3', 'M',
+        ('file3', 'modified',
          {'added': 1,
           'deleted': 0,
           'binary': False,
@@ -245,7 +245,7 @@
                   MOD_FILENODE: 'modified file'}}),
     ],
     'hg_diff_copy_and_chmod_file.diff': [
-        ('file4', 'M',
+        ('file4', 'modified',
          {'added': 0,
           'deleted': 0,
           'binary': True,
@@ -253,7 +253,7 @@
                   CHMOD_FILENODE: 'modified file chmod 100644 => 100755'}}),
     ],
     'hg_diff_copy_chmod_and_edit_file.diff': [
-        ('file5', 'M',
+        ('file5', 'modified',
          {'added': 2,
           'deleted': 1,
           'binary': False,
@@ -262,7 +262,7 @@
                   MOD_FILENODE: 'modified file'}}),
     ],
     'hg_diff_rename_space_cr.diff': [
-        ('oh yes', 'R',
+        ('oh yes', 'renamed',
          {'added': 3,
           'deleted': 2,
           'binary': False,
@@ -271,31 +271,32 @@
 }
 
 
-class DiffLibTest(BaseTestCase):
+class TestDiffLib(TestController):
 
-    @parameterized.expand([(x,) for x in DIFF_FIXTURES])
+    @parametrize('diff_fixture', DIFF_FIXTURES)
     def test_diff(self, diff_fixture):
-        diff = fixture.load_resource(diff_fixture, strip=False)
-        diff_proc = DiffProcessor(diff)
-        diff_proc_d = diff_proc.prepare()
-        data = [(x['filename'], x['operation'], x['stats']) for x in diff_proc_d]
+        raw_diff = fixture.load_resource(diff_fixture, strip=False)
+        vcs = 'hg'
+        if diff_fixture.startswith('git_'):
+            vcs = 'git'
+        diff_processor = DiffProcessor(raw_diff, vcs=vcs)
+        data = [(x['filename'], x['operation'], x['stats']) for x in diff_processor.parsed]
         expected_data = DIFF_FIXTURES[diff_fixture]
-        self.assertListEqual(expected_data, data)
+        assert expected_data == data
 
     def test_diff_markup(self):
-        diff = fixture.load_resource('markuptest.diff', strip=False)
-        diff_proc = DiffProcessor(diff)
-        diff_proc_d = diff_proc.prepare()
-        chunks = diff_proc_d[0]['chunks']
-        self.assertFalse(chunks[0])
+        raw_diff = fixture.load_resource('markuptest.diff', strip=False)
+        diff_processor = DiffProcessor(raw_diff)
+        chunks = diff_processor.parsed[0]['chunks']
+        assert not chunks[0]
         #from pprint import pprint; pprint(chunks[1])
         l = ['\n']
         for d in chunks[1]:
             l.append('%(action)-7s %(new_lineno)3s %(old_lineno)3s %(line)r\n' % d)
         s = ''.join(l)
         print s
-        self.assertEqual(s, r'''
-context ... ... u'@@ -51,5 +51,12 @@\n'
+        assert s == r'''
+context ... ... u'@@ -51,6 +51,13 @@\n'
 unmod    51  51 u'<u>\t</u>begin();\n'
 unmod    52  52 u'<u>\t</u>\n'
 add      53     u'<u>\t</u>int foo;<u class="cr"></u>\n'
@@ -305,8 +306,10 @@
 add      57     u'<u>\t</u>int tab;<u>\t</u>\n'
 add      58     u'<u>\t</u>\n'
 unmod    59  53 u' <i></i>'
-del          54 u'<u>\t</u><del>#define MAX_STEPS (48)</del>\n'
-add      60     u'<u>\t</u><ins><u class="cr"></u></ins>\n'
+del          54 u'<u>\t</u>#define MAX_STEPS (48)\n'
+add      60     u'<u>\t</u><u class="cr"></u>\n'
 add      61     u'<u>\t</u>#define MAX_STEPS (64)<u class="cr"></u>\n'
 unmod    62  55 u'\n'
-''')
+del          56 u'<u>\t</u>#define MIN_STEPS (<del>48</del>)\n'
+add      63     u'<u>\t</u>#define MIN_STEPS (<ins>42</ins>)\n'
+'''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/models/test_dump_html_mails.ref.html	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,2833 @@
+<!doctype html>
+<html lang="en">
+<head><title>Notifications</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
+<body>
+<hr/>
+<h1>cs_comment, is_mention=False, status_change=None</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
+</pre>
+<hr/>
+<pre>http://comment.org
+
+Comment on Changeset "This changeset did something clever which is hard to explain"
+
+
+Opinionated User (jsmith):
+
+This is the new 'comment'.
+
+ - and here it ends indented.
+
+
+Changeset on http://example.com/repo_target branch brunch:
+"This changeset did something clever which is hard to explain" by u2 u3 (u2).
+
+
+View Comment: http://comment.org
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://comment.org"
+               target="_blank">Comment on Changeset &#34;This changeset did something clever which is hard to explain&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the new &#39;comment&#39;.<br/><br/> - and here it ends indented.</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Changeset on
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://example.com/repo_target">http://example.com/repo_target</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">brunch</span>:
+            </div>
+            <div>
+                "<a style="color:#395fa0;text-decoration:none"
+                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>cs_comment, is_mention=True, status_change=None</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
+</pre>
+<hr/>
+<pre>http://comment.org
+
+Mention in Comment on Changeset "This changeset did something clever which is hard to explain"
+
+
+Opinionated User (jsmith):
+
+This is the new 'comment'.
+
+ - and here it ends indented.
+
+
+Changeset on http://example.com/repo_target branch brunch:
+"This changeset did something clever which is hard to explain" by u2 u3 (u2).
+
+
+View Comment: http://comment.org
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://comment.org"
+               target="_blank">Mention in Comment on Changeset &#34;This changeset did something clever which is hard to explain&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the new &#39;comment&#39;.<br/><br/> - and here it ends indented.</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Changeset on
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://example.com/repo_target">http://example.com/repo_target</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">brunch</span>:
+            </div>
+            <div>
+                "<a style="color:#395fa0;text-decoration:none"
+                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>cs_comment, is_mention=False, status_change='Approved'</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Approved: Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
+</pre>
+<hr/>
+<pre>http://comment.org
+
+Comment on Changeset "This changeset did something clever which is hard to explain"
+
+
+Opinionated User (jsmith):
+
+Status change: Approved
+
+This is the new 'comment'.
+
+ - and here it ends indented.
+
+
+Changeset on http://example.com/repo_target branch brunch:
+"This changeset did something clever which is hard to explain" by u2 u3 (u2).
+
+
+View Comment: http://comment.org
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://comment.org"
+               target="_blank">Comment on Changeset &#34;This changeset did something clever which is hard to explain&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Approved
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the new &#39;comment&#39;.<br/><br/> - and here it ends indented.</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Changeset on
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://example.com/repo_target">http://example.com/repo_target</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">brunch</span>:
+            </div>
+            <div>
+                "<a style="color:#395fa0;text-decoration:none"
+                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>cs_comment, is_mention=True, status_change='Approved'</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Approved: Comment] repo/name changeset cafe1234 "This changeset did something cl..." on brunch
+</pre>
+<hr/>
+<pre>http://comment.org
+
+Mention in Comment on Changeset "This changeset did something clever which is hard to explain"
+
+
+Opinionated User (jsmith):
+
+Status change: Approved
+
+This is the new 'comment'.
+
+ - and here it ends indented.
+
+
+Changeset on http://example.com/repo_target branch brunch:
+"This changeset did something clever which is hard to explain" by u2 u3 (u2).
+
+
+View Comment: http://comment.org
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://comment.org"
+               target="_blank">Mention in Comment on Changeset &#34;This changeset did something clever which is hard to explain&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Approved
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the new &#39;comment&#39;.<br/><br/> - and here it ends indented.</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Changeset on
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://example.com/repo_target">http://example.com/repo_target</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">brunch</span>:
+            </div>
+            <div>
+                "<a style="color:#395fa0;text-decoration:none"
+                   href="http://changeset.com">This changeset did something clever which is hard to explain</a>"
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://comment.org" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>message</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: Test Message
+</pre>
+<hr/>
+<pre>This is the 'body' of the "test" message
+ - nothing interesting here except indentation.</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <span style="font-weight:600;color:#395fa0">Message</span>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This is the &#39;body&#39; of the &quot;test&quot; message<br/> - nothing interesting here except indentation.</div></td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>registration</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: New user newbie registered
+</pre>
+<hr/>
+<pre>http://newbie.org
+
+New User Registration
+
+
+Username: newbie
+
+Full Name: New Full Name
+
+Email: new@email.com
+
+
+View User Profile: http://newbie.org
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://newbie.org"
+               target="_blank">New User Registration</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+            Username:
+        </td>
+        <td style="border:#ddd 1px solid;background:#f9f9f9">
+            newbie
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="2"></td>
+    </tr>
+    <tr>
+        <td>
+            Full Name:
+        </td>
+        <td style="border:#ddd 1px solid;background:#f9f9f9">
+            New Full Name
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="2"></td>
+    </tr>
+    <tr>
+        <td>
+            Email:
+        </td>
+        <td style="border:#ddd 1px solid;background:#f9f9f9">
+            new@email.com
+        </td>
+    </tr>
+    <tr>
+        <td colspan="2">
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://newbie.org" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View User Profile</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request, is_mention=False</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Review] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/7
+
+Added as Reviewer of Pull Request #7 "The Title" by Requesting User (root)
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+Description:
+
+This PR is 'awesome' because it does <stuff>
+ - please approve indented!
+
+
+Changesets:
+
+Introduce one and two
+Make one plus two equal tree
+
+
+View Pull Request: http://pr.org/7
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/7"
+               target="_blank">Added as Reviewer of Pull Request #7 &#34;The Title&#34; by Requesting User (root)</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <div>
+                Description:
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td width="20px" style="width:20px"></td>
+                    <td>
+                        <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This PR is &#39;awesome&#39; because it does &lt;stuff&gt;<br/> - please approve indented!</div></div>
+                    </td>
+                    <td width="20px" style="width:20px"></td>
+                </tr>
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+    <tr><td height="15px" style="height:15px"></td></tr>
+    <tr>
+        <td>
+            <div>Changesets:</div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+
+    <tr>
+        <td style="font-family:Helvetica,Arial,sans-serif">
+            <ul style="color:#395fa0;padding-left:15px;margin:0">
+                    <li style="mso-special-format:bullet">
+                        <a style="color:#395fa0;text-decoration:none"
+                           href="http://changeset_home/?repo_name=repo_org&amp;revision=123abc123abc123abc123abc123abc123abc123abc">
+                            Introduce one and two
+                        </a>
+                    </li>
+                    <li style="mso-special-format:bullet">
+                        <a style="color:#395fa0;text-decoration:none"
+                           href="http://changeset_home/?repo_name=repo_org&amp;revision=567fed567fed567fed567fed567fed567fed567fed">
+                            Make one plus two equal tree
+                        </a>
+                    </li>
+            </ul>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/7" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Pull Request</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request, is_mention=True</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Review] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/7
+
+Mention on Pull Request #7 "The Title" by Requesting User (root)
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+Description:
+
+This PR is 'awesome' because it does <stuff>
+ - please approve indented!
+
+
+Changesets:
+
+Introduce one and two
+Make one plus two equal tree
+
+
+View Pull Request: http://pr.org/7
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/7"
+               target="_blank">Mention on Pull Request #7 &#34;The Title&#34; by Requesting User (root)</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <div>
+                Description:
+            </div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+    <tr>
+        <td>
+            <table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td width="20px" style="width:20px"></td>
+                    <td>
+                        <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">This PR is &#39;awesome&#39; because it does &lt;stuff&gt;<br/> - please approve indented!</div></div>
+                    </td>
+                    <td width="20px" style="width:20px"></td>
+                </tr>
+                <tr>
+                    <td height="10px" style="height:10px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+    <tr><td height="15px" style="height:15px"></td></tr>
+    <tr>
+        <td>
+            <div>Changesets:</div>
+        </td>
+    </tr>
+    <tr><td height="10px" style="height:10px"></td></tr>
+
+    <tr>
+        <td style="font-family:Helvetica,Arial,sans-serif">
+            <ul style="color:#395fa0;padding-left:15px;margin:0">
+                    <li style="mso-special-format:bullet">
+                        <a style="color:#395fa0;text-decoration:none"
+                           href="http://changeset_home/?repo_name=repo_org&amp;revision=123abc123abc123abc123abc123abc123abc123abc">
+                            Introduce one and two
+                        </a>
+                    </li>
+                    <li style="mso-special-format:bullet">
+                        <a style="color:#395fa0;text-decoration:none"
+                           href="http://changeset_home/?repo_name=repo_org&amp;revision=567fed567fed567fed567fed567fed567fed567fed">
+                            Make one plus two equal tree
+                        </a>
+                    </li>
+            </ul>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/7" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Pull Request</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=False, status_change=None, closing_pr=False</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=True, status_change=None, closing_pr=False</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Mention in Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Mention in Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=False, status_change='Under Review', closing_pr=False</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Under Review: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+Status change: Under Review
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Under Review
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=True, status_change='Under Review', closing_pr=False</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Under Review: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Mention in Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+Status change: Under Review
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Mention in Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Under Review
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=False, status_change=None, closing_pr=True</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Pull Request #7 "The Title" Closed
+
+
+Opinionated User (jsmith):
+
+The pull request has been closed.
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Pull Request #7 &#34;The Title&#34; Closed</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        The pull request has been closed.
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=True, status_change=None, closing_pr=True</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Mention in Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+The pull request has been closed.
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Mention in Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        The pull request has been closed.
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=False, status_change='Under Review', closing_pr=True</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Under Review, Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Pull Request #7 "The Title" Closed
+
+
+Opinionated User (jsmith):
+
+Status change: Under Review
+
+The pull request has been closed.
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Pull Request #7 &#34;The Title&#34; Closed</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Under Review
+                    </div>
+                    <div style="font-weight:600">
+                        The pull request has been closed.
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>pull_request_comment, is_mention=True, status_change='Under Review', closing_pr=True</h1>
+<pre>
+From: u1
+To: u2@example.com
+Subject: [Under Review, Closing: Comment] repo/name PR #7 "The Title" from devbranch by u2
+</pre>
+<hr/>
+<pre>http://pr.org/comment
+
+Mention in Comment on Pull Request #7 "The Title"
+
+
+Opinionated User (jsmith):
+
+Status change: Under Review
+
+The pull request has been closed.
+
+Me too!
+
+ - and indented on second line
+
+
+Pull request #7 "The Title" by u2 u3 (u2)
+from https://dev.org/repo branch devbranch
+to http://mainline.com/repo branch trunk
+
+
+View Comment: http://pr.org/comment
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <a style="text-decoration:none;font-weight:600;color:#395fa0" href="http://pr.org/comment"
+               target="_blank">Mention in Comment on Pull Request #7 &#34;The Title&#34;</a>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%">
+    <tr>
+        <td>
+<table cellpadding="0" cellspacing="0" width="100%" border="0" bgcolor="#f9f9f9" style="border:1px solid #ddd;border-radius:4px">
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-weight:600;color:#395fa0">Opinionated User (jsmith)</div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+    </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3"></td>
+        </tr>
+        <tr>
+            <td width="20px" style="width:20px"></td>
+            <td>
+                    <div style="font-weight:600">
+                        Status change:
+                        Under Review
+                    </div>
+                    <div style="font-weight:600">
+                        The pull request has been closed.
+                    </div>
+            </td>
+            <td width="20px" style="width:20px"></td>
+        </tr>
+        <tr>
+            <td height="10px" style="height:10px" colspan="3" style="border-bottom:1px solid #ddd"></td>
+        </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="20px" style="width:20px"></td>
+        <td>
+            <div style="font-family:Lucida Console,Consolas,Monaco,Inconsolata,Liberation Mono,monospace;white-space:pre-wrap"><div class="formatted-fixed">Me too!<br/><br/> - and indented on second line</div></div>
+        </td>
+        <td width="20px" style="width:20px"></td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px" colspan="3"></td>
+    </tr>
+</table>
+        </td>
+    </tr>
+    <tr>
+        <td height="30px" style="height:30px"></td>
+    </tr>
+    <tr>
+        <td>
+            <div>
+                Pull request
+                <a style="color:#395fa0;text-decoration:none"
+                   href="http://pr.org/7">#7 "The Title"</a>
+                by
+                <span style="border:#ddd 1px solid;background:#f9f9f9">u2 u3 (u2)</span>.
+            </div>
+            <div>
+                from
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="https://dev.org/repo">https://dev.org/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">devbranch</span>
+                <br/>
+                to
+                <a style="color:#202020;text-decoration:none;border:#ddd 1px solid;background:#f9f9f9"
+                   href="http://mainline.com/repo">http://mainline.com/repo</a>
+                branch
+                <span style="border:#ddd 1px solid;background:#f9f9f9">trunk</span>:
+            </div>
+        </td>
+    </tr>
+    <tr>
+        <td>
+<center>
+    <table cellspacing="0" cellpadding="0" style="margin-left:auto;margin-right:auto">
+        <tr>
+            <td height="25px" style="height:25px"></td>
+        </tr>
+        <tr>
+            <td style="border-collapse:collapse;border-radius:2px;text-align:center;display:block;border:solid 1px #395fa0;padding:11px 20px 11px 20px">
+                <a href="http://pr.org/comment" style="text-decoration:none;display:block" target="_blank">
+                    <center>
+                        <font size="3">
+                            <span style="font-family:Helvetica,Arial,sans-serif;font-weight:700;font-size:15px;line-height:14px;color:#395fa0;white-space:nowrap;vertical-align:middle">View Comment</span>
+                        </font>
+                    </center>
+                </a>
+            </td>
+        </tr>
+    </table>
+</center>
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+<hr/>
+<h1>TYPE_PASSWORD_RESET</h1>
+<pre>
+From: u1
+To: john@doe.com
+Subject: Password reset link
+</pre>
+<hr/>
+<pre>Password Reset Request
+
+Hello John Doe,
+
+We have received a request to reset the password for your account.
+
+To set a new password, click the following link:
+
+http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746
+
+Should you not be able to use the link above, please type the following code into the password reset form:
+decbf64715098db5b0bd23eab44bd792670ab746
+
+If it weren't you who requested the password reset, just disregard this message.
+</pre>
+<hr/>
+<!--!doctype html-->
+<!--html lang="en"-->
+<!--head-->
+    <!--title--><!--/title-->
+    <!--meta name="viewport" content="width=device-width"-->
+    <!--meta http-equiv="Content-Type" content="text/html; charset=UTF-8"-->
+<!--/head-->
+<!--body-->
+<table align="center" cellpadding="0" cellspacing="0" border="0" style="min-width:348px;max-width:800px;font-family:Helvetica,Arial,sans-serif;font-weight:200;font-size:14px;line-height:17px;color:#202020">
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td>
+            <table width="100%" cellpadding="0" cellspacing="0" border="0"
+                   style="table-layout:fixed;font-family:Helvetica,Arial,sans-serif;border:1px solid #ddd">
+                <tr><td width="30px" style="width:30px"></td><td></td><td width="30px" style="width:30px"></td></tr>
+                <tr>
+                    <td colspan="3">
+<table bgcolor="#f9f9f9" width="100%" cellpadding="0" cellspacing="0"
+       style="border-bottom:1px solid #ddd">
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+    <tr>
+        <td width="30px" style="width:30px"></td>
+        <td style="font-family:Helvetica,Arial,sans-serif;font-size:19px;line-height:24px">
+            <span style="font-weight:600;color:#395fa0">Password Reset Request</span>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+    <tr>
+        <td height="20px" style="height:20px" colspan="3"></td>
+    </tr>
+</table>
+                    </td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td>
+<table cellpadding="0" cellspacing="0" border="0" width="100%" style="table-layout:fixed;word-wrap:break-word;">
+    <tr>
+        <td>Hello John Doe,</td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+            We have received a request to reset the password for your account.
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+                <div>
+                    To set a new password, click the following link:
+                    <br/>
+                    <a style="color:#395fa0;text-decoration:none" href="http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746"
+                        target="_blank">http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746</a>
+                    <br/>
+                    Should you not be able to use the link above, please type the following code into the password reset form:
+                    <code>decbf64715098db5b0bd23eab44bd792670ab746</code>
+                </div>
+        </td>
+    </tr>
+    <tr>
+        <td height="10px" style="height:10px"></td>
+    </tr>
+    <tr>
+        <td>
+            If it weren&#39;t you who requested the password reset, just disregard this message.
+        </td>
+    </tr>
+</table>
+                    </td>
+                    <td></td>
+                </tr>
+                <tr>
+                    <td height="30px" style="height:30px" colspan="3"></td>
+                </tr>
+            </table>
+        </td>
+        <td width="30px" style="width:30px"></td>
+    </tr>
+</table>
+<!--/body-->
+<!--/html-->
+<hr/>
+
+</body>
+</html>
--- a/kallithea/tests/models/test_notifications.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_notifications.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,163 +1,177 @@
-from kallithea.tests import *
+import os
+import re
 
-from kallithea.model.db import User, Notification, UserNotification
+import mock
+import routes.util
+
+from kallithea.tests.base import *
+from kallithea.lib import helpers as h
+from kallithea.model.db import User
 from kallithea.model.user import UserModel
+from kallithea.model.meta import Session
+from kallithea.model.notification import NotificationModel, EmailNotificationModel
 
-from kallithea.model.meta import Session
-from kallithea.model.notification import NotificationModel
+import kallithea.lib.celerylib
+import kallithea.lib.celerylib.tasks
+
+from tg.util.webtest import test_context
 
 
-class TestNotifications(BaseTestCase):
+class TestNotifications(TestController):
 
-    def __init__(self, methodName='runTest'):
+    def setup_method(self, method):
         Session.remove()
-        self.u1 = UserModel().create_or_update(username=u'u1',
+        u1 = UserModel().create_or_update(username=u'u1',
                                         password=u'qweqwe',
                                         email=u'u1@example.com',
                                         firstname=u'u1', lastname=u'u1')
         Session().commit()
-        self.u1 = self.u1.user_id
+        self.u1 = u1.user_id
 
-        self.u2 = UserModel().create_or_update(username=u'u2',
+        u2 = UserModel().create_or_update(username=u'u2',
                                         password=u'qweqwe',
                                         email=u'u2@example.com',
                                         firstname=u'u2', lastname=u'u3')
         Session().commit()
-        self.u2 = self.u2.user_id
+        self.u2 = u2.user_id
 
-        self.u3 = UserModel().create_or_update(username=u'u3',
+        u3 = UserModel().create_or_update(username=u'u3',
                                         password=u'qweqwe',
                                         email=u'u3@example.com',
                                         firstname=u'u3', lastname=u'u3')
         Session().commit()
-        self.u3 = self.u3.user_id
-
-        super(TestNotifications, self).__init__(methodName=methodName)
-
-    def setUp(self):
-        self.remove_all_notifications()
-        self.assertEqual([], Notification.query().all())
-        self.assertEqual([], UserNotification.query().all())
+        self.u3 = u3.user_id
 
     def test_create_notification(self):
-        usrs = [self.u1, self.u2]
-        notification = NotificationModel().create(created_by=self.u1,
-                                           subject=u'subj', body=u'hi there',
-                                           recipients=usrs)
-        Session().commit()
-        u1 = User.get(self.u1)
-        u2 = User.get(self.u2)
-        u3 = User.get(self.u3)
-        notifications = Notification.query().all()
-        self.assertEqual(len(notifications), 1)
+        with test_context(self.app):
+            usrs = [self.u1, self.u2]
 
-        self.assertEqual(notifications[0].recipients, [u1, u2])
-        self.assertEqual(notification.notification_id,
-                         notifications[0].notification_id)
+            def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
+                assert recipients == ['u2@example.com']
+                assert subject == 'Test Message'
+                assert body == u"hi there"
+                assert '>hi there<' in html_body
+                assert author.username == 'u1'
+            with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', send_email):
+                NotificationModel().create(created_by=self.u1,
+                                                   subject=u'subj', body=u'hi there',
+                                                   recipients=usrs)
 
-        unotification = UserNotification.query()\
-            .filter(UserNotification.notification == notification).all()
-
-        self.assertEqual(len(unotification), len(usrs))
-        self.assertEqual(set([x.user.user_id for x in unotification]),
-                         set(usrs))
+    @mock.patch.object(h, 'canonical_url', (lambda arg, **kwargs: 'http://%s/?%s' % (arg, '&'.join('%s=%s' % (k, v) for (k, v) in sorted(kwargs.items())))))
+    def test_dump_html_mails(self):
+        # Exercise all notification types and dump them to one big html file
+        l = []
 
-    def test_user_notifications(self):
-        notification1 = NotificationModel().create(created_by=self.u1,
-                                            subject=u'subj', body=u'hi there1',
-                                            recipients=[self.u3])
-        Session().commit()
-        notification2 = NotificationModel().create(created_by=self.u1,
-                                            subject=u'subj', body=u'hi there2',
-                                            recipients=[self.u3])
-        Session().commit()
-        u3 = Session().query(User).get(self.u3)
-
-        self.assertEqual(sorted([x.notification for x in u3.notifications]),
-                         sorted([notification2, notification1]))
+        def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
+            l.append('<hr/>\n')
+            l.append('<h1>%s</h1>\n' % desc) # desc is from outer scope
+            l.append('<pre>\n')
+            l.append('From: %s\n' % author.username)
+            l.append('To: %s\n' % ' '.join(recipients))
+            l.append('Subject: %s\n' % subject)
+            l.append('</pre>\n')
+            l.append('<hr/>\n')
+            l.append('<pre>%s</pre>\n' % body)
+            l.append('<hr/>\n')
+            l.append(html_body)
+            l.append('<hr/>\n')
 
-    def test_delete_notifications(self):
-        notification = NotificationModel().create(created_by=self.u1,
-                                           subject=u'title', body=u'hi there3',
-                                    recipients=[self.u3, self.u1, self.u2])
-        Session().commit()
-        notifications = Notification.query().all()
-        self.assertTrue(notification in notifications)
-
-        Notification.delete(notification.notification_id)
-        Session().commit()
-
-        notifications = Notification.query().all()
-        self.assertFalse(notification in notifications)
-
-        un = UserNotification.query().filter(UserNotification.notification
-                                             == notification).all()
-        self.assertEqual(un, [])
-
-    def test_delete_association(self):
-        notification = NotificationModel().create(created_by=self.u1,
-                                           subject=u'title', body=u'hi there3',
-                                    recipients=[self.u3, self.u1, self.u2])
-        Session().commit()
+        with test_context(self.app):
+            with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', send_email):
+                pr_kwargs = dict(
+                    pr_nice_id='#7',
+                    pr_title='The Title',
+                    pr_title_short='The Title',
+                    pr_url='http://pr.org/7',
+                    pr_target_repo='http://mainline.com/repo',
+                    pr_target_branch='trunk',
+                    pr_source_repo='https://dev.org/repo',
+                    pr_source_branch='devbranch',
+                    pr_owner=User.get(self.u2),
+                    pr_owner_username='u2'
+                    )
 
-        unotification = UserNotification.query()\
-                            .filter(UserNotification.notification ==
-                                    notification)\
-                            .filter(UserNotification.user_id == self.u3)\
-                            .scalar()
-
-        self.assertEqual(unotification.user_id, self.u3)
-
-        NotificationModel().delete(self.u3,
-                                   notification.notification_id)
-        Session().commit()
-
-        u3notification = UserNotification.query()\
-                            .filter(UserNotification.notification ==
-                                    notification)\
-                            .filter(UserNotification.user_id == self.u3)\
-                            .scalar()
-
-        self.assertEqual(u3notification, None)
-
-        # notification object is still there
-        self.assertEqual(Notification.query().all(), [notification])
+                for type_, body, kwargs in [
+                        (NotificationModel.TYPE_CHANGESET_COMMENT,
+                         u'This is the new \'comment\'.\n\n - and here it ends indented.',
+                         dict(
+                            short_id='cafe1234',
+                            raw_id='cafe1234c0ffeecafe',
+                            branch='brunch',
+                            cs_comment_user='Opinionated User (jsmith)',
+                            cs_comment_url='http://comment.org',
+                            is_mention=[False, True],
+                            message='This changeset did something clever which is hard to explain',
+                            message_short='This changeset did something cl...',
+                            status_change=[None, 'Approved'],
+                            cs_target_repo='http://example.com/repo_target',
+                            cs_url='http://changeset.com',
+                            cs_author=User.get(self.u2))),
+                        (NotificationModel.TYPE_MESSAGE,
+                         u'This is the \'body\' of the "test" message\n - nothing interesting here except indentation.',
+                         dict()),
+                        #(NotificationModel.TYPE_MENTION, '$body', None), # not used
+                        (NotificationModel.TYPE_REGISTRATION,
+                         u'Registration body',
+                         dict(
+                            new_username='newbie',
+                            registered_user_url='http://newbie.org',
+                            new_email='new@email.com',
+                            new_full_name='New Full Name')),
+                        (NotificationModel.TYPE_PULL_REQUEST,
+                         u'This PR is \'awesome\' because it does <stuff>\n - please approve indented!',
+                         dict(
+                            pr_user_created='Requesting User (root)', # pr_owner should perhaps be used for @mention in description ...
+                            is_mention=[False, True],
+                            pr_revisions=[('123abc'*7, "Introduce one and two\n\nand that's it"), ('567fed'*7, 'Make one plus two equal tree')],
+                            org_repo_name='repo_org',
+                            **pr_kwargs)),
+                        (NotificationModel.TYPE_PULL_REQUEST_COMMENT,
+                         u'Me too!\n\n - and indented on second line',
+                         dict(
+                            closing_pr=[False, True],
+                            is_mention=[False, True],
+                            pr_comment_user='Opinionated User (jsmith)',
+                            pr_comment_url='http://pr.org/comment',
+                            status_change=[None, 'Under Review'],
+                            **pr_kwargs)),
+                        ]:
+                    kwargs['repo_name'] = u'repo/name'
+                    params = [(type_, type_, body, kwargs)]
+                    for param_name in ['is_mention', 'status_change', 'closing_pr']: # TODO: inline/general
+                        if not isinstance(kwargs.get(param_name), list):
+                            continue
+                        new_params = []
+                        for v in kwargs[param_name]:
+                            for desc, type_, body, kwargs in params:
+                                kwargs = dict(kwargs)
+                                kwargs[param_name] = v
+                                new_params.append(('%s, %s=%r' % (desc, param_name, v), type_, body, kwargs))
+                        params = new_params
 
-        #u1 and u2 still have assignments
-        u1notification = UserNotification.query()\
-                            .filter(UserNotification.notification ==
-                                    notification)\
-                            .filter(UserNotification.user_id == self.u1)\
-                            .scalar()
-        self.assertNotEqual(u1notification, None)
-        u2notification = UserNotification.query()\
-                            .filter(UserNotification.notification ==
-                                    notification)\
-                            .filter(UserNotification.user_id == self.u2)\
-                            .scalar()
-        self.assertNotEqual(u2notification, None)
+                    for desc, type_, body, kwargs in params:
+                        # desc is used as "global" variable
+                        NotificationModel().create(created_by=self.u1,
+                                                           subject=u'unused', body=body, email_kwargs=kwargs,
+                                                           recipients=[self.u2], type_=type_)
 
-    def test_notification_counter(self):
-        NotificationModel().create(created_by=self.u1,
-                            subject=u'title', body=u'hi there_delete',
-                            recipients=[self.u3, self.u1])
-        Session().commit()
+                # Email type TYPE_PASSWORD_RESET has no corresponding notification type - test it directly:
+                desc = 'TYPE_PASSWORD_RESET'
+                kwargs = dict(user='John Doe', reset_token='decbf64715098db5b0bd23eab44bd792670ab746', reset_url='http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746')
+                kallithea.lib.celerylib.tasks.send_email(['john@doe.com'],
+                    "Password reset link",
+                    EmailNotificationModel().get_email_tmpl(EmailNotificationModel.TYPE_PASSWORD_RESET, 'txt', **kwargs),
+                    EmailNotificationModel().get_email_tmpl(EmailNotificationModel.TYPE_PASSWORD_RESET, 'html', **kwargs),
+                    author=User.get(self.u1))
 
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u1), 1)
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u2), 0)
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u3), 1)
+        out = '<!doctype html>\n<html lang="en">\n<head><title>Notifications</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>\n<body>\n%s\n</body>\n</html>\n' % \
+            re.sub(r'<(/?(?:!doctype|html|head|title|meta|body)\b[^>]*)>', r'<!--\1-->', ''.join(l))
 
-        notification = NotificationModel().create(created_by=self.u1,
-                                           subject=u'title', body=u'hi there3',
-                                    recipients=[self.u3, self.u1, self.u2])
-        Session().commit()
-
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u1), 2)
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u2), 1)
-        self.assertEqual(NotificationModel()
-                         .get_unread_cnt_for_user(self.u3), 2)
+        outfn = os.path.join(os.path.dirname(__file__), 'test_dump_html_mails.out.html')
+        reffn = os.path.join(os.path.dirname(__file__), 'test_dump_html_mails.ref.html')
+        with open(outfn, 'w') as f:
+            f.write(out)
+        with open(reffn) as f:
+            ref = f.read()
+        assert ref == out # copy test_dump_html_mails.out.html to test_dump_html_mails.ref.html to update expectations
+        os.unlink(outfn)
--- a/kallithea/tests/models/test_permissions.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_permissions.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,8 +1,8 @@
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 from kallithea.model.repo_group import RepoGroupModel
 from kallithea.model.repo import RepoModel
-from kallithea.model.db import RepoGroup, User, UserGroupRepoGroupToPerm,\
+from kallithea.model.db import RepoGroup, User, UserGroupRepoGroupToPerm, \
     Permission, UserToPerm
 from kallithea.model.user import UserModel
 
@@ -15,18 +15,16 @@
 fixture = Fixture()
 
 
-class TestPermissions(BaseTestCase):
-    def __init__(self, methodName='runTest'):
-        super(TestPermissions, self).__init__(methodName=methodName)
+class TestPermissions(TestController):
 
     @classmethod
-    def setUpClass(cls):
-        #recreate default user to get a clean start
+    def setup_class(cls):
+        # recreate default user to get a clean start
         PermissionModel().create_default_permissions(user=User.DEFAULT_USER,
                                                      force=True)
         Session().commit()
 
-    def setUp(self):
+    def setup_method(self, method):
         self.u1 = UserModel().create_or_update(
             username=u'u1', password=u'qweqwe',
             email=u'u1@example.com', firstname=u'u1', lastname=u'u1'
@@ -46,7 +44,7 @@
         )
         Session().commit()
 
-    def tearDown(self):
+    def teardown_method(self, method):
         if hasattr(self, 'test_repo'):
             RepoModel().delete(repo=self.test_repo)
 
@@ -54,15 +52,18 @@
         UserModel().delete(self.u2)
         UserModel().delete(self.u3)
         UserModel().delete(self.a1)
+
+        Session().commit() # commit early to avoid SQLAlchemy warning from double cascade delete to users_groups_members
+
         if hasattr(self, 'g1'):
             RepoGroupModel().delete(self.g1.group_id)
         if hasattr(self, 'g2'):
             RepoGroupModel().delete(self.g2.group_id)
 
+        if hasattr(self, 'ug2'):
+            UserGroupModel().delete(self.ug2, force=True)
         if hasattr(self, 'ug1'):
             UserGroupModel().delete(self.ug1, force=True)
-        if hasattr(self, 'ug2'):
-            UserGroupModel().delete(self.ug2, force=True)
 
         Session().commit()
 
@@ -70,30 +71,27 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.create.repository', u'repository.read',
-                           u'hg.register.manual_activate']),
-            'repositories': {HG_REPO: u'repository.read'}
+            'global': set(['hg.create.repository', 'repository.read',
+                           'hg.register.manual_activate']),
+            'repositories': {HG_REPO: 'repository.read'}
         }
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         perms['repositories'][HG_REPO])
+        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
         new_perm = 'repository.write'
         RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
                                           perm=new_perm)
         Session().commit()
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         new_perm)
+        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm
 
     def test_default_admin_perms_set(self):
         a1_auth = AuthUser(user_id=self.a1.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.admin', 'hg.create.write_on_repogroup.true']),
-            'repositories': {HG_REPO: u'repository.admin'}
+            'global': set(['hg.admin', 'hg.create.write_on_repogroup.true']),
+            'repositories': {HG_REPO: 'repository.admin'}
         }
-        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
-                         perms['repositories'][HG_REPO])
+        assert a1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
         new_perm = 'repository.write'
         RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1,
                                           perm=new_perm)
@@ -101,28 +99,24 @@
         # cannot really downgrade admins permissions !? they still gets set as
         # admin !
         u1_auth = AuthUser(user_id=self.a1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         perms['repositories'][HG_REPO])
+        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
 
     def test_default_group_perms(self):
-        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group(u'test2', skip_if_exists=True)
         u1_auth = AuthUser(user_id=self.u1.user_id)
         perms = {
             'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'},
             'global': set(Permission.DEFAULT_USER_PERMISSIONS),
-            'repositories': {HG_REPO: u'repository.read'}
+            'repositories': {HG_REPO: 'repository.read'}
         }
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         perms['repositories'][HG_REPO])
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         perms['repositories_groups'])
-        self.assertEqual(u1_auth.permissions['global'],
-                         perms['global'])
+        assert u1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert u1_auth.permissions['repositories_groups'] == perms['repositories_groups']
+        assert u1_auth.permissions['global'] == perms['global']
 
     def test_default_admin_group_perms(self):
-        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group(u'test2', skip_if_exists=True)
         a1_auth = AuthUser(user_id=self.a1.user_id)
         perms = {
             'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'},
@@ -130,14 +124,12 @@
             'repositories': {HG_REPO: 'repository.admin'}
         }
 
-        self.assertEqual(a1_auth.permissions['repositories'][HG_REPO],
-                         perms['repositories'][HG_REPO])
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                         perms['repositories_groups'])
+        assert a1_auth.permissions['repositories'][HG_REPO] == perms['repositories'][HG_REPO]
+        assert a1_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_propagated_permission_from_users_group_by_explicit_perms_exist(self):
         # make group
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
 
         # set permission to lower
@@ -145,8 +137,7 @@
         RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         new_perm)
+        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm
 
         # grant perm for group this should not override permission from user
         # since it has explicitly set
@@ -158,18 +149,16 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.create.repository', u'repository.read',
-                           u'hg.register.manual_activate']),
-            'repositories': {HG_REPO: u'repository.read'}
+            'global': set(['hg.create.repository', 'repository.read',
+                           'hg.register.manual_activate']),
+            'repositories': {HG_REPO: 'repository.read'}
         }
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         new_perm)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         perms['repositories_groups'])
+        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm
+        assert u1_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_propagated_permission_from_users_group(self):
         # make group
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         UserGroupModel().add_user_to_group(self.ug1, self.u3)
 
         # grant perm for group this should override default permission from user
@@ -181,18 +170,16 @@
         u3_auth = AuthUser(user_id=self.u3.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.create.repository', u'repository.read',
-                           u'hg.register.manual_activate']),
-            'repositories': {HG_REPO: u'repository.read'}
+            'global': set(['hg.create.repository', 'repository.read',
+                           'hg.register.manual_activate']),
+            'repositories': {HG_REPO: 'repository.read'}
         }
-        self.assertEqual(u3_auth.permissions['repositories'][HG_REPO],
-                         new_perm_gr)
-        self.assertEqual(u3_auth.permissions['repositories_groups'],
-                         perms['repositories_groups'])
+        assert u3_auth.permissions['repositories'][HG_REPO] == new_perm_gr
+        assert u3_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_propagated_permission_from_users_group_lower_weight(self):
         # make group
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         # add user to group
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
 
@@ -202,8 +189,7 @@
                                           perm=new_perm_h)
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         new_perm_h)
+        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm_h
 
         # grant perm for group this should NOT override permission from user
         # since it's lower than granted
@@ -215,28 +201,24 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         perms = {
             'repositories_groups': {},
-            'global': set([u'hg.create.repository', u'repository.read',
-                           u'hg.register.manual_activate']),
-            'repositories': {HG_REPO: u'repository.write'}
+            'global': set(['hg.create.repository', 'repository.read',
+                           'hg.register.manual_activate']),
+            'repositories': {HG_REPO: 'repository.write'}
         }
-        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
-                         new_perm_h)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         perms['repositories_groups'])
+        assert u1_auth.permissions['repositories'][HG_REPO] == new_perm_h
+        assert u1_auth.permissions['repositories_groups'] == perms['repositories_groups']
 
     def test_repo_in_group_permissions(self):
-        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
-        self.g2 = fixture.create_repo_group('group2', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'group1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group(u'group2', skip_if_exists=True)
         # both perms should be read !
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.read', u'group2': u'group.read'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.read', u'group2': u'group.read'}
 
         a1_auth = AuthUser(user_id=self.anon.user_id)
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.read', u'group2': u'group.read'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.read', u'group2': u'group.read'}
 
-        #Change perms to none for both groups
+        # Change perms to none for both groups
         RepoGroupModel().grant_user_permission(repo_group=self.g1,
                                                user=self.anon,
                                                perm='group.none')
@@ -245,12 +227,10 @@
                                                perm='group.none')
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
         a1_auth = AuthUser(user_id=self.anon.user_id)
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
         # add repo to group
         name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
@@ -260,64 +240,56 @@
                                              cur_user=self.u1,)
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
         a1_auth = AuthUser(user_id=self.anon.user_id)
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
-        #grant permission for u2 !
+        # grant permission for u2 !
         RepoGroupModel().grant_user_permission(repo_group=self.g1, user=self.u2,
                                                perm='group.read')
         RepoGroupModel().grant_user_permission(repo_group=self.g2, user=self.u2,
                                                perm='group.read')
         Session().commit()
-        self.assertNotEqual(self.u1, self.u2)
-        #u1 and anon should have not change perms while u2 should !
+        assert self.u1 != self.u2
+        # u1 and anon should have not change perms while u2 should !
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
         u2_auth = AuthUser(user_id=self.u2.user_id)
-        self.assertEqual(u2_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.read', u'group2': u'group.read'})
+        assert u2_auth.permissions['repositories_groups'] == {u'group1': u'group.read', u'group2': u'group.read'}
 
         a1_auth = AuthUser(user_id=self.anon.user_id)
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                 {u'group1': u'group.none', u'group2': u'group.none'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.none', u'group2': u'group.none'}
 
     def test_repo_group_user_as_user_group_member(self):
         # create Group1
-        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'group1', skip_if_exists=True)
         a1_auth = AuthUser(user_id=self.anon.user_id)
 
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.read'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.read'}
 
         # set default permission to none
         RepoGroupModel().grant_user_permission(repo_group=self.g1,
                                                user=self.anon,
                                                perm='group.none')
         # make group
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         # add user to group
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
         Session().commit()
 
         # check if user is in the group
-        membrs = [x.user_id for x in UserGroupModel().get(self.ug1.users_group_id).members]
-        self.assertEqual(membrs, [self.u1.user_id])
+        members = [x.user_id for x in UserGroupModel().get(self.ug1.users_group_id).members]
+        assert members == [self.u1.user_id]
         # add some user to that group
 
         # check his permissions
         a1_auth = AuthUser(user_id=self.anon.user_id)
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.none'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.none'}
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.none'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.none'}
 
         # grant ug1 read permissions for
         RepoGroupModel().grant_user_group_permission(repo_group=self.g1,
@@ -325,20 +297,18 @@
                                                       perm='group.read')
         Session().commit()
         # check if the
-        obj = Session().query(UserGroupRepoGroupToPerm)\
-            .filter(UserGroupRepoGroupToPerm.group == self.g1)\
-            .filter(UserGroupRepoGroupToPerm.users_group == self.ug1)\
+        obj = Session().query(UserGroupRepoGroupToPerm) \
+            .filter(UserGroupRepoGroupToPerm.group == self.g1) \
+            .filter(UserGroupRepoGroupToPerm.users_group == self.ug1) \
             .scalar()
-        self.assertEqual(obj.permission.permission_name, 'group.read')
+        assert obj.permission.permission_name == 'group.read'
 
         a1_auth = AuthUser(user_id=self.anon.user_id)
 
-        self.assertEqual(a1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.none'})
+        assert a1_auth.permissions['repositories_groups'] == {u'group1': u'group.none'}
 
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.read'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.read'}
 
     def test_inherited_permissions_from_default_on_user_enabled(self):
         user_model = UserModel()
@@ -353,12 +323,11 @@
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have inherited permissions from default user
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.repository', 'hg.fork.repository',
+        assert u1_auth.permissions['global'] == set(['hg.create.repository', 'hg.fork.repository',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
     def test_inherited_permissions_from_default_on_user_disabled(self):
         user_model = UserModel()
@@ -373,12 +342,11 @@
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have inherited permissions from default user
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.none', 'hg.fork.none',
+        assert u1_auth.permissions['global'] == set(['hg.create.none', 'hg.fork.none',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
     def test_non_inherited_permissions_from_default_on_user_enabled(self):
         user_model = UserModel()
@@ -389,7 +357,7 @@
         user_model.revoke_perm(usr, 'hg.fork.none')
         user_model.grant_perm(usr, 'hg.fork.repository')
 
-        #disable global perms on specific user
+        # disable global perms on specific user
         user_model.revoke_perm(self.u1, 'hg.create.repository')
         user_model.grant_perm(self.u1, 'hg.create.none')
         user_model.revoke_perm(self.u1, 'hg.fork.repository')
@@ -401,12 +369,11 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have non inherited permissions from he's
         # explicitly set permissions
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.none', 'hg.fork.none',
+        assert u1_auth.permissions['global'] == set(['hg.create.none', 'hg.fork.none',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
     def test_non_inherited_permissions_from_default_on_user_disabled(self):
         user_model = UserModel()
@@ -417,7 +384,7 @@
         user_model.revoke_perm(usr, 'hg.fork.repository')
         user_model.grant_perm(usr, 'hg.fork.none')
 
-        #enable global perms on specific user
+        # enable global perms on specific user
         user_model.revoke_perm(self.u1, 'hg.create.none')
         user_model.grant_perm(self.u1, 'hg.create.repository')
         user_model.revoke_perm(self.u1, 'hg.fork.none')
@@ -429,19 +396,18 @@
         u1_auth = AuthUser(user_id=self.u1.user_id)
         # this user will have non inherited permissions from he's
         # explicitly set permissions
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.repository', 'hg.fork.repository',
+        assert u1_auth.permissions['global'] == set(['hg.create.repository', 'hg.fork.repository',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
-                              'usergroup.read', 'hg.create.write_on_repogroup.true']))
+                              'usergroup.read', 'hg.create.write_on_repogroup.true'])
 
     def test_inactive_user_group_does_not_affect_global_permissions(self):
         # Issue #138: Inactive User Groups affecting permissions
         # Add user to inactive user group, set specific permissions on user
         # group and disable inherit-from-default. User permissions should still
         # inherit from default.
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
@@ -464,20 +430,19 @@
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
 
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.none', 'hg.fork.none',
+        assert u1_auth.permissions['global'] == set(['hg.create.none', 'hg.fork.none',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
                               'usergroup.read',
-                              'hg.create.write_on_repogroup.true']))
+                              'hg.create.write_on_repogroup.true'])
 
     def test_inactive_user_group_does_not_affect_global_permissions_inverse(self):
         # Issue #138: Inactive User Groups affecting permissions
         # Add user to inactive user group, set specific permissions on user
         # group and disable inherit-from-default. User permissions should still
         # inherit from default.
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
@@ -500,16 +465,15 @@
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
 
-        self.assertEqual(u1_auth.permissions['global'],
-                         set(['hg.create.repository', 'hg.fork.repository',
+        assert u1_auth.permissions['global'] == set(['hg.create.repository', 'hg.fork.repository',
                               'hg.register.manual_activate',
                               'hg.extern_activate.auto',
                               'repository.read', 'group.read',
                               'usergroup.read',
-                              'hg.create.write_on_repogroup.true']))
+                              'hg.create.write_on_repogroup.true'])
 
     def test_inactive_user_group_does_not_affect_repo_permissions(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
@@ -517,7 +481,7 @@
 
         # note: make u2 repo owner rather than u1, because the owner always has
         # admin permissions
-        self.test_repo = fixture.create_repo(name='myownrepo',
+        self.test_repo = fixture.create_repo(name=u'myownrepo',
                                              repo_type='hg',
                                              cur_user=self.u2)
 
@@ -531,11 +495,10 @@
                                           perm='repository.write')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.write')
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.write'
 
     def test_inactive_user_group_does_not_affect_repo_permissions_inverse(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
@@ -543,7 +506,7 @@
 
         # note: make u2 repo owner rather than u1, because the owner always has
         # admin permissions
-        self.test_repo = fixture.create_repo(name='myownrepo',
+        self.test_repo = fixture.create_repo(name=u'myownrepo',
                                              repo_type='hg',
                                              cur_user=self.u2)
 
@@ -557,17 +520,16 @@
                                           perm='repository.admin')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.admin')
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.admin'
 
     def test_inactive_user_group_does_not_affect_repo_group_permissions(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
 
-        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'group1', skip_if_exists=True)
 
         # enable admin access for user group on repo group
         RepoGroupModel().grant_user_group_permission(self.g1,
@@ -579,17 +541,16 @@
                                                perm='group.write')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.write'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.write'}
 
     def test_inactive_user_group_does_not_affect_repo_group_permissions_inverse(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
 
-        self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
+        self.g1 = fixture.create_repo_group(u'group1', skip_if_exists=True)
 
         # enable only write access for user group on repo group
         RepoGroupModel().grant_user_group_permission(self.g1,
@@ -601,17 +562,16 @@
                                                perm='group.admin')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories_groups'],
-                         {u'group1': u'group.admin'})
+        assert u1_auth.permissions['repositories_groups'] == {u'group1': u'group.admin'}
 
     def test_inactive_user_group_does_not_affect_user_group_permissions(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
 
-        self.ug2 = fixture.create_user_group('G2')
+        self.ug2 = fixture.create_user_group(u'G2')
 
         # enable admin access for user group on user group
         UserGroupModel().grant_user_group_permission(self.ug2,
@@ -623,17 +583,17 @@
                                                perm='usergroup.write')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['user_groups'][u'G1'], u'usergroup.read')
-        self.assertEqual(u1_auth.permissions['user_groups'][u'G2'], u'usergroup.write')
+        assert u1_auth.permissions['user_groups'][u'G1'] == u'usergroup.read'
+        assert u1_auth.permissions['user_groups'][u'G2'] == u'usergroup.write'
 
     def test_inactive_user_group_does_not_affect_user_group_permissions_inverse(self):
-        self.ug1 = fixture.create_user_group('G1')
+        self.ug1 = fixture.create_user_group(u'G1')
         self.ug1.inherit_default_permissions = False
         user_group_model = UserGroupModel()
         user_group_model.add_user_to_group(self.ug1, self.u1)
         user_group_model.update(self.ug1, {'users_group_active': False})
 
-        self.ug2 = fixture.create_user_group('G2')
+        self.ug2 = fixture.create_user_group(u'G2')
 
         # enable only write access for user group on user group
         UserGroupModel().grant_user_group_permission(self.ug2,
@@ -645,21 +605,20 @@
                                                perm='usergroup.admin')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['user_groups'][u'G1'], u'usergroup.read')
-        self.assertEqual(u1_auth.permissions['user_groups'][u'G2'], u'usergroup.admin')
+        assert u1_auth.permissions['user_groups'][u'G1'] == u'usergroup.read'
+        assert u1_auth.permissions['user_groups'][u'G2'] == u'usergroup.admin'
 
     def test_owner_permissions_doesnot_get_overwritten_by_group(self):
-        #create repo as USER,
-        self.test_repo = fixture.create_repo(name='myownrepo',
+        # create repo as USER,
+        self.test_repo = fixture.create_repo(name=u'myownrepo',
                                              repo_type='hg',
                                              cur_user=self.u1)
 
-        #he has permissions of admin as owner
+        # he has permissions of admin as owner
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.admin')
-        #set his permission as user group, he should still be admin
-        self.ug1 = fixture.create_user_group('G1')
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.admin'
+        # set his permission as user group, he should still be admin
+        self.ug1 = fixture.create_user_group(u'G1')
         UserGroupModel().add_user_to_group(self.ug1, self.u1)
         RepoModel().grant_user_group_permission(self.test_repo,
                                                  group_name=self.ug1,
@@ -667,34 +626,29 @@
 
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.admin')
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.admin'
 
     def test_owner_permissions_doesnot_get_overwritten_by_others(self):
-        #create repo as USER,
-        self.test_repo = fixture.create_repo(name='myownrepo',
+        # create repo as USER,
+        self.test_repo = fixture.create_repo(name=u'myownrepo',
                                              repo_type='hg',
                                              cur_user=self.u1)
 
-        #he has permissions of admin as owner
+        # he has permissions of admin as owner
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.admin')
-        #set his permission as user, he should still be admin
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.admin'
+        # set his permission as user, he should still be admin
         RepoModel().grant_user_permission(self.test_repo, user=self.u1,
                                           perm='repository.none')
         Session().commit()
         u1_auth = AuthUser(user_id=self.u1.user_id)
-        self.assertEqual(u1_auth.permissions['repositories']['myownrepo'],
-                         'repository.admin')
+        assert u1_auth.permissions['repositories']['myownrepo'] == 'repository.admin'
 
     def _test_def_perm_equal(self, user, change_factor=0):
-        perms = UserToPerm.query()\
-                .filter(UserToPerm.user == user)\
+        perms = UserToPerm.query() \
+                .filter(UserToPerm.user == user) \
                 .all()
-        self.assertEqual(len(perms),
-                         len(Permission.DEFAULT_USER_PERMISSIONS,)+change_factor,
-                         msg=perms)
+        assert len(perms) == len(Permission.DEFAULT_USER_PERMISSIONS,)+change_factor, perms
 
     def test_set_default_permissions(self):
         PermissionModel().create_default_permissions(user=self.u1)
@@ -703,20 +657,20 @@
     def test_set_default_permissions_after_one_is_missing(self):
         PermissionModel().create_default_permissions(user=self.u1)
         self._test_def_perm_equal(user=self.u1)
-        #now we delete one, it should be re-created after another call
-        perms = UserToPerm.query()\
-                .filter(UserToPerm.user == self.u1)\
+        # now we delete one, it should be re-created after another call
+        perms = UserToPerm.query() \
+                .filter(UserToPerm.user == self.u1) \
                 .all()
         Session().delete(perms[0])
         Session().commit()
 
         self._test_def_perm_equal(user=self.u1, change_factor=-1)
 
-        #create missing one !
+        # create missing one !
         PermissionModel().create_default_permissions(user=self.u1)
         self._test_def_perm_equal(user=self.u1)
 
-    @parameterized.expand([
+    @parametrize('perm,modify_to', [
         ('repository.read', 'repository.none'),
         ('group.read', 'group.none'),
         ('usergroup.read', 'usergroup.none'),
@@ -730,16 +684,15 @@
 
         old = Permission.get_by_key(perm)
         new = Permission.get_by_key(modify_to)
-        self.assertNotEqual(old, None)
-        self.assertNotEqual(new, None)
+        assert old is not None
+        assert new is not None
 
-        #now modify permissions
-        p = UserToPerm.query()\
-                .filter(UserToPerm.user == self.u1)\
-                .filter(UserToPerm.permission == old)\
+        # now modify permissions
+        p = UserToPerm.query() \
+                .filter(UserToPerm.user == self.u1) \
+                .filter(UserToPerm.permission == old) \
                 .one()
         p.permission = new
-        Session().add(p)
         Session().commit()
 
         PermissionModel().create_default_permissions(user=self.u1)
--- a/kallithea/tests/models/test_repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,8 @@
 import os
+import pytest
 from sqlalchemy.exc import IntegrityError
 
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 
 from kallithea.model.repo_group import RepoGroupModel
@@ -13,39 +14,39 @@
 fixture = Fixture()
 
 
-def _update_group(id_, group_name, desc='desc', parent_id=None):
-    form_data = fixture._get_group_create_params(group_name=group_name,
-                                                 group_desc=desc,
-                                                 group_parent_id=parent_id)
-    gr = RepoGroupModel().update(id_, form_data)
-    return gr
+def _update_repo_group(id_, group_name, desc=u'desc', parent_id=None):
+    form_data = dict(
+        group_name=group_name,
+        group_description=desc,
+        parent_group_id=parent_id,
+        )
+    return RepoGroupModel().update(id_, form_data)
 
 
 def _update_repo(name, **kwargs):
-    form_data = fixture._get_repo_create_params(**kwargs)
-    if not 'repo_name' in kwargs:
-        form_data['repo_name'] = name
-    if not 'perms_new' in kwargs:
-        form_data['perms_new'] = []
-    if not 'perms_updates' in kwargs:
-        form_data['perms_updates'] = []
-    r = RepoModel().update(name, **form_data)
+    if 'repo_name' not in kwargs:
+        kwargs['repo_name'] = name
+    if 'perms_new' not in kwargs:
+        kwargs['perms_new'] = []
+    if 'perms_updates' not in kwargs:
+        kwargs['perms_updates'] = []
+    r = RepoModel().update(name, **kwargs)
     return r
 
 
-class TestRepoGroups(BaseTestCase):
+class TestRepoGroups(TestController):
 
-    def setUp(self):
-        self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
-        self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
-        self.g3 = fixture.create_repo_group('test3', skip_if_exists=True)
+    def setup_method(self, method):
+        self.g1 = fixture.create_repo_group(u'test1', skip_if_exists=True)
+        self.g2 = fixture.create_repo_group(u'test2', skip_if_exists=True)
+        self.g3 = fixture.create_repo_group(u'test3', skip_if_exists=True)
 
-    def tearDown(self):
+    def teardown_method(self, method):
         Session.remove()
 
     def __check_path(self, *path):
         """
-        Checks the path for existance !
+        Checks the path for existence !
         """
         path = [TESTS_TMP_PATH] + list(path)
         path = os.path.join(*path)
@@ -58,142 +59,143 @@
         RepoGroupModel().delete(id_)
 
     def test_create_group(self):
-        g = fixture.create_repo_group('newGroup')
+        g = fixture.create_repo_group(u'newGroup')
         Session().commit()
-        self.assertEqual(g.full_path, 'newGroup')
+        assert g.full_path == 'newGroup'
 
-        self.assertTrue(self.__check_path('newGroup'))
+        assert self.__check_path('newGroup')
 
     def test_create_same_name_group(self):
-        self.assertRaises(IntegrityError, lambda: fixture.create_repo_group('newGroup'))
+        with pytest.raises(IntegrityError):
+            fixture.create_repo_group(u'newGroup')
         Session().rollback()
 
     def test_same_subgroup(self):
-        sg1 = fixture.create_repo_group('sub1', group_parent_id=self.g1.group_id)
-        self.assertEqual(sg1.parent_group, self.g1)
-        self.assertEqual(sg1.full_path, 'test1/sub1')
-        self.assertTrue(self.__check_path('test1', 'sub1'))
+        sg1 = fixture.create_repo_group(u'sub1', parent_group_id=self.g1.group_id)
+        assert sg1.parent_group == self.g1
+        assert sg1.full_path == 'test1/sub1'
+        assert self.__check_path('test1', 'sub1')
 
-        ssg1 = fixture.create_repo_group('subsub1', group_parent_id=sg1.group_id)
-        self.assertEqual(ssg1.parent_group, sg1)
-        self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
-        self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
+        ssg1 = fixture.create_repo_group(u'subsub1', parent_group_id=sg1.group_id)
+        assert ssg1.parent_group == sg1
+        assert ssg1.full_path == 'test1/sub1/subsub1'
+        assert self.__check_path('test1', 'sub1', 'subsub1')
 
     def test_remove_group(self):
-        sg1 = fixture.create_repo_group('deleteme')
+        sg1 = fixture.create_repo_group(u'deleteme')
         self.__delete_group(sg1.group_id)
 
-        self.assertEqual(RepoGroup.get(sg1.group_id), None)
-        self.assertFalse(self.__check_path('deteteme'))
+        assert RepoGroup.get(sg1.group_id) is None
+        assert not self.__check_path('deteteme')
 
-        sg1 = fixture.create_repo_group('deleteme', group_parent_id=self.g1.group_id)
+        sg1 = fixture.create_repo_group(u'deleteme', parent_group_id=self.g1.group_id)
         self.__delete_group(sg1.group_id)
 
-        self.assertEqual(RepoGroup.get(sg1.group_id), None)
-        self.assertFalse(self.__check_path('test1', 'deteteme'))
+        assert RepoGroup.get(sg1.group_id) is None
+        assert not self.__check_path('test1', 'deteteme')
 
     def test_rename_single_group(self):
-        sg1 = fixture.create_repo_group('initial')
+        sg1 = fixture.create_repo_group(u'initial')
 
-        new_sg1 = _update_group(sg1.group_id, 'after')
-        self.assertTrue(self.__check_path('after'))
-        self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
+        new_sg1 = _update_repo_group(sg1.group_id, u'after')
+        assert self.__check_path('after')
+        assert RepoGroup.get_by_group_name(u'initial') is None
 
     def test_update_group_parent(self):
 
-        sg1 = fixture.create_repo_group('initial', group_parent_id=self.g1.group_id)
+        sg1 = fixture.create_repo_group(u'initial', parent_group_id=self.g1.group_id)
 
-        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
-        self.assertTrue(self.__check_path('test1', 'after'))
-        self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
+        new_sg1 = _update_repo_group(sg1.group_id, u'after', parent_id=self.g1.group_id)
+        assert self.__check_path('test1', 'after')
+        assert RepoGroup.get_by_group_name(u'test1/initial') is None
 
-        new_sg1 = _update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
-        self.assertTrue(self.__check_path('test3', 'after'))
-        self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
+        new_sg1 = _update_repo_group(sg1.group_id, u'after', parent_id=self.g3.group_id)
+        assert self.__check_path('test3', 'after')
+        assert RepoGroup.get_by_group_name(u'test3/initial') == None
 
-        new_sg1 = _update_group(sg1.group_id, 'hello')
-        self.assertTrue(self.__check_path('hello'))
+        new_sg1 = _update_repo_group(sg1.group_id, u'hello')
+        assert self.__check_path('hello')
 
-        self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
+        assert RepoGroup.get_by_group_name(u'hello') == new_sg1
 
     def test_subgrouping_with_repo(self):
 
-        g1 = fixture.create_repo_group('g1')
-        g2 = fixture.create_repo_group('g2')
+        g1 = fixture.create_repo_group(u'g1')
+        g2 = fixture.create_repo_group(u'g2')
         # create new repo
-        r = fixture.create_repo('john')
+        r = fixture.create_repo(u'john')
 
-        self.assertEqual(r.repo_name, 'john')
+        assert r.repo_name == 'john'
         # put repo into group
-        r = _update_repo('john', repo_group=g1.group_id)
+        r = _update_repo(u'john', repo_group=g1.group_id)
         Session().commit()
-        self.assertEqual(r.repo_name, 'g1/john')
+        assert r.repo_name == 'g1/john'
 
-        _update_group(g1.group_id, 'g1', parent_id=g2.group_id)
-        self.assertTrue(self.__check_path('g2', 'g1'))
+        _update_repo_group(g1.group_id, u'g1', parent_id=g2.group_id)
+        assert self.__check_path('g2', 'g1')
 
         # test repo
-        self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
-                                                                r.just_name]))
+        assert r.repo_name == RepoGroup.url_sep().join(['g2', 'g1',
+                                                                r.just_name])
 
     def test_move_to_root(self):
-        g1 = fixture.create_repo_group('t11')
-        g2 = fixture.create_repo_group('t22', group_parent_id=g1.group_id)
+        g1 = fixture.create_repo_group(u't11')
+        g2 = fixture.create_repo_group(u't22', parent_group_id=g1.group_id)
 
-        self.assertEqual(g2.full_path, 't11/t22')
-        self.assertTrue(self.__check_path('t11', 't22'))
+        assert g2.full_path == 't11/t22'
+        assert self.__check_path('t11', 't22')
 
-        g2 = _update_group(g2.group_id, 'g22', parent_id=None)
+        g2 = _update_repo_group(g2.group_id, u'g22', parent_id=None)
         Session().commit()
 
-        self.assertEqual(g2.group_name, 'g22')
+        assert g2.group_name == 'g22'
         # we moved out group from t1 to '' so it's full path should be 'g2'
-        self.assertEqual(g2.full_path, 'g22')
-        self.assertFalse(self.__check_path('t11', 't22'))
-        self.assertTrue(self.__check_path('g22'))
+        assert g2.full_path == 'g22'
+        assert not self.__check_path('t11', 't22')
+        assert self.__check_path('g22')
 
     def test_rename_top_level_group_in_nested_setup(self):
-        g1 = fixture.create_repo_group('L1')
-        g2 = fixture.create_repo_group('L2', group_parent_id=g1.group_id)
-        g3 = fixture.create_repo_group('L3', group_parent_id=g2.group_id)
+        g1 = fixture.create_repo_group(u'L1')
+        g2 = fixture.create_repo_group(u'L2', parent_group_id=g1.group_id)
+        g3 = fixture.create_repo_group(u'L3', parent_group_id=g2.group_id)
 
-        r = fixture.create_repo('L1/L2/L3/L3_REPO', repo_group=g3.group_id)
+        r = fixture.create_repo(u'L1/L2/L3/L3_REPO', repo_group=g3.group_id)
 
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'L1_NEW')
+        ## rename L1 all groups should be now changed
+        _update_repo_group(g1.group_id, u'L1_NEW')
         Session().commit()
-        self.assertEqual(g1.full_path, 'L1_NEW')
-        self.assertEqual(g2.full_path, 'L1_NEW/L2')
-        self.assertEqual(g3.full_path, 'L1_NEW/L2/L3')
-        self.assertEqual(r.repo_name,  'L1_NEW/L2/L3/L3_REPO')
+        assert g1.full_path == 'L1_NEW'
+        assert g2.full_path == 'L1_NEW/L2'
+        assert g3.full_path == 'L1_NEW/L2/L3'
+        assert r.repo_name == 'L1_NEW/L2/L3/L3_REPO'
 
     def test_change_parent_of_top_level_group_in_nested_setup(self):
-        g1 = fixture.create_repo_group('R1')
-        g2 = fixture.create_repo_group('R2', group_parent_id=g1.group_id)
-        g3 = fixture.create_repo_group('R3', group_parent_id=g2.group_id)
-        g4 = fixture.create_repo_group('R1_NEW')
+        g1 = fixture.create_repo_group(u'R1')
+        g2 = fixture.create_repo_group(u'R2', parent_group_id=g1.group_id)
+        g3 = fixture.create_repo_group(u'R3', parent_group_id=g2.group_id)
+        g4 = fixture.create_repo_group(u'R1_NEW')
 
-        r = fixture.create_repo('R1/R2/R3/R3_REPO', repo_group=g3.group_id)
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'R1', parent_id=g4.group_id)
+        r = fixture.create_repo(u'R1/R2/R3/R3_REPO', repo_group=g3.group_id)
+        ## rename L1 all groups should be now changed
+        _update_repo_group(g1.group_id, u'R1', parent_id=g4.group_id)
         Session().commit()
-        self.assertEqual(g1.full_path, 'R1_NEW/R1')
-        self.assertEqual(g2.full_path, 'R1_NEW/R1/R2')
-        self.assertEqual(g3.full_path, 'R1_NEW/R1/R2/R3')
-        self.assertEqual(r.repo_name,  'R1_NEW/R1/R2/R3/R3_REPO')
+        assert g1.full_path == 'R1_NEW/R1'
+        assert g2.full_path == 'R1_NEW/R1/R2'
+        assert g3.full_path == 'R1_NEW/R1/R2/R3'
+        assert r.repo_name == 'R1_NEW/R1/R2/R3/R3_REPO'
 
     def test_change_parent_of_top_level_group_in_nested_setup_with_rename(self):
-        g1 = fixture.create_repo_group('X1')
-        g2 = fixture.create_repo_group('X2', group_parent_id=g1.group_id)
-        g3 = fixture.create_repo_group('X3', group_parent_id=g2.group_id)
-        g4 = fixture.create_repo_group('X1_NEW')
+        g1 = fixture.create_repo_group(u'X1')
+        g2 = fixture.create_repo_group(u'X2', parent_group_id=g1.group_id)
+        g3 = fixture.create_repo_group(u'X3', parent_group_id=g2.group_id)
+        g4 = fixture.create_repo_group(u'X1_NEW')
 
-        r = fixture.create_repo('X1/X2/X3/X3_REPO', repo_group=g3.group_id)
+        r = fixture.create_repo(u'X1/X2/X3/X3_REPO', repo_group=g3.group_id)
 
-        ##rename L1 all groups should be now changed
-        _update_group(g1.group_id, 'X1_PRIM', parent_id=g4.group_id)
+        ## rename L1 all groups should be now changed
+        _update_repo_group(g1.group_id, u'X1_PRIM', parent_id=g4.group_id)
         Session().commit()
-        self.assertEqual(g1.full_path, 'X1_NEW/X1_PRIM')
-        self.assertEqual(g2.full_path, 'X1_NEW/X1_PRIM/X2')
-        self.assertEqual(g3.full_path, 'X1_NEW/X1_PRIM/X2/X3')
-        self.assertEqual(r.repo_name,  'X1_NEW/X1_PRIM/X2/X3/X3_REPO')
+        assert g1.full_path == 'X1_NEW/X1_PRIM'
+        assert g2.full_path == 'X1_NEW/X1_PRIM/X2'
+        assert g3.full_path == 'X1_NEW/X1_PRIM/X2/X3'
+        assert r.repo_name == 'X1_NEW/X1_PRIM/X2/X3/X3_REPO'
--- a/kallithea/tests/models/test_repos.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_repos.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,5 @@
-from kallithea.tests import *
+import pytest
+from kallithea.tests.base import *
 
 from kallithea.model.meta import Session
 from kallithea.tests.fixture import Fixture
@@ -9,69 +10,71 @@
 fixture = Fixture()
 
 
-class TestRepos(BaseTestCase):
+class TestRepos(TestController):
 
-    def setUp(self):
-        pass
-
-    def tearDown(self):
+    def teardown_method(self, method):
         Session.remove()
 
     def test_remove_repo(self):
-        repo = fixture.create_repo(name='test-repo-1')
+        repo = fixture.create_repo(name=u'test-repo-1')
         Session().commit()
 
         RepoModel().delete(repo=repo)
         Session().commit()
 
-        self.assertEqual(None, Repository.get_by_repo_name(repo_name='test-repo-1'))
+        assert Repository.get_by_repo_name(repo_name=u'test-repo-1') is None
 
     def test_remove_repo_repo_raises_exc_when_attached_forks(self):
-        repo = fixture.create_repo(name='test-repo-1')
+        repo = fixture.create_repo(name=u'test-repo-1')
         Session().commit()
 
-        fixture.create_fork(repo.repo_name, 'test-repo-fork-1')
+        fixture.create_fork(repo.repo_name, u'test-repo-fork-1')
         Session().commit()
 
-        self.assertRaises(AttachedForksError, lambda: RepoModel().delete(repo=repo))
-
-    def test_remove_repo_delete_forks(self):
-        repo = fixture.create_repo(name='test-repo-1')
+        with pytest.raises(AttachedForksError):
+            RepoModel().delete(repo=repo)
+        # cleanup
+        RepoModel().delete(repo=u'test-repo-fork-1')
+        RepoModel().delete(repo=u'test-repo-1')
         Session().commit()
 
-        fork = fixture.create_fork(repo.repo_name, 'test-repo-fork-1')
+    def test_remove_repo_delete_forks(self):
+        repo = fixture.create_repo(name=u'test-repo-1')
         Session().commit()
 
-        #fork of fork
-        fixture.create_fork(fork.repo_name, 'test-repo-fork-fork-1')
+        fork = fixture.create_fork(repo.repo_name, u'test-repo-fork-1')
+        Session().commit()
+
+        # fork of fork
+        fixture.create_fork(fork.repo_name, u'test-repo-fork-fork-1')
         Session().commit()
 
         RepoModel().delete(repo=repo, forks='delete')
         Session().commit()
 
-        self.assertEqual(None, Repository.get_by_repo_name(repo_name='test-repo-1'))
-        self.assertEqual(None, Repository.get_by_repo_name(repo_name='test-repo-fork-1'))
-        self.assertEqual(None, Repository.get_by_repo_name(repo_name='test-repo-fork-fork-1'))
+        assert Repository.get_by_repo_name(repo_name=u'test-repo-1') is None
+        assert Repository.get_by_repo_name(repo_name=u'test-repo-fork-1') is None
+        assert Repository.get_by_repo_name(repo_name=u'test-repo-fork-fork-1') is None
 
     def test_remove_repo_detach_forks(self):
-        repo = fixture.create_repo(name='test-repo-1')
+        repo = fixture.create_repo(name=u'test-repo-1')
         Session().commit()
 
-        fork = fixture.create_fork(repo.repo_name, 'test-repo-fork-1')
+        fork = fixture.create_fork(repo.repo_name, u'test-repo-fork-1')
         Session().commit()
 
-        #fork of fork
-        fixture.create_fork(fork.repo_name, 'test-repo-fork-fork-1')
+        # fork of fork
+        fixture.create_fork(fork.repo_name, u'test-repo-fork-fork-1')
         Session().commit()
 
         RepoModel().delete(repo=repo, forks='detach')
         Session().commit()
 
         try:
-            self.assertEqual(None, Repository.get_by_repo_name(repo_name='test-repo-1'))
-            self.assertNotEqual(None, Repository.get_by_repo_name(repo_name='test-repo-fork-1'))
-            self.assertNotEqual(None, Repository.get_by_repo_name(repo_name='test-repo-fork-fork-1'))
+            assert Repository.get_by_repo_name(repo_name=u'test-repo-1') is None
+            assert Repository.get_by_repo_name(repo_name=u'test-repo-fork-1') is not None
+            assert Repository.get_by_repo_name(repo_name=u'test-repo-fork-fork-1') is not None
         finally:
-            RepoModel().delete(repo='test-repo-fork-fork-1')
-            RepoModel().delete(repo='test-repo-fork-1')
+            RepoModel().delete(repo=u'test-repo-fork-fork-1')
+            RepoModel().delete(repo=u'test-repo-fork-1')
             Session().commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/models/test_settings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,46 @@
+from kallithea.model.meta import Session
+from kallithea.model.db import Setting
+
+
+name = 'spam-setting-name'
+
+
+def test_passing_list_setting_value_results_in_string_valued_setting():
+    assert Setting.get_by_name(name) is None
+    setting = Setting.create_or_update(name, ['spam', 'eggs'])
+    Session().flush() # must flush so we can delete it below
+    try:
+        assert Setting.get_by_name(name) is not None
+        # Quirk: list value is stringified.
+        assert Setting.get_by_name(name).app_settings_value \
+               == "['spam', 'eggs']"
+        assert Setting.get_by_name(name).app_settings_type == 'unicode'
+    finally:
+        Session().delete(setting)
+
+
+def test_list_valued_setting_creation_requires_manual_value_formatting():
+    assert Setting.get_by_name(name) is None
+    # Quirk: need manual formatting of list setting value.
+    setting = Setting.create_or_update(name, 'spam,eggs', type='list')
+    Session().flush() # must flush so we can delete it below
+    try:
+        assert setting.app_settings_value == ['spam', 'eggs']
+    finally:
+        Session().delete(setting)
+
+
+def test_list_valued_setting_update():
+    assert Setting.get_by_name(name) is None
+    setting = Setting.create_or_update(name, 'spam', type='list')
+    Session().flush() # must flush so we can delete it below
+    try:
+        assert setting.app_settings_value == [u'spam']
+        # Assign back setting value.
+        setting.app_settings_value = setting.app_settings_value
+        # Quirk: value is stringified on write and listified on read.
+        assert setting.app_settings_value == ["[u'spam']"]
+        setting.app_settings_value = setting.app_settings_value
+        assert setting.app_settings_value == ["[u\"[u'spam']\"]"]
+    finally:
+        Session().delete(setting)
--- a/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_user_group_permissions_on_repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -17,7 +17,7 @@
 _get_group_perms = None
 
 
-def permissions_setup_func(group_name='g0', perm='group.read', recursive='all'):
+def permissions_setup_func(group_name=u'g0', perm='group.read', recursive='all'):
     """
     Resets all permissions to perm attribute
     """
@@ -44,7 +44,7 @@
     Session().commit()
     test_u2_id = test_u2.user_id
 
-    gr1 = fixture.create_user_group('perms_group_1')
+    gr1 = fixture.create_user_group(u'perms_group_1')
     Session().commit()
     test_u2_gr_id = gr1.users_group_id
     UserGroupModel().add_user_to_group(gr1, user=test_u2_id)
@@ -58,45 +58,45 @@
 
 def teardown_module():
     _destroy_project_tree(test_u2_id)
-    fixture.destroy_user_group('perms_group_1')
+    fixture.destroy_user_group(u'perms_group_1')
 
 
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
-    group = 'g0'
+    group = u'g0'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     items = [x for x in _get_repo_perms(group, recursive)]
     expected = 0
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     items = [x for x in _get_group_perms(group, recursive)]
     expected = 1
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     items = [x for x in _get_repo_perms(group, recursive)]
     expected = 0
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     items = [x for x in _get_group_perms(group, recursive)]
     expected = 1
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode():
@@ -104,7 +104,7 @@
     # set permission to g0 recursive mode, all children including
     # other repos and groups should have this permission now set !
     recursive = 'all'
-    group = 'g0'
+    group = u'g0'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -112,16 +112,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        check_tree_perms(name, perm, group, 'repository.write')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_3'
+    group = u'g0/g0_3'
     permissions_setup_func(group, 'group.none', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -129,16 +129,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
+        check_tree_perms(name, perm, group, 'repository.none')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
+        check_tree_perms(name, perm, group, 'group.none')
 
 
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_1/g0_1_1'
+    group = u'g0/g0_1/g0_1_1'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -146,16 +146,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        check_tree_perms(name, perm, group, 'repository.write')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_2'
+    group = u'g0/g0_2'
     permissions_setup_func(group, 'group.admin', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -163,16 +163,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.admin'
+        check_tree_perms(name, perm, group, 'repository.admin')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.admin'
+        check_tree_perms(name, perm, group, 'group.admin')
 
 
 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'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     perm = 'group.write'
     permissions_setup_func(group, perm, recursive=recursive)
 
@@ -181,7 +181,7 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        check_tree_perms(name, perm, group, 'repository.write')
 
     for name, perm in items:
         # permission is set with repos only mode, but we also change the permission
@@ -190,13 +190,13 @@
         old_perm = 'group.read'
         if name == group:
             old_perm = perm
-        yield check_tree_perms, name, perm, group, old_perm
+        check_tree_perms(name, perm, group, old_perm)
 
 
 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'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     perm = 'group.none'
     permissions_setup_func(group, perm, recursive=recursive)
 
@@ -205,7 +205,7 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
+        check_tree_perms(name, perm, group, 'group.none')
--- a/kallithea/tests/models/test_user_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_user_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,6 +1,6 @@
-from kallithea.model.db import User
+from kallithea.model.db import User, UserGroup
 
-from kallithea.tests import BaseTestCase, parameterized, TEST_USER_REGULAR_LOGIN
+from kallithea.tests.base import *
 from kallithea.tests.fixture import Fixture
 
 from kallithea.model.user_group import UserGroupModel
@@ -10,27 +10,27 @@
 fixture = Fixture()
 
 
-class TestUserGroups(BaseTestCase):
+class TestUserGroups(TestController):
 
-    def tearDown(self):
+    def teardown_method(self, method):
         # delete all groups
-        for gr in UserGroupModel.get_all():
+        for gr in UserGroup.query():
             fixture.destroy_user_group(gr)
         Session().commit()
 
-    @parameterized.expand([
+    @parametrize('pre_existing,regular_should_be,external_should_be,groups,expected', [
         ([], [], [], [], []),
-        ([], ['regular'], [], [], ['regular']),  # no changes of regular
-        (['some_other'], [], [], ['some_other'], []),   # not added to regular group
-        ([], ['regular'], ['container'], ['container'], ['regular', 'container']),
-        ([], ['regular'], [], ['container', 'container2'], ['regular', 'container', 'container2']),
-        ([], ['regular'], ['other'], [], ['regular']),  # remove not used
-        (['some_other'], ['regular'], ['other', 'container'], ['container', 'container2'], ['regular', 'container', 'container2']),
+        ([], [u'regular'], [], [], [u'regular']),  # no changes of regular
+        ([u'some_other'], [], [], [u'some_other'], []),   # not added to regular group
+        ([], [u'regular'], [u'container'], [u'container'], [u'regular', u'container']),
+        ([], [u'regular'], [], [u'container', u'container2'], [u'regular', u'container', u'container2']),
+        ([], [u'regular'], [u'other'], [], [u'regular']),  # remove not used
+        ([u'some_other'], [u'regular'], [u'other', u'container'], [u'container', u'container2'], [u'regular', u'container', u'container2']),
     ])
     def test_enforce_groups(self, pre_existing, regular_should_be,
                             external_should_be, groups, expected):
         # delete all groups
-        for gr in UserGroupModel.get_all():
+        for gr in UserGroup.query():
             fixture.destroy_user_group(gr)
         Session().commit()
 
@@ -58,4 +58,4 @@
 
         user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
         in_groups = user.group_member
-        self.assertEqual(expected, [x.users_group.users_group_name for x in in_groups])
+        assert expected == [x.users_group.users_group_name for x in in_groups]
--- a/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_user_permissions_on_repo_groups.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,7 @@
 import functools
 
 from kallithea.model.repo_group import RepoGroupModel
-from kallithea.model.db import RepoGroup, User
+from kallithea.model.db import RepoGroup, Repository, User
 
 from kallithea.model.meta import Session
 from kallithea.tests.models.common import _create_project_tree, check_tree_perms, \
@@ -13,7 +13,7 @@
 _get_group_perms = None
 
 
-def permissions_setup_func(group_name='g0', perm='group.read', recursive='all',
+def permissions_setup_func(group_name=u'g0', perm='group.read', recursive='all',
                            user_id=None):
     """
     Resets all permissions to perm attribute
@@ -58,39 +58,39 @@
 def test_user_permissions_on_group_without_recursive_mode():
     # set permission to g0 non-recursive mode
     recursive = 'none'
-    group = 'g0'
+    group = u'g0'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     items = [x for x in _get_repo_perms(group, recursive)]
     expected = 0
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     items = [x for x in _get_group_perms(group, recursive)]
     expected = 1
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_without_recursive_mode_subgroup():
     # set permission to g0 non-recursive mode
     recursive = 'none'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     items = [x for x in _get_repo_perms(group, recursive)]
     expected = 0
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     items = [x for x in _get_group_perms(group, recursive)]
     expected = 1
     assert len(items) == expected, ' %s != %s' % (len(items), expected)
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode():
@@ -98,7 +98,7 @@
     # set permission to g0 recursive mode, all children including
     # other repos and groups should have this permission now set !
     recursive = 'all'
-    group = 'g0'
+    group = u'g0'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -106,10 +106,10 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        check_tree_perms(name, perm, group, 'repository.write')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode_for_default_user():
@@ -117,7 +117,7 @@
     # set permission to g0 recursive mode, all children including
     # other repos and groups should have this permission now set !
     recursive = 'all'
-    group = 'g0'
+    group = u'g0'
     default_user_id = User.get_default_user().user_id
     permissions_setup_func(group, 'group.write', recursive=recursive,
                            user_id=default_user_id)
@@ -133,16 +133,18 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        # default user permissions do not "recurse into" private repos
+        is_private = Repository.get_by_repo_name(name).private
+        check_tree_perms(name, perm, group, 'repository.none' if is_private else 'repository.write')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode_inner_group():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_3'
+    group = u'g0/g0_3'
     permissions_setup_func(group, 'group.none', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -150,16 +152,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
+        check_tree_perms(name, perm, group, 'repository.none')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
+        check_tree_perms(name, perm, group, 'group.none')
 
 
 def test_user_permissions_on_group_with_recursive_mode_deepest():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_1/g0_1_1'
+    group = u'g0/g0_1/g0_1_1'
     permissions_setup_func(group, 'group.write', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -167,16 +169,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.write'
+        check_tree_perms(name, perm, group, 'repository.write')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 def test_user_permissions_on_group_with_recursive_mode_only_with_repos():
     ## set permission to g0_3 group to none
     recursive = 'all'
-    group = 'g0/g0_2'
+    group = u'g0/g0_2'
     permissions_setup_func(group, 'group.admin', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -184,17 +186,17 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.admin'
+        check_tree_perms(name, perm, group, 'repository.admin')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.admin'
+        check_tree_perms(name, perm, group, 'group.admin')
 
 
 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!
     recursive = 'repos'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     perm = 'group.none'
     default_user_id = User.get_default_user().user_id
 
@@ -212,7 +214,7 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
+        check_tree_perms(name, perm, group, 'repository.none')
 
     for name, perm in items:
         # permission is set with repos only mode, but we also change the permission
@@ -221,13 +223,13 @@
         old_perm = 'group.read'
         if name == group:
             old_perm = perm
-        yield check_tree_perms, name, perm, group, old_perm
+        check_tree_perms(name, perm, group, old_perm)
 
 
 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'
-    group = 'g0/g0_3'
+    group = u'g0/g0_3'
     perm = 'group.none'
     permissions_setup_func(group, perm, recursive=recursive)
 
@@ -236,7 +238,7 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.none'
+        check_tree_perms(name, perm, group, 'repository.none')
 
     for name, perm in items:
         # permission is set with repos only mode, but we also change the permission
@@ -245,7 +247,7 @@
         old_perm = 'group.read'
         if name == group:
             old_perm = perm
-        yield check_tree_perms, name, perm, group, old_perm
+        check_tree_perms(name, perm, group, old_perm)
 
 
 def test_user_permissions_on_group_with_recursive_group_mode_for_default_user():
@@ -253,7 +255,7 @@
     # other groups should have this permission now set. repositories should
     # remain intact as we use groups only mode !
     recursive = 'groups'
-    group = 'g0/g0_1'
+    group = u'g0/g0_1'
     default_user_id = User.get_default_user().user_id
     permissions_setup_func(group, 'group.write', recursive=recursive,
                            user_id=default_user_id)
@@ -269,16 +271,16 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.write'
+        check_tree_perms(name, perm, group, 'group.write')
 
 
 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'
-    group = 'g0/g0_3'
+    group = u'g0/g0_3'
     permissions_setup_func(group, 'group.none', recursive=recursive)
 
     repo_items = [x for x in _get_repo_perms(group, recursive)]
@@ -286,7 +288,7 @@
     _check_expected_count(items, repo_items, expected_count(group, True))
 
     for name, perm in repo_items:
-        yield check_tree_perms, name, perm, group, 'repository.read'
+        check_tree_perms(name, perm, group, 'repository.read')
 
     for name, perm in items:
-        yield check_tree_perms, name, perm, group, 'group.none'
+        check_tree_perms(name, perm, group, 'group.none')
--- a/kallithea/tests/models/test_user_permissions_on_repos.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-#TODO; write tests when we activate algo for permissions.
--- a/kallithea/tests/models/test_users.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/models/test_users.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,9 @@
-from kallithea.tests import *
+import pytest
+from kallithea.tests.base import *
 
-from kallithea.model.db import User, UserGroup, UserGroupMember, UserEmailMap,\
+from kallithea.model.db import User, UserGroup, UserGroupMember, UserEmailMap, \
     Permission
 from kallithea.model.user import UserModel
-
 from kallithea.model.meta import Session
 from kallithea.model.user_group import UserGroupModel
 from kallithea.tests.fixture import Fixture
@@ -11,12 +11,13 @@
 fixture = Fixture()
 
 
-class TestUser(BaseTestCase):
-    def __init__(self, methodName='runTest'):
+class TestUser(TestController):
+
+    @classmethod
+    def setup_class(cls):
         Session.remove()
-        super(TestUser, self).__init__(methodName=methodName)
 
-    def tearDown(self):
+    def teardown_method(self, method):
         Session.remove()
 
     def test_create_and_remove(self):
@@ -25,36 +26,41 @@
                                            email=u'u232@example.com',
                                            firstname=u'u1', lastname=u'u1')
         Session().commit()
-        self.assertEqual(User.get_by_username(u'test_user'), usr)
+        assert User.get_by_username(u'test_user') == usr
+        assert User.get_by_username(u'test_USER', case_insensitive=True) == usr
+        # User.get_by_username without explicit request for case insensitivty
+        # will use database case sensitivity. The following will thus return
+        # None on for example PostgreSQL but find test_user on MySQL - we are
+        # fine with leaving that as undefined as long as it doesn't crash.
+        User.get_by_username(u'test_USER', case_insensitive=False)
 
         # make user group
-        user_group = fixture.create_user_group('some_example_group')
+        user_group = fixture.create_user_group(u'some_example_group')
         Session().commit()
 
         UserGroupModel().add_user_to_group(user_group, usr)
         Session().commit()
 
-        self.assertEqual(UserGroup.get(user_group.users_group_id), user_group)
-        self.assertEqual(UserGroupMember.query().count(), 1)
+        assert UserGroup.get(user_group.users_group_id) == user_group
+        assert UserGroupMember.query().count() == 1
         UserModel().delete(usr.user_id)
         Session().commit()
 
-        self.assertEqual(UserGroupMember.query().all(), [])
+        assert UserGroupMember.query().all() == []
 
-    def test_additonal_email_as_main(self):
+    def test_additional_email_as_main(self):
         usr = UserModel().create_or_update(username=u'test_user',
                                            password=u'qweqwe',
                                      email=u'main_email@example.com',
                                      firstname=u'u1', lastname=u'u1')
         Session().commit()
 
-        def do():
+        with pytest.raises(AttributeError):
             m = UserEmailMap()
             m.email = u'main_email@example.com'
             m.user = usr
             Session().add(m)
             Session().commit()
-        self.assertRaises(AttributeError, do)
 
         UserModel().delete(usr.user_id)
         Session().commit()
@@ -72,32 +78,38 @@
         Session().add(m)
         Session().commit()
 
+        u = User.get_by_email(email='MAIN_email@example.com')
+        assert usr.user_id == u.user_id
+        assert usr.username == u.username
+
         u = User.get_by_email(email='main_email@example.com')
-        self.assertEqual(usr.user_id, u.user_id)
-        self.assertEqual(usr.username, u.username)
+        assert usr.user_id == u.user_id
+        assert usr.username == u.username
 
         u = User.get_by_email(email='main_email2@example.com')
-        self.assertEqual(usr.user_id, u.user_id)
-        self.assertEqual(usr.username, u.username)
+        assert usr.user_id == u.user_id
+        assert usr.username == u.username
         u = User.get_by_email(email='main_email3@example.com')
-        self.assertEqual(None, u)
+        assert u is None
+
+        u = User.get_by_email(email='main_e%ail@example.com')
+        assert u is None
+        u = User.get_by_email(email='main_emai_@example.com')
+        assert u is None
 
         UserModel().delete(usr.user_id)
         Session().commit()
 
 
-class TestUsers(BaseTestCase):
+class TestUsers(TestController):
 
-    def __init__(self, methodName='runTest'):
-        super(TestUsers, self).__init__(methodName=methodName)
-
-    def setUp(self):
+    def setup_method(self, method):
         self.u1 = UserModel().create_or_update(username=u'u1',
                                         password=u'qweqwe',
                                         email=u'u1@example.com',
                                         firstname=u'u1', lastname=u'u1')
 
-    def tearDown(self):
+    def teardown_method(self, method):
         perm = Permission.query().all()
         for p in perm:
             UserModel().revoke_perm(self.u1, p)
@@ -110,21 +122,21 @@
         perm = Permission.query().all()[0]
         UserModel().grant_perm(self.u1, perm)
         Session().commit()
-        self.assertEqual(UserModel().has_perm(self.u1, perm), True)
+        assert UserModel().has_perm(self.u1, perm) == True
 
     def test_has_perm(self):
         perm = Permission.query().all()
         for p in perm:
             has_p = UserModel().has_perm(self.u1, p)
-            self.assertEqual(False, has_p)
+            assert False == has_p
 
     def test_revoke_perm(self):
         perm = Permission.query().all()[0]
         UserModel().grant_perm(self.u1, perm)
         Session().commit()
-        self.assertEqual(UserModel().has_perm(self.u1, perm), True)
+        assert UserModel().has_perm(self.u1, perm) == True
 
-        #revoke
+        # revoke
         UserModel().revoke_perm(self.u1, perm)
         Session().commit()
-        self.assertEqual(UserModel().has_perm(self.u1, perm), False)
+        assert UserModel().has_perm(self.u1, perm) == False
--- a/kallithea/tests/other/manual_test_vcs_operations.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,537 +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.other.manual_test_vcs_operations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Test suite for making push/pull operations.
-
-Run it in two terminals::
- paster serve kallithea/tests/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 re
-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:4999'  # 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@example.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@example.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://%s/tmp' % HOST)
-
-        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://%s/tmp' % HOST)
-
-        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)
-            # The message apparently changed in Git 1.8.3, so match it loosely.
-            assert re.search(r'\b403\b', 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 == ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/other/test_auth_ldap.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,138 @@
+from kallithea.lib.auth_modules import auth_ldap, authenticate
+from kallithea.model.db import Setting, User
+from kallithea.model.meta import Session
+import uuid
+import pytest
+
+
+@pytest.fixture
+def arrange_ldap_auth(set_test_settings):
+    set_test_settings(
+        ('auth_plugins', 'kallithea.lib.auth_modules.auth_ldap', 'list'),
+        ('auth_ldap_enabled', True, 'bool'),
+        ('auth_ldap_attr_firstname', 'test_ldap_firstname'),
+        ('auth_ldap_attr_lastname', 'test_ldap_lastname'),
+        ('auth_ldap_attr_email', 'test_ldap_email'))
+
+
+class _AuthLdapMock():
+
+    def __init__(self, **kwargs):
+        pass
+
+    def authenticate_ldap(self, username, password):
+        return 'spam dn', dict(test_ldap_firstname=[u'spam ldap first name'],
+                               test_ldap_lastname=[u'spam ldap last name'],
+                               test_ldap_email=['spam ldap email'])
+
+
+def test_update_user_attributes_from_ldap(monkeypatch, create_test_user,
+                                          arrange_ldap_auth):
+    """Authenticate user with mocked LDAP, verify attributes are updated.
+    """
+
+    # Arrange test user.
+    uniqifier = uuid.uuid4()
+    username = 'test-user-{0}'.format(uniqifier)
+    assert User.get_by_username(username) is None
+    user_input = dict(username='test-user-{0}'.format(uniqifier),
+                      password='spam password',
+                      email='spam-email-{0}'.format(uniqifier),
+                      firstname=u'spam first name',
+                      lastname=u'spam last name',
+                      active=True,
+                      admin=False)
+    user = create_test_user(user_input)
+
+    # Arrange LDAP auth.
+    monkeypatch.setattr(auth_ldap, 'AuthLdap', _AuthLdapMock)
+
+    # Authenticate with LDAP.
+    user_data = authenticate(username, 'password')
+
+    # Verify that authenication succeeded and retrieved correct attributes
+    # from LDAP.
+    assert user_data is not None
+    assert user_data.get('firstname') == u'spam ldap first name'
+    assert user_data.get('lastname') == u'spam ldap last name'
+    assert user_data.get('email') == 'spam ldap email'
+
+    # Verify that authentication overwrote user attributes with the ones
+    # retrieved from LDAP.
+    assert user.firstname == u'spam ldap first name'
+    assert user.lastname == u'spam ldap last name'
+    assert user.email == 'spam ldap email'
+
+
+def test_init_user_attributes_from_ldap(monkeypatch, arrange_ldap_auth):
+    """Authenticate unknown user with mocked LDAP, verify user is created.
+    """
+
+    # Arrange test user.
+    uniqifier = uuid.uuid4()
+    username = 'test-user-{0}'.format(uniqifier)
+    assert User.get_by_username(username) is None
+
+    # Arrange LDAP auth.
+    monkeypatch.setattr(auth_ldap, 'AuthLdap', _AuthLdapMock)
+
+    # Authenticate with LDAP.
+    user_data = authenticate(username, 'password')
+
+    # Verify that authenication succeeded and retrieved correct attributes
+    # from LDAP.
+    assert user_data is not None
+    assert user_data.get('firstname') == u'spam ldap first name'
+    assert user_data.get('lastname') == u'spam ldap last name'
+    assert user_data.get('email') == 'spam ldap email'
+
+    # Verify that authentication created new user with attributes
+    # retrieved from LDAP.
+    new_user = User.get_by_username(username)
+    assert new_user is not None
+    assert new_user.firstname == u'spam ldap first name'
+    assert new_user.lastname == u'spam ldap last name'
+    assert new_user.email == 'spam ldap email'
+
+
+class _AuthLdapNoEmailMock():
+
+    def __init__(self, **kwargs):
+        pass
+
+    def authenticate_ldap(self, username, password):
+        return 'spam dn', dict(test_ldap_firstname=['spam ldap first name'],
+                               test_ldap_lastname=['spam ldap last name'],
+                               test_ldap_email=[''])
+
+
+def test_init_user_attributes_from_ldap_with_missing_email(monkeypatch,
+                                                           arrange_ldap_auth):
+    """Authenticate unknown user with mocked LDAP where email is missing.
+    """
+
+    # Arrange test user.
+    uniqifier = uuid.uuid4()
+    username = 'test-user-{0}'.format(uniqifier)
+    assert User.get_by_username(username) is None
+
+    # Arrange LDAP auth.
+    monkeypatch.setattr(auth_ldap, 'AuthLdap', _AuthLdapNoEmailMock)
+
+    # Authenticate with LDAP.
+    user_data = authenticate(username, 'password')
+
+    # Verify that authenication succeeded and retrieved correct attributes
+    # from LDAP, with empty email.
+    assert user_data is not None
+    assert user_data.get('firstname') == u'spam ldap first name'
+    assert user_data.get('lastname') == u'spam ldap last name'
+    assert user_data.get('email') == ''
+
+    # Verify that authentication created new user with attributes
+    # retrieved from LDAP, with email == None.
+    new_user = User.get_by_username(username)
+    assert new_user is not None
+    assert new_user.firstname == u'spam ldap first name'
+    assert new_user.lastname == u'spam ldap last name'
+    assert new_user.email is None
--- a/kallithea/tests/other/test_libs.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/other/test_libs.py	Sun Mar 31 21:28:56 2019 +0200
@@ -28,9 +28,10 @@
 import datetime
 import hashlib
 import mock
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.lib.utils2 import AttributeDict
 from kallithea.model.db import Repository
+from tg.util.webtest import test_context
 
 proto = 'http'
 TEST_URLS = [
@@ -67,19 +68,40 @@
 ]
 
 
-class TestLibs(BaseTestCase):
+class FakeUrlGenerator(object):
+
+    def __init__(self, current_url=None, default_route=None, **routes):
+        """Initialize using specified 'current' URL template,
+        default route template, and all other aguments describing known
+        routes (format: route=template)"""
+        self.current_url = current_url
+        self.default_route = default_route
+        self.routes = routes
 
-    @parameterized.expand(TEST_URLS)
+    def __call__(self, route_name, *args, **kwargs):
+        if route_name in self.routes:
+            return self.routes[route_name] % kwargs
+
+        return self.default_route % kwargs
+
+    def current(self, *args, **kwargs):
+        return self.current_url % kwargs
+
+
+class TestLibs(TestController):
+
+    @parametrize('test_url,expected,expected_creds', TEST_URLS)
     def test_uri_filter(self, test_url, expected, expected_creds):
         from kallithea.lib.utils2 import uri_filter
-        self.assertEqual(uri_filter(test_url), expected)
+        assert uri_filter(test_url) == expected
 
-    @parameterized.expand(TEST_URLS)
+    @parametrize('test_url,expected,expected_creds', TEST_URLS)
     def test_credentials_filter(self, test_url, expected, expected_creds):
         from kallithea.lib.utils2 import credentials_filter
-        self.assertEqual(credentials_filter(test_url), expected_creds)
+        assert credentials_filter(test_url) == expected_creds
 
-    @parameterized.expand([('t', True),
+    @parametrize('str_bool,expected', [
+                           ('t', True),
                            ('true', True),
                            ('y', True),
                            ('yes', True),
@@ -99,10 +121,10 @@
     ])
     def test_str2bool(self, str_bool, expected):
         from kallithea.lib.utils2 import str2bool
-        self.assertEqual(str2bool(str_bool), expected)
+        assert str2bool(str_bool) == expected
 
     def test_mention_extractor(self):
-        from kallithea.lib.utils2 import extract_mentioned_users
+        from kallithea.lib.utils2 import extract_mentioned_usernames
         sample = (
             "@first hi there @world here's my email username@example.com "
             "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
@@ -111,12 +133,12 @@
             "user.dot  hej ! not-needed maril@example.com"
         )
 
-        s = sorted([
+        expected = set([
             '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))
+            'marian.user', 'marco-polo', 'marco_polo', 'world'])
+        assert expected == set(extract_mentioned_usernames(sample))
 
-    @parameterized.expand([
+    @parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds= -1), u'1 second ago'),
         (dict(seconds= -60 * 2), u'2 minutes ago'),
@@ -135,11 +157,12 @@
     def test_age(self, age_args, expected):
         from kallithea.lib.utils2 import age
         from dateutil import relativedelta
-        n = datetime.datetime(year=2012, month=5, day=17)
-        delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
-        self.assertEqual(age(n + delt(**age_args), now=n), expected)
+        with test_context(self.app):
+            n = datetime.datetime(year=2012, month=5, day=17)
+            delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
+            assert age(n + delt(**age_args), now=n) == expected
 
-    @parameterized.expand([
+    @parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds= -1), u'1 second ago'),
         (dict(seconds= -60 * 2), u'2 minutes ago'),
@@ -159,11 +182,12 @@
     def test_age_short(self, age_args, expected):
         from kallithea.lib.utils2 import age
         from dateutil import relativedelta
-        n = datetime.datetime(year=2012, month=5, day=17)
-        delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
-        self.assertEqual(age(n + delt(**age_args), show_short_version=True, now=n), expected)
+        with test_context(self.app):
+            n = datetime.datetime(year=2012, month=5, day=17)
+            delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
+            assert age(n + delt(**age_args), show_short_version=True, now=n) == expected
 
-    @parameterized.expand([
+    @parametrize('age_args,expected', [
         (dict(), u'just now'),
         (dict(seconds=1), u'in 1 second'),
         (dict(seconds=60 * 2), u'in 2 minutes'),
@@ -177,11 +201,12 @@
     def test_age_in_future(self, age_args, expected):
         from kallithea.lib.utils2 import age
         from dateutil import relativedelta
-        n = datetime.datetime(year=2012, month=5, day=17)
-        delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
-        self.assertEqual(age(n + delt(**age_args), now=n), expected)
+        with test_context(self.app):
+            n = datetime.datetime(year=2012, month=5, day=17)
+            delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
+            assert age(n + delt(**age_args), now=n) == expected
 
-    def test_tag_exctrator(self):
+    def test_tag_extractor(self):
         sample = (
             "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
             "[requires] [stale] [see<>=>] [see => http://example.com]"
@@ -190,24 +215,18 @@
         )
         from kallithea.lib.helpers import urlify_text
         res = urlify_text(sample, stylize=True)
-        self.assertIn('<div class="metatag" tag="tag">tag</div>', res)
-        self.assertIn('<div class="metatag" tag="obsolete">obsolete</div>', res)
-        self.assertIn('<div class="metatag" tag="stale">stale</div>', res)
-        self.assertIn('<div class="metatag" tag="lang">python</div>', res)
-        self.assertIn('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>', res)
-        self.assertIn('<div class="metatag" tag="tag">tag</div>', res)
+        assert '<div class="label label-meta" data-tag="tag">tag</div>' in res
+        assert '<div class="label label-meta" data-tag="obsolete">obsolete</div>' in res
+        assert '<div class="label label-meta" data-tag="stale">stale</div>' in res
+        assert '<div class="label label-meta" data-tag="lang">python</div>' in res
+        assert '<div class="label label-meta" data-tag="requires">requires =&gt; <a href="/url">url</a></div>' in res
+        assert '<div class="label label-meta" data-tag="tag">tag</div>' in res
 
     def test_alternative_gravatar(self):
         from kallithea.lib.helpers import gravatar_url
         _md5 = lambda s: hashlib.md5(s).hexdigest()
 
-        #mock pylons.url
-        class fake_url(object):
-            @classmethod
-            def current(cls, *args, **kwargs):
-                return 'https://example.com'
-
-        #mock pylons.tmpl_context
+        # mock tg.tmpl_context
         def fake_tmpl_context(_url):
             _c = AttributeDict()
             _c.visual = AttributeDict()
@@ -216,39 +235,39 @@
 
             return _c
 
-
-        with mock.patch('pylons.url', fake_url):
+        fake_url = FakeUrlGenerator(current_url='https://example.com')
+        with mock.patch('kallithea.config.routing.url', fake_url):
             fake = fake_tmpl_context(_url='http://example.com/{email}')
-            with mock.patch('pylons.tmpl_context', fake):
-                    from pylons import url
+            with mock.patch('tg.tmpl_context', fake):
+                    from kallithea.config.routing import url
                     assert url.current() == 'https://example.com'
                     grav = gravatar_url(email_address='test@example.com', size=24)
                     assert grav == 'http://example.com/test@example.com'
 
             fake = fake_tmpl_context(_url='http://example.com/{email}')
-            with mock.patch('pylons.tmpl_context', fake):
+            with mock.patch('tg.tmpl_context', fake):
                 grav = gravatar_url(email_address='test@example.com', size=24)
                 assert grav == 'http://example.com/test@example.com'
 
             fake = fake_tmpl_context(_url='http://example.com/{md5email}')
-            with mock.patch('pylons.tmpl_context', fake):
+            with mock.patch('tg.tmpl_context', fake):
                 em = 'test@example.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'http://example.com/%s' % (_md5(em))
 
             fake = fake_tmpl_context(_url='http://example.com/{md5email}/{size}')
-            with mock.patch('pylons.tmpl_context', fake):
+            with mock.patch('tg.tmpl_context', fake):
                 em = 'test@example.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'http://example.com/%s/%s' % (_md5(em), 24)
 
             fake = fake_tmpl_context(_url='{scheme}://{netloc}/{md5email}/{size}')
-            with mock.patch('pylons.tmpl_context', fake):
+            with mock.patch('tg.tmpl_context', fake):
                 em = 'test@example.com'
                 grav = gravatar_url(email_address=em, size=24)
                 assert grav == 'https://example.com/%s/%s' % (_md5(em), 24)
 
-    @parameterized.expand([
+    @parametrize('tmpl,repo_name,overrides,prefix,expected', [
         (Repository.DEFAULT_CLONE_URI, 'group/repo1', {}, '', 'http://vps1:8000/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'),
@@ -268,9 +287,9 @@
         from kallithea.lib.utils2 import get_clone_url
         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)
+        assert clone_url == expected
 
-    def _quick_url(self, text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
+    def _quick_url(self, text, tmpl="""<a class="changeset_hash" href="%s">%s</a>""", url_=None):
         """
         Changes `some text url[foo]` => `some text <a href="/">foo</a>
 
@@ -278,79 +297,240 @@
         """
         import re
         # quickly change expected url[] into a link
-        URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
+        url_pattern = re.compile(r'(?:url\[)(.+?)(?:\])')
 
         def url_func(match_obj):
             _url = match_obj.groups()[0]
-            return tmpl % (url_ or '/some-url', _url)
-        return URL_PAT.sub(url_func, text)
+            return tmpl % (url_ or '/repo_name/changeset/%s' % _url, _url)
+        return url_pattern.sub(url_func, text)
 
-    @parameterized.expand([
+    @parametrize('sample,expected', [
       ("",
        ""),
       ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
-       "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
+       """git-svn-id: <a href="https://svn.apache.org/repos/asf/libcloud/trunk@1441655">https://svn.apache.org/repos/asf/libcloud/trunk@1441655</a> 13f79535-47bb-0310-9956-ffa450edef68"""),
       ("from rev 000000000000",
-       "from rev url[000000000000]"),
+       """from rev url[000000000000]"""),
       ("from rev 000000000000123123 also rev 000000000000",
-       "from rev url[000000000000123123] also rev url[000000000000]"),
+       """from rev url[000000000000123123] also rev url[000000000000]"""),
       ("this should-000 00",
-       "this should-000 00"),
+       """this should-000 00"""),
       ("longtextffffffffff rev 123123123123",
-       "longtextffffffffff rev url[123123123123]"),
+       """longtextffffffffff rev url[123123123123]"""),
       ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
-       "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
+       """rev ffffffffffffffffffffffffffffffffffffffffffffffffff"""),
       ("ffffffffffff some text traalaa",
-       "url[ffffffffffff] some text traalaa"),
+       """url[ffffffffffff] some text traalaa"""),
        ("""Multi line
        123123123123
        some text 123123123123
        sometimes !
        """,
-       """Multi line
-       url[123123123123]
-       some text url[123123123123]
-       sometimes !
-       """)
+       """Multi line<br/>"""
+       """       url[123123123123]<br/>"""
+       """       some text url[123123123123]<br/>"""
+       """       sometimes !"""),
     ])
-    def test_urlify_changesets(self, sample, expected):
-        def fake_url(self, *args, **kwargs):
-            return '/some-url'
-
+    def test_urlify_text(self, sample, expected):
         expected = self._quick_url(expected)
+        fake_url = FakeUrlGenerator(changeset_home='/%(repo_name)s/changeset/%(revision)s')
+        with mock.patch('kallithea.config.routing.url', fake_url):
+            from kallithea.lib.helpers import urlify_text
+            assert urlify_text(sample, 'repo_name') == expected
 
-        with mock.patch('pylons.url', fake_url):
-            from kallithea.lib.helpers import urlify_changesets
-            self.assertEqual(urlify_changesets(sample, 'repo_name'), expected)
-
-    @parameterized.expand([
+    @parametrize('sample,expected,url_', [
       ("",
        "",
        ""),
       ("https://svn.apache.org/repos",
-       "url[https://svn.apache.org/repos]",
+       """url[https://svn.apache.org/repos]""",
        "https://svn.apache.org/repos"),
       ("http://svn.apache.org/repos",
-       "url[http://svn.apache.org/repos]",
+       """url[http://svn.apache.org/repos]""",
        "http://svn.apache.org/repos"),
       ("from rev a also rev http://google.com",
-       "from rev a also rev url[http://google.com]",
+       """from rev a also rev url[http://google.com]""",
        "http://google.com"),
-       ("""Multi line
+      ("http://imgur.com/foo.gif inline http://imgur.com/foo.gif ending http://imgur.com/foo.gif",
+       """url[http://imgur.com/foo.gif] inline url[http://imgur.com/foo.gif] ending url[http://imgur.com/foo.gif]""",
+       "http://imgur.com/foo.gif"),
+      ("""Multi line
        https://foo.bar.example.com
        some text lalala""",
-       """Multi line
-       url[https://foo.bar.example.com]
-       some text lalala""",
-       "https://foo.bar.example.com")
+       """Multi line<br/>"""
+       """       url[https://foo.bar.example.com]<br/>"""
+       """       some text lalala""",
+       "https://foo.bar.example.com"),
+      ("@mention @someone",
+       """<b>@mention</b> <b>@someone</b>""",
+       ""),
+      ("deadbeefcafe 123412341234",
+       """<a class="changeset_hash" href="/repo_name/changeset/deadbeefcafe">deadbeefcafe</a> <a class="changeset_hash" href="/repo_name/changeset/123412341234">123412341234</a>""",
+       ""),
+      ("We support * markup for *bold* markup of *single or multiple* words, "
+       "*a bit @like http://slack.com*. "
+       "The first * must come after whitespace and not be followed by whitespace, "
+       "contain anything but * and newline until the next *, "
+       "which must not come after whitespace "
+       "and not be followed by * or alphanumerical *characters*.",
+       """We support * markup for <b>*bold*</b> markup of <b>*single or multiple*</b> words, """
+       """<b>*a bit <b>@like</b> <a href="http://slack.com">http://slack.com</a>*</b>. """
+       """The first * must come after whitespace and not be followed by whitespace, """
+       """contain anything but * and newline until the next *, """
+       """which must not come after whitespace """
+       """and not be followed by * or alphanumerical <b>*characters*</b>.""",
+       "-"),
+      ("HTML escaping: <abc> 'single' \"double\" &pointer",
+       "HTML escaping: &lt;abc&gt; &#39;single&#39; &quot;double&quot; &amp;pointer",
+       "-"),
+      # tags are covered by test_tag_extractor
     ])
     def test_urlify_test(self, sample, expected, url_):
-        from kallithea.lib.helpers import urlify_text
         expected = self._quick_url(expected,
                                    tmpl="""<a href="%s">%s</a>""", url_=url_)
-        self.assertEqual(urlify_text(sample), expected)
+        fake_url = FakeUrlGenerator(changeset_home='/%(repo_name)s/changeset/%(revision)s')
+        with mock.patch('kallithea.config.routing.url', fake_url):
+            from kallithea.lib.helpers import urlify_text
+            assert urlify_text(sample, 'repo_name', stylize=True) == expected
+
+    @parametrize('sample,expected', [
+      ("deadbeefcafe @mention, and http://foo.bar/ yo",
+       """<a class="changeset_hash" href="/repo_name/changeset/deadbeefcafe">deadbeefcafe</a>"""
+       """<a class="message-link" href="#the-link"> <b>@mention</b>, and </a>"""
+       """<a href="http://foo.bar/">http://foo.bar/</a>"""
+       """<a class="message-link" href="#the-link"> yo</a>"""),
+    ])
+    def test_urlify_link(self, sample, expected):
+        fake_url = FakeUrlGenerator(changeset_home='/%(repo_name)s/changeset/%(revision)s')
+        with mock.patch('kallithea.config.routing.url', fake_url):
+            from kallithea.lib.helpers import urlify_text
+            assert urlify_text(sample, 'repo_name', link_='#the-link') == expected
 
-    @parameterized.expand([
+    @parametrize('issue_pat,issue_server,issue_sub,sample,expected', [
+        (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'issue #123 and issue#456',
+            """issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
+            """issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/456">#456</a>"""),
+        (r'(?:\s*#)(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'issue #123 and issue#456',
+            """issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
+            """issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/456">#456</a>"""),
+        # to require whitespace before the issue reference, one may be tempted to use \b...
+        (r'\bPR(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'issue PR123 and issuePR456',
+            """issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
+            """issuePR456"""),
+        # ... but it turns out that \b does not work well in combination with '#': the expectations
+        # are reversed from what is actually happening.
+        (r'\b#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'issue #123 and issue#456',
+            """issue #123 and """
+            """issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/456">#456</a>"""),
+        # ... so maybe try to be explicit? Unfortunately the whitespace before the issue
+        # reference is not retained, again, because it is part of the pattern.
+        (r'(?:^|\s)#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            '#15 and issue #123 and issue#456',
+            """<a class="issue-tracker-link" href="http://foo/repo_name/issue/15">#15</a> and """
+            """issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
+            """issue#456"""),
+        # ... instead, use lookbehind assertions.
+        (r'(?:^|(?<=\s))#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            '#15 and issue #123 and issue#456',
+            """<a class="issue-tracker-link" href="http://foo/repo_name/issue/15">#15</a> and """
+            """issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a> and """
+            """issue#456"""),
+        (r'(?:pullrequest|pull request|PR|pr) ?#?(\d+)', 'http://foo/{repo}/issue/\\1', 'PR#\\1',
+            'fixed with pullrequest #1, pull request#2, PR 3, pr4',
+            """fixed with <a class="issue-tracker-link" href="http://foo/repo_name/issue/1">PR#1</a>, """
+            """<a class="issue-tracker-link" href="http://foo/repo_name/issue/2">PR#2</a>, """
+            """<a class="issue-tracker-link" href="http://foo/repo_name/issue/3">PR#3</a>, """
+            """<a class="issue-tracker-link" href="http://foo/repo_name/issue/4">PR#4</a>"""),
+        (r'#(\d+)', 'http://foo/{repo}/issue/\\1', 'PR\\1',
+            'interesting issue #123',
+            """interesting issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">PR123</a>"""),
+        (r'BUG\d{5}', 'https://bar/{repo}/\\1', '\\1',
+            'silly me, I did not parenthesize the id, BUG12345.',
+            """silly me, I did not parenthesize the id, <a class="issue-tracker-link" href="https://bar/repo_name/\\1">BUG12345</a>."""),
+        (r'BUG(\d{5})', 'https://bar/{repo}/', 'BUG\\1',
+            'silly me, the URL does not contain id, BUG12345.',
+            """silly me, the URL does not contain id, <a class="issue-tracker-link" href="https://bar/repo_name/">BUG12345</a>."""),
+        (r'(PR-\d+)', 'http://foo/{repo}/issue/\\1', '',
+            'interesting issue #123, err PR-56',
+            """interesting issue #123, err <a class="issue-tracker-link" href="http://foo/repo_name/issue/PR-56">PR-56</a>"""),
+        (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            "some 'standard' text with apostrophes",
+            """some &#39;standard&#39; text with apostrophes"""),
+        (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            "some 'standard' issue #123",
+            """some &#39;standard&#39; issue <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>"""),
+        (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'an issue   #123       with extra whitespace',
+            """an issue   <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>       with extra whitespace"""),
+        (r'(?:\s*#)(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
+            'an issue   #123       with extra whitespace',
+            """an issue<a class="issue-tracker-link" href="http://foo/repo_name/issue/123">#123</a>       with extra whitespace"""),
+        # invalid issue pattern
+        (r'(PR\d+', 'http://foo/{repo}/issue/{id}', '',
+            'PR135',
+            """PR135"""),
+        # other character than #
+        (r'(?:^|(?<=\s))\$(\d+)', 'http://foo/{repo}/issue/\\1', '',
+            'empty issue_sub $123 and issue$456',
+            """empty issue_sub <a class="issue-tracker-link" href="http://foo/repo_name/issue/123">$123</a> and """
+            """issue$456"""),
+        # named groups
+        (r'(PR|pullrequest|pull request) ?(?P<sitecode>BRU|CPH|BER)-(?P<id>\d+)', 'http://foo/\g<sitecode>/pullrequest/\g<id>/', 'PR-\g<sitecode>-\g<id>',
+            'pullrequest CPH-789 is similar to PRBRU-747',
+            """<a class="issue-tracker-link" href="http://foo/CPH/pullrequest/789/">PR-CPH-789</a> is similar to """
+            """<a class="issue-tracker-link" href="http://foo/BRU/pullrequest/747/">PR-BRU-747</a>"""),
+    ])
+    def test_urlify_issues(self, issue_pat, issue_server, issue_sub, sample, expected):
+        from kallithea.lib.helpers import urlify_text
+        config_stub = {
+            'sqlalchemy.url': 'foo',
+            'issue_pat': issue_pat,
+            'issue_server_link': issue_server,
+            'issue_sub': issue_sub,
+        }
+        # force recreation of lazy function
+        with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
+            with mock.patch('kallithea.CONFIG', config_stub):
+                assert urlify_text(sample, 'repo_name') == expected
+
+    @parametrize('sample,expected', [
+        ('abc X5', 'abc <a class="issue-tracker-link" href="http://main/repo_name/main/5/">#5</a>'),
+        ('abc pullrequest #6 xyz', 'abc <a class="issue-tracker-link" href="http://pr/repo_name/pr/6">PR#6</a> xyz'),
+        ('pull request7 #', '<a class="issue-tracker-link" href="http://pr/repo_name/pr/7">PR#7</a> #'),
+        ('look PR9 and pr #11', 'look <a class="issue-tracker-link" href="http://pr/repo_name/pr/9">PR#9</a> and <a class="issue-tracker-link" href="http://pr/repo_name/pr/11">PR#11</a>'),
+        ('pullrequest#10 solves issue 9', '<a class="issue-tracker-link" href="http://pr/repo_name/pr/10">PR#10</a> solves <a class="issue-tracker-link" href="http://bug/repo_name/bug/9">bug#9</a>'),
+        ('issue FAIL67', 'issue <a class="issue-tracker-link" href="http://fail/repo_name/67">FAIL67</a>'),
+        ('issue FAILMORE89', 'issue FAILMORE89'), # no match because absent prefix
+    ])
+    def test_urlify_issues_multiple_issue_patterns(self, sample, expected):
+        from kallithea.lib.helpers import urlify_text
+        config_stub = {
+            'sqlalchemy.url': 'foo',
+            'issue_pat': 'X(\d+)',
+            'issue_server_link': 'http://main/{repo}/main/\\1/',
+            'issue_sub': '#\\1',
+            'issue_pat_pr': '(?:pullrequest|pull request|PR|pr) ?#?(\d+)',
+            'issue_server_link_pr': 'http://pr/{repo}/pr/\\1',
+            'issue_sub_pr': 'PR#\\1',
+            'issue_pat_bug': '(?:BUG|bug|issue) ?#?(\d+)',
+            'issue_server_link_bug': 'http://bug/{repo}/bug/\\1',
+            'issue_sub_bug': 'bug#\\1',
+            'issue_pat_empty_prefix': 'FAIL(\d+)',
+            'issue_server_link_empty_prefix': 'http://fail/{repo}/\\1',
+            'issue_sub_empty_prefix': '',
+            'issue_pat_absent_prefix': 'FAILMORE(\d+)',
+            'issue_server_link_absent_prefix': 'http://failmore/{repo}/\\1',
+        }
+        # force recreation of lazy function
+        with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
+            with mock.patch('kallithea.CONFIG', config_stub):
+                assert urlify_text(sample, 'repo_name') == expected
+
+    @parametrize('test,expected', [
       ("", None),
       ("/_2", '2'),
       ("_2", '2'),
@@ -370,5 +550,56 @@
     def test_get_repo_by_id(self, test, expected):
         from kallithea.lib.utils import _extract_id_from_repo_name
         _test = _extract_id_from_repo_name(test)
-        self.assertEqual(_test, expected, msg='url:%s, got:`%s` expected: `%s`'
-                                              % (test, _test, expected))
+        assert _test == expected, 'url:%s, got:`%s` expected: `%s`' % (test, _test, expected)
+
+
+    @parametrize('canonical,test,expected', [
+        ('http://www.example.org/', '/abc/xyz', 'http://www.example.org/abc/xyz'),
+        ('http://www.example.org', '/abc/xyz', 'http://www.example.org/abc/xyz'),
+        ('http://www.example.org', '/abc/xyz/', 'http://www.example.org/abc/xyz/'),
+        ('http://www.example.org', 'abc/xyz/', 'http://www.example.org/abc/xyz/'),
+        ('http://www.example.org', 'about', 'http://www.example.org/about-page'),
+        ('http://www.example.org/repos/', 'abc/xyz/', 'http://www.example.org/repos/abc/xyz/'),
+        ('http://www.example.org/kallithea/repos/', 'abc/xyz/', 'http://www.example.org/kallithea/repos/abc/xyz/'),
+    ])
+    def test_canonical_url(self, canonical, test, expected):
+        from kallithea.lib.helpers import canonical_url
+        from tg import request
+
+        # setup url(), used by canonical_url
+        import routes
+        m = routes.Mapper()
+        m.connect('about', '/about-page')
+        url = routes.URLGenerator(m, {'HTTP_HOST': 'http_host.example.org'})
+
+        config_mock = {
+            'canonical_url': canonical,
+        }
+
+        with test_context(self.app):
+            request.environ['routes.url'] = url
+            with mock.patch('kallithea.CONFIG', config_mock):
+                assert canonical_url(test) == expected
+
+    @parametrize('canonical,expected', [
+        ('http://www.example.org', 'www.example.org'),
+        ('http://www.example.org/repos/', 'www.example.org'),
+        ('http://www.example.org/kallithea/repos/', 'www.example.org'),
+    ])
+    def test_canonical_hostname(self, canonical, expected):
+        from kallithea.lib.helpers import canonical_hostname
+        from tg import request
+
+        # setup url(), used by canonical_hostname
+        import routes
+        m = routes.Mapper()
+        url = routes.URLGenerator(m, {'HTTP_HOST': 'http_host.example.org'})
+
+        config_mock = {
+            'canonical_url': canonical,
+        }
+
+        with test_context(self.app):
+            request.environ['routes.url'] = url
+            with mock.patch('kallithea.CONFIG', config_mock):
+                assert canonical_hostname() == expected
--- a/kallithea/tests/other/test_mail.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/other/test_mail.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,10 @@
 import mock
 
 import kallithea
-from kallithea.tests import *
+from kallithea.tests.base import *
 from kallithea.model.db import User
 
+
 class smtplib_mock(object):
 
     @classmethod
@@ -12,16 +13,19 @@
 
     def ehlo(self):
         pass
+
     def quit(self):
         pass
+
     def sendmail(self, sender, dest, msg):
         smtplib_mock.lastsender = sender
         smtplib_mock.lastdest = dest
         smtplib_mock.lastmsg = msg
         pass
 
+
 @mock.patch('kallithea.lib.rcmail.smtp_mailer.smtplib', smtplib_mock)
-class TestMail(BaseTestCase):
+class TestMail(TestController):
 
     def test_send_mail_trivial(self):
         mailserver = 'smtp.mailserver.org'
@@ -38,12 +42,12 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set(recipients))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn('From: %s' % envelope_from, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set(recipients)
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
 
     def test_send_mail_no_recipients(self):
         mailserver = 'smtp.mailserver.org'
@@ -62,12 +66,36 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set([TEST_USER_ADMIN_EMAIL, email_to]))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn('From: %s' % envelope_from, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL, email_to])
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
+
+    def test_send_mail_no_recipients_multiple_email_to(self):
+        mailserver = 'smtp.mailserver.org'
+        recipients = []
+        envelope_from = 'noreply@mailserver.org'
+        email_to = 'admin@mailserver.org,admin2@example.com'
+        subject = 'subject'
+        body = 'body'
+        html_body = 'html_body'
+
+        config_mock = {
+            'smtp_server': mailserver,
+            'app_email_from': envelope_from,
+            'email_to': email_to,
+        }
+        with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
+            kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
+
+        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL] + email_to.split(','))
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
 
     def test_send_mail_no_recipients_no_email_to(self):
         mailserver = 'smtp.mailserver.org'
@@ -84,12 +112,12 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set([TEST_USER_ADMIN_EMAIL]))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn('From: %s' % envelope_from, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set([TEST_USER_ADMIN_EMAIL])
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: %s' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
 
     def test_send_mail_with_author(self):
         mailserver = 'smtp.mailserver.org'
@@ -107,12 +135,12 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set(recipients))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn('From: "Kallithea Admin (no-reply)" <%s>' % envelope_from, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set(recipients)
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: "Kallithea Admin (no-reply)" <%s>' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
 
     def test_send_mail_with_author_full_mail_from(self):
         mailserver = 'smtp.mailserver.org'
@@ -131,12 +159,12 @@
         with mock.patch('kallithea.lib.celerylib.tasks.config', config_mock):
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body, author=author)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set(recipients))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn('From: "Kallithea Admin (no-reply)" <%s>' % envelope_addr, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set(recipients)
+        assert smtplib_mock.lastsender == envelope_from
+        assert 'From: "Kallithea Admin (no-reply)" <%s>' % envelope_addr in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
 
     def test_send_mail_extra_headers(self):
         mailserver = 'smtp.mailserver.org'
@@ -145,7 +173,7 @@
         subject = 'subject'
         body = 'body'
         html_body = 'html_body'
-        author = User(name='foo', lastname='(fubar) "baz"')
+        author = User(name='foo', lastname=u'(fubar) "baz"')
         headers = {'extra': 'yes'}
 
         config_mock = {
@@ -156,12 +184,12 @@
             kallithea.lib.celerylib.tasks.send_email(recipients, subject, body, html_body,
                                                      author=author, headers=headers)
 
-        self.assertSetEqual(smtplib_mock.lastdest, set(recipients))
-        self.assertEqual(smtplib_mock.lastsender, envelope_from)
-        self.assertIn(r'From: "foo (fubar) \"baz\" (no-reply)" <%s>' % envelope_from, smtplib_mock.lastmsg)
-        self.assertIn('Subject: %s' % subject, smtplib_mock.lastmsg)
-        self.assertIn(body, smtplib_mock.lastmsg)
-        self.assertIn(html_body, smtplib_mock.lastmsg)
-        self.assertIn('Extra: yes', smtplib_mock.lastmsg)
+        assert smtplib_mock.lastdest == set(recipients)
+        assert smtplib_mock.lastsender == envelope_from
+        assert r'From: "foo (fubar) \"baz\" (no-reply)" <%s>' % envelope_from in smtplib_mock.lastmsg
+        assert 'Subject: %s' % subject in smtplib_mock.lastmsg
+        assert body in smtplib_mock.lastmsg
+        assert html_body in smtplib_mock.lastmsg
+        assert 'Extra: yes' in smtplib_mock.lastmsg
         # verify that headers dict hasn't mutated by send_email
-        self.assertDictEqual(headers, {'extra': 'yes'})
+        assert headers == {'extra': 'yes'}
--- a/kallithea/tests/other/test_validators.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/other/test_validators.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,7 +1,8 @@
 # -*- coding: utf-8 -*-
 import formencode
+import pytest
 
-from kallithea.tests import *
+from kallithea.tests.base import *
 
 from kallithea.model import validators as v
 from kallithea.model.user_group import UserGroupModel
@@ -13,59 +14,67 @@
 fixture = Fixture()
 
 
-class TestRepoGroups(BaseTestCase):
+@pytest.mark.usefixtures("test_context_fixture") # apply fixture for all test methods
+class TestRepoGroups(TestController):
 
-    def setUp(self):
-        pass
-
-    def tearDown(self):
+    def teardown_method(self, method):
         Session.remove()
 
     def test_Message_extractor(self):
         validator = v.ValidUsername()
-        self.assertRaises(formencode.Invalid, validator.to_python, 'default')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('default')
 
         class StateObj(object):
             pass
 
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, 'default', StateObj)
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('default', StateObj)
 
     def test_ValidUsername(self):
         validator = v.ValidUsername()
 
-        self.assertRaises(formencode.Invalid, validator.to_python, 'default')
-        self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
-        self.assertRaises(formencode.Invalid, validator.to_python, '.,')
-        self.assertRaises(formencode.Invalid, validator.to_python,
-                          TEST_USER_ADMIN_LOGIN)
-        self.assertEqual('test', validator.to_python('test'))
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('default')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('new_user')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('.,')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(TEST_USER_ADMIN_LOGIN)
+        assert 'test' == validator.to_python('test')
 
         validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
 
     def test_ValidRepoUser(self):
         validator = v.ValidRepoUser()
-        self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
-        self.assertEqual(TEST_USER_ADMIN_LOGIN,
-                         validator.to_python(TEST_USER_ADMIN_LOGIN))
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('nouser')
+        assert TEST_USER_ADMIN_LOGIN == validator.to_python(TEST_USER_ADMIN_LOGIN)
 
     def test_ValidUserGroup(self):
         validator = v.ValidUserGroup()
-        self.assertRaises(formencode.Invalid, validator.to_python, 'default')
-        self.assertRaises(formencode.Invalid, validator.to_python, '.,')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'default')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'.,')
 
-        gr = fixture.create_user_group('test')
-        gr2 = fixture.create_user_group('tes2')
+        gr = fixture.create_user_group(u'test')
+        gr2 = fixture.create_user_group(u'tes2')
         Session().commit()
-        self.assertRaises(formencode.Invalid, validator.to_python, 'test')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'test')
         assert gr.users_group_id is not None
         validator = v.ValidUserGroup(edit=True,
                                     old_data={'users_group_id':
                                               gr2.users_group_id})
 
-        self.assertRaises(formencode.Invalid, validator.to_python, 'test')
-        self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
-        self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'test')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'TesT')
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(u'TEST')
         UserGroupModel().delete(gr)
         UserGroupModel().delete(gr2)
         Session().commit()
@@ -73,49 +82,48 @@
     def test_ValidRepoGroup(self):
         validator = v.ValidRepoGroup()
         model = RepoGroupModel()
-        self.assertRaises(formencode.Invalid, validator.to_python,
-                          {'group_name': HG_REPO, })
-        gr = model.create(group_name='test_gr', group_description='desc',
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'group_name': HG_REPO, })
+        gr = model.create(group_name=u'test_gr', group_description=u'desc',
                           parent=None,
                           just_db=True,
                           owner=TEST_USER_ADMIN_LOGIN)
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, {'group_name': gr.group_name, })
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'group_name': gr.group_name, })
 
         validator = v.ValidRepoGroup(edit=True,
                                       old_data={'group_id':  gr.group_id})
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, {
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({
                                         'group_name': gr.group_name + 'n',
-                                        'group_parent_id': gr.group_id
+                                        'parent_group_id': gr.group_id
                                         })
         model.delete(gr)
 
     def test_ValidPassword(self):
         validator = v.ValidPassword()
-        self.assertEqual('lol', validator.to_python('lol'))
-        self.assertEqual(None, validator.to_python(None))
-        self.assertRaises(formencode.Invalid, validator.to_python, 'ąćżź')
+        assert 'lol' == validator.to_python('lol')
+        assert validator.to_python(None) is None
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('ąćżź')
 
     def test_ValidPasswordsMatch(self):
         validator = v.ValidPasswordsMatch('new_password', 'password_confirmation')
-        self.assertRaises(formencode.Invalid,
-                    validator.to_python, {'new_password': 'pass',
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'new_password': 'pass',
                                           'password_confirmation': 'pass2'})
 
-        self.assertRaises(formencode.Invalid,
-                    validator.to_python, {'new_password': 'pass',
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'new_password': 'pass',
                                           'password_confirmation': 'pass2'})
 
-        self.assertEqual({'new_password': 'pass',
-                          'password_confirmation': 'pass'},
-                    validator.to_python({'new_password': 'pass',
-                                         'password_confirmation': 'pass'}))
+        assert {'new_password': 'pass',
+                          'password_confirmation': 'pass'} == validator.to_python({'new_password': 'pass',
+                                         'password_confirmation': 'pass'})
 
-        self.assertEqual({'new_password': 'pass',
-                          'password_confirmation': 'pass'},
-                    validator.to_python({'new_password': 'pass',
-                                         'password_confirmation': 'pass'}))
+        assert {'new_password': 'pass',
+                          'password_confirmation': 'pass'} == validator.to_python({'new_password': 'pass',
+                                         'password_confirmation': 'pass'})
 
     def test_ValidAuth(self):
         validator = v.ValidAuth()
@@ -127,9 +135,9 @@
             'username': 'err',
             'password': 'err',
         }
-        self.assertEqual(valid_creds, validator.to_python(valid_creds))
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, invalid_creds)
+        assert valid_creds == validator.to_python(valid_creds)
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(invalid_creds)
 
     def test_ValidAuthToken(self):
         validator = v.ValidAuthToken()
@@ -141,20 +149,20 @@
     def test_ValidRepoName(self):
         validator = v.ValidRepoName()
 
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, {'repo_name': ''})
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'repo_name': ''})
 
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, {'repo_name': HG_REPO})
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'repo_name': HG_REPO})
 
-        gr = RepoGroupModel().create(group_name='group_test',
-                                      group_description='desc',
+        gr = RepoGroupModel().create(group_name=u'group_test',
+                                      group_description=u'desc',
                                       parent=None,
                                       owner=TEST_USER_ADMIN_LOGIN)
-        self.assertRaises(formencode.Invalid,
-                          validator.to_python, {'repo_name': gr.group_name})
+        with pytest.raises(formencode.Invalid):
+            validator.to_python({'repo_name': gr.group_name})
 
-        #TODO: write an error case for that ie. create a repo withinh a group
+        # TODO: write an error case for that ie. create a repo withinh a group
 #        self.assertRaises(formencode.Invalid,
 #                          validator.to_python, {'repo_name': 'some',
 #                                                'repo_group': gr.group_id})
@@ -163,69 +171,69 @@
         # this uses ValidRepoName validator
         assert True
 
-    @parameterized.expand([
+    @parametrize('name,expected', [
         ('test', 'test'), ('lolz!', 'lolz'), ('  aavv', 'aavv'),
         ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
         ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
         ('/]re po', 're-po')])
     def test_SlugifyName(self, name, expected):
         validator = v.SlugifyName()
-        self.assertEqual(expected, validator.to_python(name))
+        assert expected == validator.to_python(name)
 
     def test_ValidCloneUri(self):
-            #TODO: write this one
+            # TODO: write this one
             pass
 
     def test_ValidForkType(self):
             validator = v.ValidForkType(old_data={'repo_type': 'hg'})
-            self.assertEqual('hg', validator.to_python('hg'))
-            self.assertRaises(formencode.Invalid, validator.to_python, 'git')
+            assert 'hg' == validator.to_python('hg')
+            with pytest.raises(formencode.Invalid):
+                validator.to_python('git')
 
     def test_ValidPerms(self):
-            #TODO: write this one
+            # TODO: write this one
             pass
 
     def test_ValidSettings(self):
         validator = v.ValidSettings()
-        self.assertEqual({'pass': 'pass'},
-                         validator.to_python(value={'user': 'test',
-                                                    'pass': 'pass'}))
+        assert {'pass': 'pass'} == validator.to_python(value={'user': 'test',
+                                                    'pass': 'pass'})
 
-        self.assertEqual({'user2': 'test', 'pass': 'pass'},
-                         validator.to_python(value={'user2': 'test',
-                                                    'pass': 'pass'}))
+        assert {'user2': 'test', 'pass': 'pass'} == validator.to_python(value={'user2': 'test',
+                                                    'pass': 'pass'})
 
     def test_ValidPath(self):
             validator = v.ValidPath()
-            self.assertEqual(TESTS_TMP_PATH,
-                             validator.to_python(TESTS_TMP_PATH))
-            self.assertRaises(formencode.Invalid, validator.to_python,
-                              '/no_such_dir')
+            assert TESTS_TMP_PATH == validator.to_python(TESTS_TMP_PATH)
+            with pytest.raises(formencode.Invalid):
+                validator.to_python('/no_such_dir')
 
     def test_UniqSystemEmail(self):
         validator = v.UniqSystemEmail(old_data={})
 
-        self.assertEqual('mail@python.org',
-                         validator.to_python('MaiL@Python.org'))
+        assert 'mail@python.org' == validator.to_python('MaiL@Python.org')
 
         email = TEST_USER_REGULAR2_EMAIL
-        self.assertRaises(formencode.Invalid, validator.to_python, email)
+        with pytest.raises(formencode.Invalid):
+            validator.to_python(email)
 
     def test_ValidSystemEmail(self):
         validator = v.ValidSystemEmail()
         email = TEST_USER_REGULAR2_EMAIL
 
-        self.assertEqual(email, validator.to_python(email))
-        self.assertRaises(formencode.Invalid, validator.to_python, 'err')
+        assert email == validator.to_python(email)
+        with pytest.raises(formencode.Invalid):
+            validator.to_python('err')
 
     def test_LdapLibValidator(self):
         if ldap_lib_installed:
             validator = v.LdapLibValidator()
-            self.assertEqual("DN", validator.to_python('DN'))
+            assert "DN" == validator.to_python('DN')
         else:
             validator = v.LdapLibValidator()
-            self.assertRaises(v.LdapImportError, validator.to_python, 'err')
+            with pytest.raises(v.LdapImportError):
+                validator.to_python('err')
 
     def test_AttrLoginValidator(self):
         validator = v.AttrLoginValidator()
-        self.assertEqual('DN_attr', validator.to_python('DN_attr'))
+        assert 'DN_attr' == validator.to_python('DN_attr')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/other/test_vcs_operations.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,737 @@
+# -*- 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/>.
+"""
+Test suite for vcs push/pull operations.
+
+The tests need Git > 1.8.1.
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Dec 30, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+import os
+import re
+import tempfile
+import time
+import urllib2
+import json
+from tempfile import _RandomNameSequence
+from subprocess import Popen, PIPE
+
+import pytest
+
+from kallithea.tests.base import *
+from kallithea.tests.fixture import Fixture
+from kallithea.model.db import User, Repository, UserIpMap, CacheInvalidation, Ui, UserLog
+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:4999'  # test host
+
+fixture = Fixture()
+
+
+class Command(object):
+
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def execute(self, cmd, *args, **environ):
+        """
+        Runs command on the system with given ``args``.
+        """
+
+        command = cmd + ' ' + ' '.join(args)
+        ignoreReturnCode = environ.pop('ignoreReturnCode', False)
+        if DEBUG:
+            print '*** CMD %s ***' % command
+        testenv = dict(os.environ)
+        testenv['LANG'] = 'en_US.UTF-8'
+        testenv['LANGUAGE'] = 'en_US:en'
+        testenv['HGPLAIN'] = ''
+        testenv['HGRCPATH'] = ''
+        testenv.update(environ)
+        p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd, env=testenv)
+        stdout, stderr = p.communicate()
+        if DEBUG:
+            if stdout:
+                print 'stdout:', repr(stdout)
+            if stderr:
+                print 'stderr:', repr(stderr)
+        if not ignoreReturnCode:
+            assert p.returncode == 0
+        return stdout, stderr
+
+
+def _get_tmp_dir(prefix='vcs_operations-', suffix=''):
+    return tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix=prefix, suffix=suffix)
+
+
+def _add_files(vcs, dest_dir, files_no=3):
+    """
+    Generate some files, add it to dest_dir repo and push back
+    vcs is git or hg and defines what VCS we want to make those files for
+
+    :param vcs:
+    :param dest_dir:
+    """
+    added_file = '%ssetup.py' % _RandomNameSequence().next()
+    open(os.path.join(dest_dir, added_file), 'a').close()
+    Command(dest_dir).execute('%s add %s' % (vcs, added_file))
+
+    email = 'me@example.com'
+    if os.name == 'nt':
+        author_str = 'User <%s>' % email
+    else:
+        author_str = 'User ǝɯɐᴎ <%s>' % email
+    for i in xrange(files_no):
+        cmd = """echo "added_line%s" >> %s""" % (i, added_file)
+        Command(dest_dir).execute(cmd)
+        if vcs == 'hg':
+            cmd = """hg commit -m "committed new %s" -u "%s" "%s" """ % (
+                i, author_str, added_file
+            )
+        elif vcs == 'git':
+            cmd = """git commit -m "committed new %s" --author "%s" "%s" """ % (
+                i, author_str, added_file
+            )
+        # git commit needs EMAIL on some machines
+        Command(dest_dir).execute(cmd, EMAIL=email)
+
+def _add_files_and_push(webserver, vcs, dest_dir, ignoreReturnCode=False, files_no=3,
+                            clone_url=None, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS):
+    _add_files(vcs, dest_dir, files_no=files_no)
+    # PUSH it back
+    _REPO = None
+    if vcs == 'hg':
+        _REPO = HG_REPO
+    elif vcs == 'git':
+        _REPO = GIT_REPO
+
+    if clone_url is None:
+        clone_url = webserver.repo_url(_REPO, username=username, password=password)
+
+    stdout = stderr = None
+    if vcs == 'hg':
+        stdout, stderr = Command(dest_dir).execute('hg push --verbose', clone_url, ignoreReturnCode=ignoreReturnCode)
+    elif vcs == 'git':
+        stdout, stderr = Command(dest_dir).execute('git push --verbose', clone_url, "master", ignoreReturnCode=ignoreReturnCode)
+
+    return stdout, stderr
+
+
+def _check_outgoing(vcs, cwd, clone_url=''):
+    if vcs == 'hg':
+        # hg removes the password from default URLs, so we have to provide it here via the clone_url
+        return Command(cwd).execute('hg -q outgoing', clone_url, ignoreReturnCode=True)
+    elif vcs == 'git':
+        Command(cwd).execute('git remote update')
+        return Command(cwd).execute('git log origin/master..master')
+
+
+def set_anonymous_access(enable=True):
+    user = User.get_default_user()
+    user.active = enable
+    Session().commit()
+    if enable != User.get_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
+
+
+@pytest.mark.usefixtures("test_context_fixture")
+class TestVCSOperations(TestController):
+
+    @classmethod
+    def setup_class(cls):
+        # DISABLE ANONYMOUS ACCESS
+        set_anonymous_access(False)
+
+    def setup_method(self, method):
+        r = Repository.get_by_repo_name(GIT_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().commit()
+
+        r = Repository.get_by_repo_name(HG_REPO)
+        Repository.unlock(r)
+        r.enable_locking = False
+        Session().commit()
+
+    @pytest.fixture()
+    def testhook_cleanup(self):
+        yield
+        # remove hook
+        for hook in ['prechangegroup', 'pretxnchangegroup', 'preoutgoing', 'changegroup', 'outgoing', 'incoming']:
+            entry = Ui.get_by_key('hooks', '%s.testhook' % hook)
+            if entry:
+                Session().delete(entry)
+        Session().commit()
+
+    @pytest.fixture(scope="module")
+    def testfork(self):
+        # create fork so the repo stays untouched
+        git_fork_name = u'%s_fork%s' % (GIT_REPO, _RandomNameSequence().next())
+        fixture.create_fork(GIT_REPO, git_fork_name)
+        hg_fork_name = u'%s_fork%s' % (HG_REPO, _RandomNameSequence().next())
+        fixture.create_fork(HG_REPO, hg_fork_name)
+        return {'git': git_fork_name, 'hg': hg_fork_name}
+
+    def test_clone_hg_repo_by_admin(self, webserver):
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
+
+        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, webserver):
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
+
+    def test_clone_wrong_credentials_hg(self, webserver):
+        clone_url = webserver.repo_url(HG_REPO, password='bad!')
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'abort: authorization failed' in stderr
+
+    def test_clone_wrong_credentials_git(self, webserver):
+        clone_url = webserver.repo_url(GIT_REPO, password='bad!')
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_clone_git_dir_as_hg(self, webserver):
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_hg_repo_as_git(self, webserver):
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'not found' in stderr
+
+    def test_clone_non_existing_path_hg(self, webserver):
+        clone_url = webserver.repo_url('trololo')
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_clone_non_existing_path_git(self, webserver):
+        clone_url = webserver.repo_url('trololo')
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        assert 'not found' in stderr
+
+    def test_push_new_repo_git(self, webserver):
+        # Clear the log so we know what is added
+        UserLog.query().delete()
+        Session().commit()
+
+        # Create an empty server repo using the API
+        repo_name = u'new_git_%s' % _RandomNameSequence().next()
+        usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
+        params = {
+            "id": 7,
+            "api_key": usr.api_key,
+            "method": 'create_repo',
+            "args": dict(repo_name=repo_name,
+                         owner=TEST_USER_ADMIN_LOGIN,
+                         repo_type='git'),
+        }
+        req = urllib2.Request(
+            'http://%s:%s/_admin/api' % webserver.server_address,
+            data=json.dumps(params),
+            headers={'content-type': 'application/json'})
+        response = urllib2.urlopen(req)
+        result = json.loads(response.read())
+        # Expect something like:
+        # {u'result': {u'msg': u'Created new repository `new_git_XXX`', u'task': None, u'success': True}, u'id': 7, u'error': None}
+        assert result[u'result'][u'success']
+
+        # Create local clone of the empty server repo
+        local_clone_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(repo_name)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, local_clone_dir, ignoreReturnCode=True)
+
+        # Make 3 commits and push to the empty server repo.
+        # The server repo doesn't have any other heads than the
+        # refs/heads/master we are pushing, but the `git log` in the push hook
+        # should still list the 3 commits.
+        stdout, stderr = _add_files_and_push(webserver, 'git', local_clone_dir, clone_url=clone_url)
+        _check_proper_git_push(stdout, stderr)
+
+        # Verify that we got the right events in UserLog. Expect something like:
+        # <UserLog('id:new_git_XXX:started_following_repo')>
+        # <UserLog('id:new_git_XXX:user_created_repo')>
+        # <UserLog('id:new_git_XXX:pull')>
+        # <UserLog('id:new_git_XXX:push:aed9d4c1732a1927da3be42c47eb9afdc200d427,d38b083a07af10a9f44193486959a96a23db78da,4841ff9a2b385bec995f4679ef649adb3f437622')>
+        uls = list(UserLog.query().order_by(UserLog.user_log_id))
+        assert len(uls) == 4
+        assert uls[0].action == 'started_following_repo'
+        assert uls[1].action == 'user_created_repo'
+        assert uls[2].action == 'pull'
+        assert uls[3].action.startswith(u'push:')
+        assert uls[3].action.count(',') == 2 # expect 3 commits
+
+    def test_push_new_file_hg(self, webserver, testfork):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        clone_url = webserver.repo_url(testfork['hg'])
+        stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, clone_url=clone_url)
+
+        assert 'pushing to' in stdout
+        assert 'Repository size' in stdout
+        assert 'Last revision is now' in stdout
+
+    def test_push_new_file_git(self, webserver, testfork):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        # commit some stuff into this repo
+        clone_url = webserver.repo_url(testfork['git'])
+        stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, clone_url=clone_url)
+        print [(x.repo_full_path,x.repo_path) for x in Repository.query()] # TODO: what is this for
+        _check_proper_git_push(stdout, stderr)
+
+    def test_push_invalidates_cache_hg(self, webserver, testfork):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               == HG_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(HG_REPO, HG_REPO)
+            Session().add(key)
+
+        key.cache_active = True
+        Session().commit()
+
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(testfork['hg'])
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, files_no=1, clone_url=clone_url)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               == testfork['hg']).all()
+        assert key == []
+
+    def test_push_invalidates_cache_git(self, webserver, testfork):
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               == GIT_REPO).scalar()
+        if not key:
+            key = CacheInvalidation(GIT_REPO, GIT_REPO)
+            Session().add(key)
+
+        key.cache_active = True
+        Session().commit()
+
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(testfork['git'])
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        # commit some stuff into this repo
+        stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, files_no=1, clone_url=clone_url)
+        _check_proper_git_push(stdout, stderr)
+
+        key = CacheInvalidation.query().filter(CacheInvalidation.cache_key
+                                               == testfork['git']).all()
+        assert key == []
+
+    def test_push_wrong_credentials_hg(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username='bad',
+                                             password='name', ignoreReturnCode=True)
+
+        assert 'abort: authorization failed' in stderr
+
+    def test_push_wrong_credentials_git(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username='bad',
+                                             password='name', ignoreReturnCode=True)
+
+        assert 'fatal: Authentication failed' in stderr
+
+    def test_push_with_readonly_credentials_hg(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(HG_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, 'hg', dest_dir, username=TEST_USER_REGULAR_LOGIN,
+                                             password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
+
+        assert 'abort: HTTP Error 403: Forbidden' in stderr
+
+    def test_push_with_readonly_credentials_git(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO, username=TEST_USER_REGULAR_LOGIN, password=TEST_USER_REGULAR_PASS)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, 'git', dest_dir, username=TEST_USER_REGULAR_LOGIN,
+                                             password=TEST_USER_REGULAR_PASS, ignoreReturnCode=True)
+
+        assert 'The requested URL returned error: 403' in stderr
+
+    def test_push_back_to_wrong_url_hg(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(
+            webserver, 'hg', dest_dir, clone_url='http://%s:%s/tmp' % (
+                webserver.server_address[0], webserver.server_address[1]),
+            ignoreReturnCode=True)
+
+        assert 'HTTP Error 404: Not Found' in stderr
+
+    def test_push_back_to_wrong_url_git(self, webserver):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(
+            webserver, 'git', dest_dir, clone_url='http://%s:%s/tmp' % (
+                webserver.server_address[0], webserver.server_address[1]),
+            ignoreReturnCode=True)
+
+        assert 'not found' in stderr
+
+    def test_clone_and_create_lock_hg(self, webserver):
+        # enable locking
+        r = Repository.get_by_repo_name(HG_REPO)
+        r.enable_locking = True
+        Session().commit()
+        # clone
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
+
+        # 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, webserver):
+        # enable locking
+        r = Repository.get_by_repo_name(GIT_REPO)
+        r.enable_locking = True
+        Session().commit()
+        # clone
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
+
+        # 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, webserver):
+        # 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 = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        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, webserver):
+        # 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 = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+        msg = ("""The requested URL returned error: 423""")
+        assert msg in stderr
+
+    def test_push_on_locked_repo_by_other_user_hg(self, webserver):
+        # clone some temp
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        # 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(webserver, 'hg', dest_dir,
+                                             username=TEST_USER_REGULAR_LOGIN,
+                                             password=TEST_USER_REGULAR_PASS,
+                                             ignoreReturnCode=True)
+        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, webserver):
+        # Note: Git hooks must be executable on unix. This test will thus fail
+        # for example on Linux if /tmp is mounted noexec.
+
+        # clone some temp
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        # 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(webserver, 'git', dest_dir,
+                                             username=TEST_USER_REGULAR_LOGIN,
+                                             password=TEST_USER_REGULAR_PASS,
+                                             ignoreReturnCode=True)
+        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, webserver, testfork):
+        # enable locking
+        r = Repository.get_by_repo_name(testfork['hg'])
+        r.enable_locking = True
+        Session().commit()
+        # clone some temp
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(testfork['hg'])
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, dest_dir)
+
+        # check for lock repo after clone
+        r = Repository.get_by_repo_name(testfork['hg'])
+        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(webserver, 'hg', dest_dir, clone_url=clone_url)
+        assert str('remote: Released lock on repo `%s`' % testfork['hg']) in stdout
+        # we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(testfork['hg'])
+        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, webserver, testfork):
+        # enable locking
+        r = Repository.get_by_repo_name(testfork['git'])
+        r.enable_locking = True
+        Session().commit()
+        # clone some temp
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(testfork['git'])
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, dest_dir)
+
+        # check for lock repo after clone
+        r = Repository.get_by_repo_name(testfork['git'])
+        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(webserver, 'git', dest_dir, clone_url=clone_url)
+        _check_proper_git_push(stdout, stderr)
+
+        assert ('remote: Released lock on repo `%s`' % testfork['git']) in stderr
+        # we need to cleanup the Session Here !
+        Session.remove()
+        r = Repository.get_by_repo_name(testfork['git'])
+        assert r.locked == [None, None]
+
+    def test_ip_restriction_hg(self, webserver):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            # IP permissions are cached, need to wait for the cache in the server process to expire
+            time.sleep(1.5)
+            clone_url = webserver.repo_url(HG_REPO)
+            stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+            assert 'abort: HTTP Error 403: Forbidden' in stderr
+        finally:
+            # release IP restrictions
+            for ip in UserIpMap.query():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+            # IP permissions are cached, need to wait for the cache in the server process to expire
+            time.sleep(1.5)
+
+        clone_url = webserver.repo_url(HG_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('hg clone', clone_url, _get_tmp_dir())
+
+        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, webserver):
+        user_model = UserModel()
+        try:
+            user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
+            Session().commit()
+            # IP permissions are cached, need to wait for the cache in the server process to expire
+            time.sleep(1.5)
+            clone_url = webserver.repo_url(GIT_REPO)
+            stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir(), ignoreReturnCode=True)
+            # The message apparently changed in Git 1.8.3, so match it loosely.
+            assert re.search(r'\b403\b', stderr)
+        finally:
+            # release IP restrictions
+            for ip in UserIpMap.query():
+                UserIpMap.delete(ip.ip_id)
+            Session().commit()
+            # IP permissions are cached, need to wait for the cache in the server process to expire
+            time.sleep(1.5)
+
+        clone_url = webserver.repo_url(GIT_REPO)
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', clone_url, _get_tmp_dir())
+
+        assert 'Cloning into' in stdout + stderr
+        assert stderr == '' or stdout == ''
+
+    @parametrize('repo_type, repo_name', [
+                #('git',      GIT_REPO), # git hooks doesn't work like hg hooks
+                ('hg',       HG_REPO),
+    ])
+    def test_custom_hooks_preoutgoing(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
+        # set prechangegroup to failing hook (returns True)
+        Ui.create_or_update_hook('preoutgoing.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
+        Session().commit()
+        # clone repo
+        clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
+        dest_dir = _get_tmp_dir()
+        stdout, stderr = Command(TESTS_TMP_PATH) \
+            .execute('%s clone' % repo_type, clone_url, dest_dir, ignoreReturnCode=True)
+        if repo_type == 'hg':
+            assert 'preoutgoing.testhook hook failed' in stdout
+        elif repo_type == 'git':
+            assert 'error: 406' in stderr
+
+    @parametrize('repo_type, repo_name', [
+                #('git',      GIT_REPO), # git hooks doesn't work like hg hooks
+                ('hg',       HG_REPO),
+    ])
+    def test_custom_hooks_prechangegroup(self, testhook_cleanup, webserver, testfork, repo_type, repo_name):
+
+        # set prechangegroup to failing hook (returns True)
+        Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.failing_test_hook')
+        Session().commit()
+        # clone repo
+        clone_url = webserver.repo_url(testfork[repo_type], username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS)
+        dest_dir = _get_tmp_dir()
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('%s clone' % repo_type, clone_url, dest_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, repo_type, dest_dir,
+                                             username=TEST_USER_ADMIN_LOGIN,
+                                             password=TEST_USER_ADMIN_PASS,
+                                             ignoreReturnCode=True)
+        assert 'failing_test_hook failed' in stdout + stderr
+        assert 'Traceback' not in stdout + stderr
+        assert 'prechangegroup.testhook hook failed' in stdout + stderr
+        # there are still outgoing changesets
+        stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
+        assert stdout != ''
+
+        # set prechangegroup hook to exception throwing method
+        Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.exception_test_hook')
+        Session().commit()
+        # re-try to push
+        stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
+        if repo_type == 'hg':
+            # like with 'hg serve...' 'HTTP Error 500: INTERNAL SERVER ERROR' should be returned
+            assert 'HTTP Error 500: INTERNAL SERVER ERROR' in stderr
+        elif repo_type == 'git':
+            assert 'exception_test_hook threw an exception' in stderr
+        # there are still outgoing changesets
+        stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
+        assert stdout != ''
+
+        # set prechangegroup hook to method that returns False
+        Ui.create_or_update_hook('prechangegroup.testhook', 'python:kallithea.tests.fixture.passing_test_hook')
+        Session().commit()
+        # re-try to push
+        stdout, stderr = Command(dest_dir).execute('%s push' % repo_type, clone_url, ignoreReturnCode=True)
+        assert 'passing_test_hook succeeded' in stdout + stderr
+        assert 'Traceback' not in stdout + stderr
+        assert 'prechangegroup.testhook hook failed' not in stdout + stderr
+        # no more outgoing changesets
+        stdout, stderr = _check_outgoing(repo_type, dest_dir, clone_url)
+        assert stdout == ''
+        assert stderr == ''
+
+    def test_add_submodule(self, webserver, testfork):
+        dest_dir = _get_tmp_dir()
+        clone_url = webserver.repo_url(GIT_REPO)
+
+        fork_url = webserver.repo_url(testfork['git'])
+
+        # add submodule
+        stdout, stderr = Command(TESTS_TMP_PATH).execute('git clone', fork_url, dest_dir)
+        stdout, stderr = Command(dest_dir).execute('git submodule add', clone_url, 'testsubmodule')
+        stdout, stderr = Command(dest_dir).execute('git commit -am "added testsubmodule pointing to', clone_url, '"', EMAIL=TEST_USER_ADMIN_EMAIL)
+        stdout, stderr = Command(dest_dir).execute('git push', fork_url, 'master')
+
+        # check for testsubmodule link in files page
+        self.log_user()
+        response = self.app.get(url(controller='files', action='index',
+                                    repo_name=testfork['git'],
+                                    revision='tip',
+                                    f_path='/'))
+        response.mustcontain('<a class="submodule-dir" href="%s" target="_blank"><i class="icon-file-submodule"></i><span>testsubmodule @ ' % clone_url)
+
+        # check that following a submodule link actually works - and redirects
+        response = self.app.get(url(controller='files', action='index',
+                                    repo_name=testfork['git'],
+                                    revision='tip',
+                                    f_path='/testsubmodule'),
+                                status=302)
+        assert response.location == clone_url
--- a/kallithea/tests/parameterized.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-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 as 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/performance/test_vcs.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,36 @@
+# -*- 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/>.
+
+import pytest
+
+from kallithea.model.db import Repository
+from kallithea.tests.base import *
+
+@pytest.mark.skipif("not os.environ.has_key('TEST_PERFORMANCE')", reason="skipping performance tests, set TEST_PERFORMANCE in environment if desired")
+class TestVCSPerformance(TestController):
+
+    def graphmod(self, repo):
+        """ Simple test for running the graph_data function for profiling/testing performance. """
+        from kallithea.lib.graphmod import graph_data
+        dbr = Repository.get_by_repo_name(repo)
+        scm_inst = dbr.scm_instance
+        collection = scm_inst.get_changesets(start=0, end=None, branch_name=None)
+        revs = [x.revision for x in collection]
+        jsdata = graph_data(scm_inst, revs)
+
+    def test_graphmod_hg(self, benchmark):
+        benchmark(self.graphmod, HG_REPO)
+
+    def test_graphmod_git(self, benchmark):
+        benchmark(self.graphmod, GIT_REPO)
--- a/kallithea/tests/scripts/create_rc.sh	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/scripts/create_rc.sh	Sun Mar 31 21:28:56 2019 +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 server.ini --force-yes --user=username --password=qweqwe --email=username@example.com --repos=/home/username/repos --no-public-access
+kallithea-cli db-create -c 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 server.ini --pid-file=server.pid --daemon
+gearbox serve -c 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
--- a/kallithea/tests/scripts/manual_test_concurrency.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/scripts/manual_test_concurrency.py	Sun Mar 31 21:28:56 2019 +0200
@@ -30,29 +30,27 @@
 import sys
 import shutil
 import logging
-from os.path import join as jn
-from os.path import dirname as dn
-
-from tempfile import _RandomNameSequence
+import tempfile
+from os.path import dirname
 from subprocess import Popen, PIPE
 
 from paste.deploy import appconfig
 from sqlalchemy import engine_from_config
 
-from kallithea.lib.utils import add_cache
-from kallithea.model import init_model
+from kallithea.lib.utils import setup_cache_regions
+from kallithea.model.base import init_model
 from kallithea.model import meta
-from kallithea.model.db import User, Repository
+from kallithea.model.db import User, Repository, Ui
 from kallithea.lib.auth import get_crypt_password
 
-from kallithea.tests import TESTS_TMP_PATH, HG_REPO
+from kallithea.tests.base import HG_REPO
 from kallithea.config.environment import load_environment
 
-rel_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
+rel_path = dirname(dirname(dirname(dirname(os.path.abspath(__file__)))))
 conf = appconfig('config:development.ini', relative_to=rel_path)
 load_environment(conf.global_conf, conf.local_conf)
 
-add_cache(conf)
+setup_cache_regions(conf)
 
 USER = TEST_USER_ADMIN_LOGIN
 PASS = TEST_USER_ADMIN_PASS
@@ -83,7 +81,7 @@
 
 
 def get_session():
-    engine = engine_from_config(conf, 'sqlalchemy.db1.')
+    engine = engine_from_config(conf, 'sqlalchemy.')
     init_model(engine)
     sa = meta.Session
     return sa
@@ -134,9 +132,9 @@
 
         form_data = {'repo_name': HG_REPO,
                      'repo_type': 'hg',
-                     'private':False,
-                     'clone_uri': '' }
-        rm = RepoModel(sa)
+                     'private': False,
+                     'clone_uri': ''}
+        rm = RepoModel()
         rm.base_path = '/home/hg'
         rm.create(form_data, user)
 
@@ -160,16 +158,13 @@
 # TESTS
 #==============================================================================
 def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
-                                seq=None, backend='hg'):
-    cwd = path = jn(TESTS_TMP_PATH, repo)
-
-    if seq is None:
-        seq = _RandomNameSequence().next()
+                                backend='hg'):
+    cwd = path = os.path.join(Ui.get_by_key('paths', '/').ui_value, repo)
 
     try:
         shutil.rmtree(path, ignore_errors=True)
         os.makedirs(path)
-        #print 'made dirs %s' % jn(path)
+        #print 'made dirs %s' % os.path.join(path)
     except OSError:
         raise
 
@@ -179,23 +174,22 @@
                    'host': HOST,
                    'cloned_repo': repo, }
 
-    dest = path + seq
+    dest = tempfile.mktemp(dir=path, prefix='dest-')
     if method == 'pull':
         stdout, stderr = Command(cwd).execute(backend, method, '--cwd', dest, clone_url)
     else:
         stdout, stderr = Command(cwd).execute(backend, method, clone_url, dest)
-        print stdout,'sdasdsadsa'
         if not no_errors:
             if backend == 'hg':
                 assert """adding file changes""" in stdout, 'no messages about cloning'
-                assert """abort""" not in stderr , 'got error from clone'
+                assert """abort""" not in stderr, 'got error from clone'
             elif backend == 'git':
                 assert """Cloning into""" in stdout, 'no messages about cloning'
 
+
 if __name__ == '__main__':
     try:
         create_test_user(force=False)
-        seq = None
         import time
 
         try:
@@ -209,14 +203,14 @@
             backend = 'hg'
 
         if METHOD == 'pull':
-            seq = _RandomNameSequence().next()
+            seq = tempfile._RandomNameSequence().next()
             test_clone_with_credentials(repo=sys.argv[1], method='clone',
-                                        seq=seq, backend=backend)
+                                        backend=backend)
         s = time.time()
         for i in range(1, int(sys.argv[2]) + 1):
             print 'take', i
             test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
-                                        seq=seq, backend=backend)
+                                        backend=backend)
         print 'time taken %.3f' % (time.time() - s)
     except Exception as e:
         sys.exit('stop on %s' % e)
--- a/kallithea/tests/scripts/manual_test_crawler.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/scripts/manual_test_crawler.py	Sun Mar 31 21:28:56 2019 +0200
@@ -37,11 +37,11 @@
 import time
 import os
 import sys
-from os.path import join as jn
-from os.path import dirname as dn
+import tempfile
+from os.path import dirname
 
 __here__ = os.path.abspath(__file__)
-__root__ = dn(dn(dn(__here__)))
+__root__ = dirname(dirname(dirname(__here__)))
 sys.path.append(__root__)
 
 from kallithea.lib import vcs
@@ -61,15 +61,15 @@
 
 print 'Crawling @ %s' % BASE_URI
 BASE_URI += '%s'
-PROJECT_PATH = jn('/', 'home', 'username', 'repos')
+PROJECT_PATH = os.path.join('/', 'home', 'username', 'repos')
 PROJECTS = [
-    #'linux-magx-pbranch',
+    # 'linux-magx-pbranch',
     'CPython',
     'kallithea',
 ]
 
 
-cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
+cj = cookielib.FileCookieJar(os.path.join(tempfile.gettempdir(), 'rc_test_cookie.txt'))
 o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
 o.addheaders = [
     ('User-agent', 'kallithea-crawler'),
@@ -81,7 +81,7 @@
 
 def _get_repo(proj):
     if isinstance(proj, basestring):
-        repo = vcs.get_repo(jn(PROJECT_PATH, proj))
+        repo = vcs.get_repo(os.path.join(PROJECT_PATH, proj))
         proj = proj
     else:
         repo = proj
@@ -116,7 +116,7 @@
 def test_changeset_walk(proj, limit=None):
     repo, proj = _get_repo(proj)
 
-    print 'processing', jn(PROJECT_PATH, proj)
+    print 'processing', os.path.join(PROJECT_PATH, proj)
     total_time = 0
 
     cnt = 0
@@ -142,7 +142,7 @@
 def test_files_walk(proj, limit=100):
     repo, proj = _get_repo(proj)
 
-    print 'processing', jn(PROJECT_PATH, proj)
+    print 'processing', os.path.join(PROJECT_PATH, proj)
     total_time = 0
 
     paths_ = OrderedSet([''])
@@ -182,7 +182,7 @@
 
 if __name__ == '__main__':
     for path in PROJECTS:
-        repo = vcs.get_repo(jn(PROJECT_PATH, path))
+        repo = vcs.get_repo(os.path.join(PROJECT_PATH, path))
         for i in range(PASES):
             print 'PASS %s/%s' % (i, PASES)
             test_changelog_walk(repo, pages=80)
--- a/kallithea/tests/test.ini	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,591 +0,0 @@
-################################################################################
-################################################################################
-# Kallithea - config for tests:                                                #
-# initial_repo_scan = true                                                     #
-# vcs_full_cache = false                                                       #
-# sqlalchemy and kallithea_test.sqlite                                         #
-# custom logging                                                               #
-#                                                                              #
-# The %(here)s variable will be replaced with the parent directory of this file#
-################################################################################
-################################################################################
-
-[DEFAULT]
-debug = true
-pdebug = false
-
-################################################################################
-## Email settings                                                             ##
-##                                                                            ##
-## Refer to the documentation ("Email settings") for more details.            ##
-##                                                                            ##
-## It is recommended to use a valid sender address that passes access         ##
-## validation and spam filtering in mail servers.                             ##
-################################################################################
-
-## 'From' header for application emails. You can optionally add a name.
-## Default:
-#app_email_from = Kallithea
-## Examples:
-#app_email_from = Kallithea <kallithea-noreply@example.com>
-#app_email_from = kallithea-noreply@example.com
-
-## Subject prefix for application emails.
-## A space between this prefix and the real subject is automatically added.
-## Default:
-#email_prefix =
-## Example:
-#email_prefix = [Kallithea]
-
-## Recipients for error emails and fallback recipients of application mails.
-## Multiple addresses can be specified, space-separated.
-## Only addresses are allowed, do not add any name part.
-## Default:
-#email_to =
-## Examples:
-#email_to = admin@example.com
-#email_to = admin@example.com another_admin@example.com
-
-## 'From' header for error emails. You can optionally add a name.
-## Default:
-#error_email_from = pylons@yourapp.com
-## Examples:
-#error_email_from = Kallithea Errors <kallithea-noreply@example.com>
-#error_email_from = paste_error@example.com
-
-## SMTP server settings
-## Only smtp_server is mandatory. All other settings take the specified default
-## values.
-#smtp_server = smtp.example.com
-#smtp_username =
-#smtp_password =
-#smtp_port = 25
-#smtp_use_tls = false
-#smtp_use_ssl = false
-## SMTP authentication parameters to use (e.g. LOGIN PLAIN CRAM-MD5, etc.).
-## If empty, use any of the authentication parameters supported by the server.
-#smtp_auth =
-
-[server:main]
-## PASTE ##
-#use = egg:Paste#http
-## nr of worker threads to spawn
-#threadpool_workers = 1
-## max request before thread respawn
-#threadpool_max_requests = 100
-## option to use threads of process
-#use_threadpool = true
-
-## WAITRESS ##
-use = egg:waitress#main
-## number of worker threads
-threads = 1
-## MAX BODY SIZE 100GB
-max_request_body_size = 107374182400
-## use poll instead of select, fixes fd limits, may not work on old
-## windows systems.
-#asyncore_use_poll = True
-
-## GUNICORN ##
-#use = egg:gunicorn#main
-## number of process workers. You must set `instance_id = *` when this option
-## is set to more than one worker
-#workers = 1
-## process name
-#proc_name = kallithea
-## type of worker class, one of sync, eventlet, gevent, tornado
-## recommended for bigger setup is using of of other than sync one
-#worker_class = sync
-#max_requests = 1000
-## ammount of time a worker can handle request before it gets killed and
-## restarted
-#timeout = 3600
-
-## UWSGI ##
-## run with uwsgi --ini-paste-logged <inifile.ini>
-#[uwsgi]
-#socket = /tmp/uwsgi.sock
-#master = true
-#http = 127.0.0.1:5000
-
-## set as deamon and redirect all output to file
-#daemonize = ./uwsgi_kallithea.log
-
-## master process PID
-#pidfile = ./uwsgi_kallithea.pid
-
-## stats server with workers statistics, use uwsgitop
-## for monitoring, `uwsgitop 127.0.0.1:1717`
-#stats = 127.0.0.1:1717
-#memory-report = true
-
-## log 5XX errors
-#log-5xx = true
-
-## Set the socket listen queue size.
-#listen = 256
-
-## Gracefully Reload workers after the specified amount of managed requests
-## (avoid memory leaks).
-#max-requests = 1000
-
-## enable large buffers
-#buffer-size = 65535
-
-## socket and http timeouts ##
-#http-timeout = 3600
-#socket-timeout = 3600
-
-## Log requests slower than the specified number of milliseconds.
-#log-slow = 10
-
-## Exit if no app can be loaded.
-#need-app = true
-
-## Set lazy mode (load apps in workers instead of master).
-#lazy = true
-
-## scaling ##
-## set cheaper algorithm to use, if not set default will be used
-#cheaper-algo = spare
-
-## minimum number of workers to keep at all times
-#cheaper = 1
-
-## number of workers to spawn at startup
-#cheaper-initial = 1
-
-## maximum number of workers that can be spawned
-#workers = 4
-
-## how many workers should be spawned at a time
-#cheaper-step = 1
-
-## COMMON ##
-host = 127.0.0.1
-port = 4999
-
-## middleware for hosting the WSGI application under a URL prefix
-#[filter:proxy-prefix]
-#use = egg:PasteDeploy#prefix
-#prefix = /<your-prefix>
-
-[app:main]
-use = egg:kallithea
-## enable proxy prefix middleware
-#filter-with = proxy-prefix
-
-full_stack = true
-static_files = true
-## Available Languages:
-## cs de fr hu ja nl_BE pl pt_BR ru sk zh_CN zh_TW
-lang =
-cache_dir = %(here)s/data
-index_dir = %(here)s/data/index
-
-## perform a full repository scan on each server start, this should be
-## set to false after first startup, to allow faster server restarts.
-#initial_repo_scan = false
-initial_repo_scan = true
-
-## uncomment and set this path to use archive download cache
-archive_cache_dir = %(here)s/tarballcache
-
-## change this to unique ID for security
-app_instance_uuid = test
-
-## cut off limit for large diffs (size in bytes)
-cut_off_limit = 256000
-
-## use cache version of scm repo everywhere
-#vcs_full_cache = true
-vcs_full_cache = false
-
-## force https in Kallithea, fixes https redirects, assumes it's always https
-force_https = false
-
-## use Strict-Transport-Security headers
-use_htsts = false
-
-## number of commits stats will parse on each iteration
-commit_parse_limit = 25
-
-## path to git executable
-git_path = git
-
-## git rev filter option, --all is the default filter, if you need to
-## hide all refs in changelog switch this to --branches --tags
-#git_rev_filter = --branches --tags
-
-## RSS feed options
-rss_cut_off_limit = 256000
-rss_items_per_page = 10
-rss_include_diff = false
-
-## options for showing and identifying changesets
-show_sha_length = 12
-show_revision_number = true
-
-## gist URL alias, used to create nicer urls for gist. This should be an
-## url that does rewrites to _admin/gists/<gistid>.
-## example: http://gist.example.com/{gistid}. Empty means use the internal
-## Kallithea url, ie. http[s]://kallithea.example.com/_admin/gists/<gistid>
-gist_alias_url =
-
-## white list of API enabled controllers. This allows to add list of
-## controllers to which access will be enabled by api_key. eg: to enable
-## api access to raw_files put `FilesController:raw`, to enable access to patches
-## add `ChangesetController:changeset_patch`. This list should be "," separated
-## Syntax is <ControllerClass>:<function>. Check debug logs for generated names
-## Recommended settings below are commented out:
-api_access_controllers_whitelist =
-#    ChangesetController:changeset_patch,
-#    ChangesetController:changeset_raw,
-#    FilesController:raw,
-#    FilesController:archivefile
-
-## default encoding used to convert from and to unicode
-## can be also a comma seperated list of encoding in case of mixed encodings
-default_encoding = utf8
-
-## issue tracker for Kallithea (leave blank to disable, absent for default)
-#bugtracker = https://bitbucket.org/conservancy/kallithea/issues
-
-## issue tracking mapping for commits messages
-## comment out issue_pat, issue_server, issue_prefix to enable
-
-## pattern to get the issues from commit messages
-## default one used here is #<numbers> with a regex passive group for `#`
-## {id} will be all groups matched from this pattern
-
-issue_pat = (?:\s*#)(\d+)
-
-## server url to the issue, each {id} will be replaced with match
-## fetched from the regex and {repo} is replaced with full repository name
-## including groups {repo_name} is replaced with just name of repo
-
-issue_server_link = https://issues.example.com/{repo}/issue/{id}
-
-## prefix to add to link to indicate it's an url
-## #314 will be replaced by <issue_prefix><id>
-
-issue_prefix = #
-
-## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
-## multiple patterns, to other issues server, wiki or others
-## below an example how to create a wiki pattern
-# wiki-some-id -> https://wiki.example.com/some-id
-
-#issue_pat_wiki = (?:wiki-)(.+)
-#issue_server_link_wiki = https://wiki.example.com/{id}
-#issue_prefix_wiki = WIKI-
-
-## instance-id prefix
-## a prefix key for this instance used for cache invalidation when running
-## multiple instances of kallithea, make sure it's globally unique for
-## all running kallithea instances. Leave empty if you don't use it
-instance_id =
-
-## alternative return HTTP header for failed authentication. Default HTTP
-## response is 401 HTTPUnauthorized. Currently Mercurial clients have trouble with
-## handling that. Set this variable to 403 to return HTTPForbidden
-auth_ret_code =
-
-## locking return code. When repository is locked return this HTTP code. 2XX
-## codes don't break the transactions while 4XX codes do
-lock_ret_code = 423
-
-## allows to change the repository location in settings page
-allow_repo_location_change = True
-
-## allows to setup custom hooks in settings page
-allow_custom_hooks_settings = True
-
-####################################
-###        CELERY CONFIG        ####
-####################################
-
-use_celery = false
-broker.host = localhost
-broker.vhost = rabbitmqhost
-broker.port = 5672
-broker.user = rabbitmq
-broker.password = qweqwe
-
-celery.imports = kallithea.lib.celerylib.tasks
-
-celery.result.backend = amqp
-celery.result.dburi = amqp://
-celery.result.serialier = json
-
-#celery.send.task.error.emails = true
-#celery.amqp.task.result.expires = 18000
-
-celeryd.concurrency = 2
-#celeryd.log.file = celeryd.log
-celeryd.log.level = DEBUG
-celeryd.max.tasks.per.child = 1
-
-## tasks will never be sent to the queue, but executed locally instead.
-celery.always.eager = false
-
-####################################
-###         BEAKER CACHE        ####
-####################################
-
-beaker.cache.data_dir = %(here)s/data/cache/data
-beaker.cache.lock_dir = %(here)s/data/cache/lock
-
-beaker.cache.regions = short_term,long_term,sql_cache_short
-
-beaker.cache.short_term.type = memory
-beaker.cache.short_term.expire = 60
-beaker.cache.short_term.key_length = 256
-
-beaker.cache.long_term.type = memory
-beaker.cache.long_term.expire = 36000
-beaker.cache.long_term.key_length = 256
-
-beaker.cache.sql_cache_short.type = memory
-beaker.cache.sql_cache_short.expire = 1
-beaker.cache.sql_cache_short.key_length = 256
-
-####################################
-###       BEAKER SESSION        ####
-####################################
-
-## Name of session cookie. Should be unique for a given host and path, even when running
-## on different ports. Otherwise, cookie sessions will be shared and messed up.
-beaker.session.key = kallithea
-## Sessions should always only be accessible by the browser, not directly by JavaScript.
-beaker.session.httponly = true
-## Session lifetime. 2592000 seconds is 30 days.
-beaker.session.timeout = 2592000
-
-## Server secret used with HMAC to ensure integrity of cookies.
-beaker.session.secret = {74e0cd75-b339-478b-b129-07dd221def1f}
-## Further, encrypt the data with AES.
-#beaker.session.encrypt_key = <key_for_encryption>
-#beaker.session.validate_key = <validation_key>
-
-## Type of storage used for the session, current types are
-## dbm, file, memcached, database, and memory.
-
-## File system storage of session data. (default)
-#beaker.session.type = file
-
-## Cookie only, store all session data inside the cookie. Requires secure secrets.
-#beaker.session.type = cookie
-
-## Database storage of session data.
-#beaker.session.type = ext:database
-#beaker.session.sa.url = postgresql://postgres:qwe@localhost/kallithea
-#beaker.session.table_name = db_session
-
-############################
-## ERROR HANDLING SYSTEMS ##
-############################
-
-####################
-### [appenlight] ###
-####################
-
-## AppEnlight is tailored to work with Kallithea, see
-## http://appenlight.com for details how to obtain an account
-## you must install python package `appenlight_client` to make it work
-
-## appenlight enabled
-appenlight = false
-
-appenlight.server_url = https://api.appenlight.com
-appenlight.api_key = YOUR_API_KEY
-
-## TWEAK AMOUNT OF INFO SENT HERE
-
-## enables 404 error logging (default False)
-appenlight.report_404 = false
-
-## time in seconds after request is considered being slow (default 1)
-appenlight.slow_request_time = 1
-
-## record slow requests in application
-## (needs to be enabled for slow datastore recording and time tracking)
-appenlight.slow_requests = true
-
-## enable hooking to application loggers
-#appenlight.logging = true
-
-## minimum log level for log capture
-#appenlight.logging.level = WARNING
-
-## send logs only from erroneous/slow requests
-## (saves API quota for intensive logging)
-appenlight.logging_on_error = false
-
-## list of additonal keywords that should be grabbed from environ object
-## can be string with comma separated list of words in lowercase
-## (by default client will always send following info:
-## 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
-## start with HTTP* this list be extended with additional keywords here
-appenlight.environ_keys_whitelist =
-
-## list of keywords that should be blanked from request object
-## can be string with comma separated list of words in lowercase
-## (by default client will always blank keys that contain following words
-## 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
-## this list be extended with additional keywords set here
-appenlight.request_keys_blacklist =
-
-## list of namespaces that should be ignores when gathering log entries
-## can be string with comma separated list of namespaces
-## (by default the client ignores own entries: appenlight_client.client)
-appenlight.log_namespace_blacklist =
-
-################
-### [sentry] ###
-################
-
-## sentry is a alternative open source error aggregator
-## you must install python packages `sentry` and `raven` to enable
-
-sentry.dsn = YOUR_DNS
-sentry.servers =
-sentry.name =
-sentry.key =
-sentry.public_key =
-sentry.secret_key =
-sentry.project =
-sentry.site =
-sentry.include_paths =
-sentry.exclude_paths =
-
-################################################################################
-## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
-## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
-## execute malicious code after an exception is raised.                       ##
-################################################################################
-set debug = false
-
-##################################
-###       LOGVIEW CONFIG       ###
-##################################
-
-logview.sqlalchemy = #faa
-logview.pylons.templating = #bfb
-logview.pylons.util = #eee
-
-#########################################################
-### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
-#########################################################
-
-# SQLITE [default]
-#sqlalchemy.db1.url = sqlite:///%(here)s/kallithea.db?timeout=60
-sqlalchemy.db1.url = sqlite:///%(here)s/kallithea_test.sqlite
-
-# POSTGRESQL
-#sqlalchemy.db1.url = postgresql://user:pass@localhost/kallithea
-
-# MySQL
-#sqlalchemy.db1.url = mysql://user:pass@localhost/kallithea
-
-# see sqlalchemy docs for others
-
-sqlalchemy.db1.echo = false
-sqlalchemy.db1.pool_recycle = 3600
-sqlalchemy.db1.convert_unicode = true
-
-################################
-### LOGGING CONFIGURATION   ####
-################################
-
-[loggers]
-keys = root, routes, kallithea, sqlalchemy, beaker, templates, whoosh_indexer
-
-[handlers]
-keys = console, console_sql
-
-[formatters]
-keys = generic, color_formatter, color_formatter_sql
-
-#############
-## LOGGERS ##
-#############
-
-[logger_root]
-#level = NOTSET
-level = DEBUG
-handlers = console
-
-[logger_routes]
-level = DEBUG
-handlers =
-qualname = routes.middleware
-## "level = DEBUG" logs the route matched and routing variables.
-propagate = 1
-
-[logger_beaker]
-level = DEBUG
-handlers =
-qualname = beaker.container
-propagate = 1
-
-[logger_templates]
-level = INFO
-handlers =
-qualname = pylons.templating
-propagate = 1
-
-[logger_kallithea]
-level = DEBUG
-handlers =
-qualname = kallithea
-propagate = 1
-
-[logger_sqlalchemy]
-#level = INFO
-#handlers = console_sql
-level = ERROR
-handlers = console
-qualname = sqlalchemy.engine
-propagate = 0
-
-[logger_whoosh_indexer]
-level = DEBUG
-handlers =
-qualname = whoosh_indexer
-propagate = 1
-
-##############
-## HANDLERS ##
-##############
-
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-#level = INFO
-level = NOTSET
-formatter = generic
-
-[handler_console_sql]
-class = StreamHandler
-args = (sys.stderr,)
-level = WARN
-formatter = generic
-
-################
-## FORMATTERS ##
-################
-
-[formatter_generic]
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter]
-class = kallithea.lib.colored_formatter.ColorFormatter
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
-
-[formatter_color_formatter_sql]
-class = kallithea.lib.colored_formatter.ColorFormatterSql
-format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %Y-%m-%d %H:%M:%S
--- a/kallithea/tests/vcs/__init__.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/__init__.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,29 +1,36 @@
 """
 Unit tests for vcs_ library.
 
-In order to run tests we need to prepare our environment first. Tests would be
-run for each engine listed at ``conf.SCM_TESTS`` - keys are aliases from
-``vcs.backends.BACKENDS``.
+While some tests are implemented for a specific backend, a huge number
+is completely independent of the underlying backend.
+
+For such independent tests a base testing class is implemented, and
+backend-specific test classes are defined. These sub-classes simply
+need to set the correct backend to use by setting the
+``backend_alias`` property, which should correspond to one of the keys
+from ``vcs.backends.BACKENDS``.
 
 For each SCM we run tests for, we need some repository. We would use
-repositories location from system environment variables or test suite defaults
-- see ``conf`` module for more detail. We simply try to check if repository at
-certain location exists, if not we would try to fetch them. At ``test_vcs`` or
-``test_common`` we run unit tests common for each repository type and for
-example specific mercurial tests are located at ``test_hg`` module.
+repositories location provided in test suite defaults - see ``conf``
+module for more detail. We simply try to check if repository at
+certain location exists, if not we would try to fetch them. At
+``test_vcs`` or ``test_common`` we run unit tests common for each
+repository type and for example specific mercurial tests are located
+at ``test_hg`` module.
+"""
 
-Oh, and tests are run with ``unittest.collector`` wrapped by ``collector``
-function at ``tests/__init__.py``.
+import os
+import shutil
 
-.. _vcs: http://bitbucket.org/marcinkuzminski/vcs
-.. _unittest: http://pypi.python.org/pypi/unittest
-
-"""
-from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.conf import *
+from kallithea.tests.base import TEST_HG_REPO, HG_REMOTE_REPO, TEST_GIT_REPO, GIT_REMOTE_REPO, TESTS_TMP_PATH
 from kallithea.tests.vcs.utils import SCMFetcher
 
-from kallithea.tests import *
+
+# Base directory for the VCS tests.
+VCS_TEST_MODULE_BASE_DIR = os.path.abspath(os.path.dirname(__file__))
+
+# Path to user configuration file used during tests.
+TEST_USER_CONFIG_FILE = os.path.join(TESTS_TMP_PATH, 'aconfig')
 
 
 def setup_package():
@@ -50,16 +57,6 @@
         fetcher = SCMFetcher(**fetcher_info)
         fetcher.setup()
 
-
-def collector():
-    setup_package()
-    start_dir = os.path.abspath(os.path.dirname(__file__))
-    return unittest.defaultTestLoader.discover(start_dir)
-
-
-def main():
-    collector()
-    unittest.main()
-
-#if __name__ == '__main__':
-#    main()
+    # Copy the test user configuration file to location where
+    # temporary test data is stored at.
+    shutil.copy(os.path.join(VCS_TEST_MODULE_BASE_DIR, 'aconfig'), TEST_USER_CONFIG_FILE)
--- a/kallithea/tests/vcs/aconfig	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/aconfig	Sun Mar 31 21:28:56 2019 +0200
@@ -7,4 +7,3 @@
 
 [universal]
 foo = bar
-
--- a/kallithea/tests/vcs/base.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/base.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,38 +1,30 @@
 """
-Module providing backend independent mixin class. It requires that
-InMemoryChangeset class is working properly at backend class.
+Module providing backend independent mixin class.
 """
 import os
 import time
-import shutil
 import datetime
-from kallithea.tests.vcs.conf import SCM_TESTS, get_new_dir
+import pytest
 
 from kallithea.lib import vcs
-from kallithea.lib.vcs.utils.compat import unittest
 from kallithea.lib.vcs.nodes import FileNode
 
+from kallithea.tests.vcs.conf import get_new_dir
+
 
 class _BackendTestMixin(object):
     """
-    This is a backend independent test case class which should be created
-    with ``type`` method.
+    This is a backend independent test case class
 
-    It is required to set following attributes at subclass:
+    It is possible to set following attributes at subclass:
 
     - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
-    - ``repo_path``: path to the repository which would be created for set of
-      tests
     - ``recreate_repo_per_test``: If set to ``False``, repo would NOT be created
       before every single test. Defaults to ``True``.
     """
     recreate_repo_per_test = True
 
     @classmethod
-    def get_backend(cls):
-        return vcs.get_backend(cls.backend_alias)
-
-    @classmethod
     def _get_commits(cls):
         commits = [
             {
@@ -60,12 +52,23 @@
         ]
         return commits
 
+    # Note: cannot use classmethod fixtures with pytest 3.7.1+
+    @pytest.fixture(autouse=True,
+                    scope='class')
+    def _configure_backend(self, request):
+        Backend = vcs.get_backend(self.backend_alias)
+        type(self).backend_class = Backend
+        type(self).setup_repo(Backend)
+
     @classmethod
-    def setUpClass(cls):
-        Backend = cls.get_backend()
-        cls.backend_class = Backend
-        cls.repo_path = get_new_dir(str(time.time()))
-        cls.repo = Backend(cls.repo_path, create=True)
+    def setup_empty_repo(cls, backend):
+        repo_path = get_new_dir(str(time.time()))
+        repo = backend(repo_path, create=True)
+        return repo
+
+    @classmethod
+    def setup_repo(cls, backend):
+        cls.repo = cls.setup_empty_repo(backend)
         cls.imc = cls.repo.in_memory_changeset
         cls.default_branch = cls.repo.DEFAULT_BRANCH_NAME
 
@@ -81,31 +84,7 @@
                                      author=unicode(commit['author']),
                                      date=commit['date'])
 
-    @classmethod
-    def tearDownClass(cls):
-        if not getattr(cls, 'recreate_repo_per_test', False) and \
-            'VCS_REMOVE_TEST_DIRS' in os.environ:
-            shutil.rmtree(cls.repo_path)
-
-    def setUp(self):
+    @pytest.fixture(autouse=True)
+    def _possibly_recreate_repo(self):
         if getattr(self, 'recreate_repo_per_test', False):
-            self.__class__.setUpClass()
-
-    def tearDown(self):
-        if getattr(self, 'recreate_repo_per_test', False) and \
-            'VCS_REMOVE_TEST_DIRS' in os.environ:
-            shutil.rmtree(self.repo_path)
-
-
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s base backend test' % alias).title().split())
-    bases = (_BackendTestMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
-
-
-if __name__ == '__main__':
-    unittest.main()
+            self.setup_repo(self.backend_class)
--- a/kallithea/tests/vcs/conf.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/conf.py	Sun Mar 31 21:28:56 2019 +0200
@@ -2,57 +2,35 @@
 Unit tests configuration module for vcs.
 """
 import os
-import time
-import hashlib
-import tempfile
-import datetime
-import shutil
 import uuid
-from os.path import join as jn
+
+# Retrieve the necessary configuration options from the test base
+# module. Some of these configuration options are subsequently
+# consumed by the VCS test module.
+from kallithea.tests.base import (
+    TESTS_TMP_PATH,
+    TEST_HG_REPO, HG_REMOTE_REPO,
+    TEST_HG_REPO_CLONE, TEST_HG_REPO_PULL,
+    TEST_GIT_REPO, GIT_REMOTE_REPO,
+    TEST_GIT_REPO_CLONE,
+)
 
 __all__ = (
     'TEST_HG_REPO', 'TEST_GIT_REPO', 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO',
-    'SCM_TESTS',
+    'TEST_HG_REPO_CLONE', 'TEST_GIT_REPO_CLONE', 'TEST_HG_REPO_PULL',
 )
 
-SCM_TESTS = ['hg', 'git']
-uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple())))
-
-THIS = os.path.abspath(os.path.dirname(__file__))
-
-GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git'
-
-TEST_TMP_PATH = os.environ.get('VCS_TEST_ROOT', '/tmp')
-TEST_GIT_REPO = os.environ.get('VCS_TEST_GIT_REPO',
-                              jn(TEST_TMP_PATH, 'vcs-git'))
-TEST_GIT_REPO_CLONE = os.environ.get('VCS_TEST_GIT_REPO_CLONE',
-                            jn(TEST_TMP_PATH, 'vcsgitclone%s' % uniq_suffix))
-TEST_GIT_REPO_PULL = os.environ.get('VCS_TEST_GIT_REPO_PULL',
-                            jn(TEST_TMP_PATH, 'vcsgitpull%s' % uniq_suffix))
-
-HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs'
-TEST_HG_REPO = os.environ.get('VCS_TEST_HG_REPO',
-                              jn(TEST_TMP_PATH, 'vcs-hg'))
-TEST_HG_REPO_CLONE = os.environ.get('VCS_TEST_HG_REPO_CLONE',
-                              jn(TEST_TMP_PATH, 'vcshgclone%s' % uniq_suffix))
-TEST_HG_REPO_PULL = os.environ.get('VCS_TEST_HG_REPO_PULL',
-                              jn(TEST_TMP_PATH, 'vcshgpull%s' % uniq_suffix))
-
-TEST_DIR = os.environ.get('VCS_TEST_ROOT', tempfile.gettempdir())
-TEST_REPO_PREFIX = 'vcs-test'
-
 
 def get_new_dir(title=None):
     """
-    Calculates a path for a new, non-existant, unique sub-directory in TEST_DIR.
+    Calculates a path for a new, non-existant, unique sub-directory in TESTS_TMP_PATH.
 
     Resulting directory name will have format:
 
-    prefix-[title-]hexuuid
+    vcs-test-[title-]hexuuid
 
-    Prefix is equal to value of variable TEST_REPO_PREFIX. The "hexuuid" is a
-    hexadecimal value of a randomly generated UUID. Title will be added if
-    specified.
+    The "hexuuid" is a hexadecimal value of a randomly generated
+    UUID. Title will be added if specified.
 
     Args:
         title: Custom title to include as part of the resulting sub-directory
@@ -63,12 +41,14 @@
         Path to the new directory as a string.
     """
 
+    test_repo_prefix = 'vcs-test'
+
     if title:
-        name = "%s-%s" % (TEST_REPO_PREFIX, title)
+        name = "%s-%s" % (test_repo_prefix, title)
     else:
-        name = TEST_REPO_PREFIX
+        name = test_repo_prefix
 
-    path = os.path.join(TEST_DIR, name)
+    path = os.path.join(TESTS_TMP_PATH, name)
 
     # Generate new hexes until we get a unique name (just in case).
     hex_uuid = uuid.uuid4().hex
@@ -76,13 +56,3 @@
         hex_uuid = uuid.uuid4().hex
 
     return "%s-%s" % (path, hex_uuid)
-
-
-PACKAGE_DIR = os.path.abspath(os.path.join(
-    os.path.dirname(__file__), '..'))
-_dest = jn(TEST_TMP_PATH, 'aconfig')
-shutil.copy(jn(THIS, 'aconfig'), _dest)
-TEST_USER_CONFIG_FILE = _dest
-
-#overide default configurations with kallithea ones
-from kallithea.tests import *
--- a/kallithea/tests/vcs/test_archives.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_archives.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,15 +1,17 @@
-
 import os
 import tarfile
 import zipfile
 import datetime
 import tempfile
 import StringIO
-from kallithea.tests.vcs.base import _BackendTestMixin
-from kallithea.tests.vcs.conf import SCM_TESTS
+
+import pytest
+
 from kallithea.lib.vcs.exceptions import VCSError
 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.conf import TESTS_TMP_PATH
 
 
 class ArchivesTestCaseMixin(_BackendTestMixin):
@@ -29,7 +31,7 @@
             }
 
     def test_archive_zip(self):
-        path = tempfile.mkstemp()[1]
+        path = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_zip-')[1]
         with open(path, 'wb') as f:
             self.tip.fill_archive(stream=f, kind='zip', prefix='repo')
         out = zipfile.ZipFile(path)
@@ -38,70 +40,66 @@
             node_path = '%d/file_%d.txt' % (x, x)
             decompressed = StringIO.StringIO()
             decompressed.write(out.read('repo/' + node_path))
-            self.assertEqual(
-                decompressed.getvalue(),
-                self.tip.get_node(node_path).content)
+            assert decompressed.getvalue() == self.tip.get_node(node_path).content
 
     def test_archive_tgz(self):
-        path = tempfile.mkstemp()[1]
+        path = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_tgz-')[1]
         with open(path, 'wb') as f:
             self.tip.fill_archive(stream=f, kind='tgz', prefix='repo')
-        outdir = tempfile.mkdtemp()
+        outdir = tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix='test_archive_tgz-', suffix='-outdir')
 
         outfile = tarfile.open(path, 'r|gz')
         outfile.extractall(outdir)
 
         for x in xrange(5):
             node_path = '%d/file_%d.txt' % (x, x)
-            self.assertEqual(
-                open(os.path.join(outdir, 'repo/' + node_path)).read(),
-                self.tip.get_node(node_path).content)
+            assert open(os.path.join(outdir, 'repo/' + node_path)).read() == self.tip.get_node(node_path).content
 
     def test_archive_tbz2(self):
-        path = tempfile.mkstemp()[1]
+        path = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_tbz2-')[1]
         with open(path, 'w+b') as f:
             self.tip.fill_archive(stream=f, kind='tbz2', prefix='repo')
-        outdir = tempfile.mkdtemp()
+        outdir = tempfile.mkdtemp(dir=TESTS_TMP_PATH, prefix='test_archive_tbz2-', suffix='-outdir')
 
         outfile = tarfile.open(path, 'r|bz2')
         outfile.extractall(outdir)
 
         for x in xrange(5):
             node_path = '%d/file_%d.txt' % (x, x)
-            self.assertEqual(
-                open(os.path.join(outdir, 'repo/' + node_path)).read(),
-                self.tip.get_node(node_path).content)
+            assert open(os.path.join(outdir, 'repo/' + node_path)).read() == self.tip.get_node(node_path).content
 
     def test_archive_default_stream(self):
-        tmppath = tempfile.mkstemp()[1]
-        with open(tmppath, 'w') as stream:
+        tmppath = tempfile.mkstemp(dir=TESTS_TMP_PATH, prefix='test_archive_default_stream-')[1]
+        with open(tmppath, 'wb') as stream:
             self.tip.fill_archive(stream=stream)
         mystream = StringIO.StringIO()
         self.tip.fill_archive(stream=mystream)
         mystream.seek(0)
-        with open(tmppath, 'r') as f:
-            self.assertEqual(f.read(), mystream.read())
+        with open(tmppath, 'rb') as f:
+            file_content = f.read()
+            stringio_content = mystream.read()
+            # the gzip header contains a MTIME header
+            # because is takes a little bit of time from one fill_archive call to the next
+            # this part may differ so don't include that part in the comparison
+            assert file_content[:4] == stringio_content[:4]
+            assert file_content[8:] == stringio_content[8:]
 
     def test_archive_wrong_kind(self):
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             self.tip.fill_archive(kind='wrong kind')
 
     def test_archive_empty_prefix(self):
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             self.tip.fill_archive(prefix='')
 
     def test_archive_prefix_with_leading_slash(self):
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             self.tip.fill_archive(prefix='/any')
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s archive test' % alias).title().split())
-    bases = (ArchivesTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+
+class TestGitArchive(ArchivesTestCaseMixin):
+    backend_alias = 'git'
 
-if __name__ == '__main__':
-    unittest.main()
+
+class TestHgArchive(ArchivesTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_branches.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_branches.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,11 +1,8 @@
-
 import datetime
 from kallithea.lib import vcs
-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.conf import SCM_TESTS
 
 
 class BranchesTestCaseMixin(_BackendTestMixin):
@@ -40,12 +37,12 @@
 
     def test_simple(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip.date, datetime.datetime(2010, 1, 1, 21))
+        assert tip.date == datetime.datetime(2010, 1, 1, 21)
 
     def test_new_branch(self):
         # This check must not be removed to ensure the 'branches' LazyProperty
         # gets hit *before* the new 'foobar' branch got created:
-        self.assertFalse('foobar' in self.repo.branches)
+        assert 'foobar' not in self.repo.branches
         self.imc.add(vcs.nodes.FileNode('docs/index.txt',
             content='Documentation\n'))
         foobar_tip = self.imc.commit(
@@ -53,8 +50,9 @@
             author=u'joe',
             branch='foobar',
         )
-        self.assertTrue('foobar' in self.repo.branches)
-        self.assertEqual(foobar_tip.branch, 'foobar')
+        assert 'foobar' in self.repo.branches
+        assert foobar_tip.branch == 'foobar'
+        assert foobar_tip.branches == ['foobar']
 
     def test_new_head(self):
         tip = self.repo.get_changeset()
@@ -82,14 +80,14 @@
             parents=[newtip, foobar_tip],
         )
 
-        self.assertEqual(newest_tip.branch,
-            self.backend_class.DEFAULT_BRANCH_NAME)
+        assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
+        assert newest_tip.branches == [self.backend_class.DEFAULT_BRANCH_NAME]
 
     def test_branch_with_slash_in_name(self):
         self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n'))
         self.imc.commit(u'Branch with a slash!', author=u'joe',
             branch='issue/123')
-        self.assertTrue('issue/123' in self.repo.branches)
+        assert 'issue/123' in self.repo.branches
 
     def test_branch_with_slash_in_name_and_similar_without(self):
         self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n'))
@@ -98,19 +96,13 @@
         self.imc.add(vcs.nodes.FileNode('extrafile II', content='Some data\n'))
         self.imc.commit(u'Branch without a slash...', author=u'joe',
             branch='123')
-        self.assertIn('issue/123', self.repo.branches)
-        self.assertIn('123', self.repo.branches)
+        assert 'issue/123' in self.repo.branches
+        assert '123' in self.repo.branches
 
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s branches test' % alias).title().split())
-    bases = (BranchesTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitBranches(BranchesTestCaseMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestHgBranches(BranchesTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_changesets.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_changesets.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,10 +1,11 @@
-# encoding: utf8
+# encoding: utf-8
 
 import time
 import datetime
+
+import pytest
+
 from kallithea.lib import vcs
-from kallithea.tests.vcs.base import _BackendTestMixin
-from kallithea.tests.vcs.conf import SCM_TESTS
 
 from kallithea.lib.vcs.backends.base import BaseChangeset
 from kallithea.lib.vcs.nodes import (
@@ -15,11 +16,12 @@
     BranchDoesNotExistError, ChangesetDoesNotExistError,
     RepositoryError, EmptyRepositoryError
 )
-from kallithea.lib.vcs.utils.compat import unittest
+
+from kallithea.tests.vcs.base import _BackendTestMixin
 from kallithea.tests.vcs.conf import get_new_dir
 
 
-class TestBaseChangeset(unittest.TestCase):
+class TestBaseChangeset(object):
 
     def test_as_dict(self):
         changeset = BaseChangeset()
@@ -30,10 +32,10 @@
         changeset.date = datetime.datetime(2011, 1, 30, 1, 45)
         changeset.message = 'Message of a commit'
         changeset.author = 'Joe Doe <joe.doe@example.com>'
-        changeset.added = [FileNode('foo/bar/baz'), FileNode('foobar')]
+        changeset.added = [FileNode('foo/bar/baz'), FileNode(u'foobar'), FileNode(u'blåbærgrød')]
         changeset.changed = []
         changeset.removed = []
-        self.assertEqual(changeset.as_dict(), {
+        assert changeset.as_dict() == {
             'id': 'ID',
             'raw_id': 'RAW_ID',
             'short_id': 'SHORT_ID',
@@ -44,13 +46,13 @@
                 'name': 'Joe Doe',
                 'email': 'joe.doe@example.com',
             },
-            'added': ['foo/bar/baz', 'foobar'],
+            'added': ['foo/bar/baz', 'foobar', u'bl\xe5b\xe6rgr\xf8d'],
             'changed': [],
             'removed': [],
-        })
+        }
+
 
 class _ChangesetsWithCommitsTestCaseixin(_BackendTestMixin):
-    recreate_repo_per_test = True
 
     @classmethod
     def _get_commits(cls):
@@ -73,10 +75,12 @@
             author=u'joe',
             branch='foobar',
         )
-        self.assertTrue('foobar' in self.repo.branches)
-        self.assertEqual(foobar_tip.branch, 'foobar')
+        assert 'foobar' in self.repo.branches
+        assert foobar_tip.branch == 'foobar'
+        assert foobar_tip.branches == ['foobar']
         # 'foobar' should be the only branch that contains the new commit
-        self.assertNotEqual(*self.repo.branches.values())
+        branch_tips = self.repo.branches.values()
+        assert branch_tips.count(str(foobar_tip.raw_id)) == 1
 
     def test_new_head_in_default_branch(self):
         tip = self.repo.get_changeset()
@@ -104,8 +108,8 @@
             parents=[newtip, foobar_tip],
         )
 
-        self.assertEqual(newest_tip.branch,
-            self.backend_class.DEFAULT_BRANCH_NAME)
+        assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
+        assert newest_tip.branches == [self.backend_class.DEFAULT_BRANCH_NAME]
 
     def test_get_changesets_respects_branch_name(self):
         tip = self.repo.get_changeset()
@@ -124,25 +128,25 @@
         )
         default_branch_changesets = self.repo.get_changesets(
             branch_name=self.repo.DEFAULT_BRANCH_NAME)
-        self.assertNotIn(doc_changeset, default_branch_changesets)
+        assert doc_changeset not in default_branch_changesets
 
     def test_get_changeset_by_branch(self):
         for branch, sha in self.repo.branches.iteritems():
-            self.assertEqual(sha, self.repo.get_changeset(branch).raw_id)
+            assert sha == self.repo.get_changeset(branch).raw_id
 
     def test_get_changeset_by_tag(self):
         for tag, sha in self.repo.tags.iteritems():
-            self.assertEqual(sha, self.repo.get_changeset(tag).raw_id)
+            assert sha == self.repo.get_changeset(tag).raw_id
 
     def test_get_changeset_parents(self):
         for test_rev in [1, 2, 3]:
             sha = self.repo.get_changeset(test_rev-1)
-            self.assertEqual([sha], self.repo.get_changeset(test_rev).parents)
+            assert [sha] == self.repo.get_changeset(test_rev).parents
 
     def test_get_changeset_children(self):
         for test_rev in [1, 2, 3]:
             sha = self.repo.get_changeset(test_rev+1)
-            self.assertEqual([sha], self.repo.get_changeset(test_rev).children)
+            assert [sha] == self.repo.get_changeset(test_rev).children
 
 
 class _ChangesetsTestCaseMixin(_BackendTestMixin):
@@ -163,139 +167,137 @@
 
     def test_simple(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip.date, datetime.datetime(2010, 1, 3, 20))
+        assert tip.date == datetime.datetime(2010, 1, 3, 20)
 
     def test_get_changesets_is_ordered_by_date(self):
         changesets = list(self.repo.get_changesets())
         ordered_by_date = sorted(changesets,
             key=lambda cs: cs.date)
-        self.assertItemsEqual(changesets, ordered_by_date)
+
+        assert changesets == ordered_by_date
 
     def test_get_changesets_respects_start(self):
         second_id = self.repo.revisions[1]
         changesets = list(self.repo.get_changesets(start=second_id))
-        self.assertEqual(len(changesets), 4)
+        assert len(changesets) == 4
 
     def test_get_changesets_numerical_id_respects_start(self):
         second_id = 1
         changesets = list(self.repo.get_changesets(start=second_id))
-        self.assertEqual(len(changesets), 4)
+        assert len(changesets) == 4
 
     def test_get_changesets_includes_start_changeset(self):
         second_id = self.repo.revisions[1]
         changesets = list(self.repo.get_changesets(start=second_id))
-        self.assertEqual(changesets[0].raw_id, second_id)
+        assert changesets[0].raw_id == second_id
 
     def test_get_changesets_respects_end(self):
         second_id = self.repo.revisions[1]
         changesets = list(self.repo.get_changesets(end=second_id))
-        self.assertEqual(changesets[-1].raw_id, second_id)
-        self.assertEqual(len(changesets), 2)
+        assert changesets[-1].raw_id == second_id
+        assert len(changesets) == 2
 
     def test_get_changesets_numerical_id_respects_end(self):
         second_id = 1
         changesets = list(self.repo.get_changesets(end=second_id))
-        self.assertEqual(changesets.index(changesets[-1]), second_id)
-        self.assertEqual(len(changesets), 2)
+        assert changesets.index(changesets[-1]) == second_id
+        assert len(changesets) == 2
 
     def test_get_changesets_respects_both_start_and_end(self):
         second_id = self.repo.revisions[1]
         third_id = self.repo.revisions[2]
         changesets = list(self.repo.get_changesets(start=second_id,
             end=third_id))
-        self.assertEqual(len(changesets), 2)
+        assert len(changesets) == 2
 
     def test_get_changesets_numerical_id_respects_both_start_and_end(self):
         changesets = list(self.repo.get_changesets(start=2, end=3))
-        self.assertEqual(len(changesets), 2)
+        assert len(changesets) == 2
 
     def test_get_changesets_on_empty_repo_raises_EmptyRepository_error(self):
-        Backend = self.get_backend()
-        repo_path = get_new_dir(str(time.time()))
-        repo = Backend(repo_path, create=True)
-
-        with self.assertRaises(EmptyRepositoryError):
+        repo = self.setup_empty_repo(self.backend_class)
+        with pytest.raises(EmptyRepositoryError):
             list(repo.get_changesets(start='foobar'))
 
     def test_get_changesets_includes_end_changeset(self):
         second_id = self.repo.revisions[1]
         changesets = list(self.repo.get_changesets(end=second_id))
-        self.assertEqual(changesets[-1].raw_id, second_id)
+        assert changesets[-1].raw_id == second_id
 
     def test_get_changesets_respects_start_date(self):
         start_date = datetime.datetime(2010, 2, 1)
         for cs in self.repo.get_changesets(start_date=start_date):
-            self.assertGreaterEqual(cs.date, start_date)
+            assert cs.date >= start_date
 
     def test_get_changesets_respects_end_date(self):
         start_date = datetime.datetime(2010, 1, 1)
         end_date = datetime.datetime(2010, 2, 1)
         for cs in self.repo.get_changesets(start_date=start_date,
                                            end_date=end_date):
-            self.assertGreaterEqual(cs.date, start_date)
-            self.assertLessEqual(cs.date, end_date)
+            assert cs.date >= start_date
+            assert cs.date <= end_date
 
     def test_get_changesets_respects_start_date_and_end_date(self):
         end_date = datetime.datetime(2010, 2, 1)
         for cs in self.repo.get_changesets(end_date=end_date):
-            self.assertLessEqual(cs.date, end_date)
+            assert cs.date <= end_date
 
     def test_get_changesets_respects_reverse(self):
         changesets_id_list = [cs.raw_id for cs in
             self.repo.get_changesets(reverse=True)]
-        self.assertItemsEqual(changesets_id_list, reversed(self.repo.revisions))
+        assert changesets_id_list == list(reversed(self.repo.revisions))
 
     def test_get_filenodes_generator(self):
         tip = self.repo.get_changeset()
         filepaths = [node.path for node in tip.get_filenodes_generator()]
-        self.assertItemsEqual(filepaths, ['file_%d.txt' % x for x in xrange(5)])
+        assert filepaths == ['file_%d.txt' % x for x in xrange(5)]
 
     def test_size(self):
         tip = self.repo.get_changeset()
         size = 5 * len('Foobar N') # Size of 5 files
-        self.assertEqual(tip.size, size)
+        assert tip.size == size
 
     def test_author(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip.author, u'Joe Doe <joe.doe@example.com>')
+        assert tip.author == u'Joe Doe <joe.doe@example.com>'
 
     def test_author_name(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip.author_name, u'Joe Doe')
+        assert tip.author_name == u'Joe Doe'
 
     def test_author_email(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip.author_email, u'joe.doe@example.com')
+        assert tip.author_email == u'joe.doe@example.com'
 
     def test_get_changesets_raise_changesetdoesnotexist_for_wrong_start(self):
-        with self.assertRaises(ChangesetDoesNotExistError):
+        with pytest.raises(ChangesetDoesNotExistError):
             list(self.repo.get_changesets(start='foobar'))
 
     def test_get_changesets_raise_changesetdoesnotexist_for_wrong_end(self):
-        with self.assertRaises(ChangesetDoesNotExistError):
+        with pytest.raises(ChangesetDoesNotExistError):
             list(self.repo.get_changesets(end='foobar'))
 
     def test_get_changesets_raise_branchdoesnotexist_for_wrong_branch_name(self):
-        with self.assertRaises(BranchDoesNotExistError):
+        with pytest.raises(BranchDoesNotExistError):
             list(self.repo.get_changesets(branch_name='foobar'))
 
     def test_get_changesets_raise_repositoryerror_for_wrong_start_end(self):
         start = self.repo.revisions[-1]
         end = self.repo.revisions[0]
-        with self.assertRaises(RepositoryError):
+        with pytest.raises(RepositoryError):
             list(self.repo.get_changesets(start=start, end=end))
 
     def test_get_changesets_numerical_id_reversed(self):
-        with self.assertRaises(RepositoryError):
+        with pytest.raises(RepositoryError):
             [x for x in self.repo.get_changesets(start=3, end=2)]
 
     def test_get_changesets_numerical_id_respects_both_start_and_end_last(self):
-        with self.assertRaises(RepositoryError):
+        with pytest.raises(RepositoryError):
             last = len(self.repo.revisions)
             list(self.repo.get_changesets(start=last-1, end=last-2))
 
     def test_get_changesets_numerical_id_last_zero_error(self):
-        with self.assertRaises(RepositoryError):
+        with pytest.raises(RepositoryError):
             last = len(self.repo.revisions)
             list(self.repo.get_changesets(start=last-1, end=0))
 
@@ -332,59 +334,61 @@
 
     def test_initial_commit(self):
         changeset = self.repo.get_changeset(0)
-        self.assertItemsEqual(changeset.added, [
+        assert sorted(list(changeset.added)) == sorted([
             changeset.get_node('foo/bar'),
             changeset.get_node('foo/bał'),
             changeset.get_node('foobar'),
             changeset.get_node('qwe'),
         ])
-        self.assertItemsEqual(changeset.changed, [])
-        self.assertItemsEqual(changeset.removed, [])
+        assert list(changeset.changed) == []
+        assert list(changeset.removed) == []
+        assert u'foo/ba\u0142' in changeset.as_dict()['added']
+        assert u'foo/ba\u0142' in changeset.__json__(with_file_list=True)['added']
 
     def test_head_added(self):
         changeset = self.repo.get_changeset()
-        self.assertTrue(isinstance(changeset.added, AddedFileNodesGenerator))
-        self.assertItemsEqual(changeset.added, [
+        assert isinstance(changeset.added, AddedFileNodesGenerator)
+        assert list(changeset.added) == [
             changeset.get_node('fallout'),
-        ])
-        self.assertTrue(isinstance(changeset.changed, ChangedFileNodesGenerator))
-        self.assertItemsEqual(changeset.changed, [
+        ]
+        assert isinstance(changeset.changed, ChangedFileNodesGenerator)
+        assert list(changeset.changed) == [
             changeset.get_node('foo/bar'),
             changeset.get_node('foobar'),
-        ])
-        self.assertTrue(isinstance(changeset.removed, RemovedFileNodesGenerator))
-        self.assertEqual(len(changeset.removed), 1)
-        self.assertEqual(list(changeset.removed)[0].path, 'qwe')
+        ]
+        assert isinstance(changeset.removed, RemovedFileNodesGenerator)
+        assert len(changeset.removed) == 1
+        assert list(changeset.removed)[0].path == 'qwe'
 
     def test_get_filemode(self):
         changeset = self.repo.get_changeset()
-        self.assertEqual(33188, changeset.get_file_mode('foo/bar'))
+        assert 33188 == changeset.get_file_mode('foo/bar')
 
     def test_get_filemode_non_ascii(self):
         changeset = self.repo.get_changeset()
-        self.assertEqual(33188, changeset.get_file_mode('foo/bał'))
-        self.assertEqual(33188, changeset.get_file_mode(u'foo/bał'))
+        assert 33188 == changeset.get_file_mode('foo/bał')
+        assert 33188 == changeset.get_file_mode(u'foo/bał')
+
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    # tests with additional commits
-    cls_name = ''.join(('%s changesets with commits test' % alias).title().split())
-    bases = (_ChangesetsWithCommitsTestCaseixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitChangesetsWithCommits(_ChangesetsWithCommitsTestCaseixin):
+    backend_alias = 'git'
 
-    # tests without additional commits
-    cls_name = ''.join(('%s changesets test' % alias).title().split())
-    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)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitChangesets(_ChangesetsTestCaseMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestGitChangesetsChanges(_ChangesetsChangesTestCaseMixin):
+    backend_alias = 'git'
+
+
+class TestHgChangesetsWithCommits(_ChangesetsWithCommitsTestCaseixin):
+    backend_alias = 'hg'
+
+
+class TestHgChangesets(_ChangesetsTestCaseMixin):
+    backend_alias = 'hg'
+
+
+class TestHgChangesetsChanges(_ChangesetsChangesTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_filenodes_unicode_path.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_filenodes_unicode_path.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,21 +1,20 @@
-# encoding: utf8
-
+# encoding: utf-8
 
 import datetime
+
 from kallithea.lib.vcs.nodes import FileNode
-from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.test_inmemchangesets import BackendBaseTestCase
-from kallithea.tests.vcs.conf import SCM_TESTS
+from kallithea.tests.vcs.base import _BackendTestMixin
 
 
-class FileNodeUnicodePathTestsMixin(object):
+class FileNodeUnicodePathTestsMixin(_BackendTestMixin):
 
     fname = 'ąśðąęłąć.txt'
     ufname = (fname).decode('utf-8')
 
-    def get_commits(self):
-        self.nodes = [
-            FileNode(self.fname, content='Foobar'),
+    @classmethod
+    def _get_commits(cls):
+        cls.nodes = [
+            FileNode(cls.fname, content='Foobar'),
         ]
 
         commits = [
@@ -23,7 +22,7 @@
                 'message': 'Initial commit',
                 'author': 'Joe Doe <joe.doe@example.com>',
                 'date': datetime.datetime(2010, 1, 1, 20),
-                'added': self.nodes,
+                'added': cls.nodes,
             },
         ]
         return commits
@@ -31,18 +30,12 @@
     def test_filenode_path(self):
         node = self.tip.get_node(self.fname)
         unode = self.tip.get_node(self.ufname)
-        self.assertEqual(node, unode)
+        assert node == unode
 
 
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s file node unicode path test' % alias).title()
-        .split())
-    bases = (FileNodeUnicodePathTestsMixin, BackendBaseTestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitFileNodeUnicodePath(FileNodeUnicodePathTestsMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestHgFileNodeUnicodePath(FileNodeUnicodePathTestsMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_getitem.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_getitem.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,7 @@
+import datetime
 
-import datetime
 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):
@@ -22,22 +20,16 @@
             }
 
     def test__getitem__last_item_is_tip(self):
-        self.assertEqual(self.repo[-1], self.repo.get_changeset())
+        assert self.repo[-1] == self.repo.get_changeset()
 
     def test__getitem__returns_correct_items(self):
         changesets = [self.repo[x] for x in xrange(len(self.repo.revisions))]
-        self.assertEqual(changesets, list(self.repo.get_changesets()))
+        assert changesets == list(self.repo.get_changesets())
 
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s getitem test' % alias).title().split())
-    bases = (GetitemTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitGetitem(GetitemTestCaseMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestHgGetitem(GetitemTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_getslice.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_getslice.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,8 @@
+import datetime
 
-import datetime
+from kallithea.lib.vcs.nodes import FileNode
+
 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):
@@ -22,34 +21,24 @@
             }
 
     def test__getslice__last_item_is_tip(self):
-        self.assertEqual(list(self.repo[-1:])[0], self.repo.get_changeset())
+        assert list(self.repo[-1:])[0] == self.repo.get_changeset()
 
     def test__getslice__respects_start_index(self):
-        self.assertEqual(list(self.repo[2:]),
-            [self.repo.get_changeset(rev) for rev in self.repo.revisions[2:]])
+        assert list(self.repo[2:]) == [self.repo.get_changeset(rev) for rev in self.repo.revisions[2:]]
 
     def test__getslice__respects_negative_start_index(self):
-        self.assertEqual(list(self.repo[-2:]),
-            [self.repo.get_changeset(rev) for rev in self.repo.revisions[-2:]])
+        assert list(self.repo[-2:]) == [self.repo.get_changeset(rev) for rev in self.repo.revisions[-2:]]
 
     def test__getslice__respects_end_index(self):
-        self.assertEqual(list(self.repo[:2]),
-            [self.repo.get_changeset(rev) for rev in self.repo.revisions[:2]])
+        assert list(self.repo[:2]) == [self.repo.get_changeset(rev) for rev in self.repo.revisions[:2]]
 
     def test__getslice__respects_negative_end_index(self):
-        self.assertEqual(list(self.repo[:-2]),
-            [self.repo.get_changeset(rev) for rev in self.repo.revisions[:-2]])
+        assert list(self.repo[:-2]) == [self.repo.get_changeset(rev) for rev in self.repo.revisions[:-2]]
 
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s getslice test' % alias).title().split())
-    bases = (GetsliceTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitGetslice(GetsliceTestCaseMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestHgGetslice(GetsliceTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_git.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_git.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,40 +1,43 @@
-
 import os
 import sys
 import mock
 import datetime
 import urllib2
+
+import pytest
+
 from kallithea.lib.vcs.backends.git import GitRepository, GitChangeset
 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.model.scm import ScmModel
+
 from kallithea.tests.vcs.base import _BackendTestMixin
-from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
+from kallithea.tests.vcs.conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, TESTS_TMP_PATH, get_new_dir
 
 
-class GitRepositoryTest(unittest.TestCase):
+class TestGitRepository(object):
 
     def __check_for_existing_repo(self):
         if os.path.exists(TEST_GIT_REPO_CLONE):
-            self.fail('Cannot test git clone repo as location %s already '
+            pytest.fail('Cannot test git clone repo as location %s already '
                       'exists. You should manually remove it first.'
                       % TEST_GIT_REPO_CLONE)
 
-    def setUp(self):
+    def setup_method(self):
         self.repo = GitRepository(TEST_GIT_REPO)
 
     def test_wrong_repo_path(self):
-        wrong_repo_path = '/tmp/errorrepo'
-        self.assertRaises(RepositoryError, GitRepository, wrong_repo_path)
+        wrong_repo_path = os.path.join(TESTS_TMP_PATH, 'errorrepo')
+        with pytest.raises(RepositoryError):
+            GitRepository(wrong_repo_path)
 
     def test_git_cmd_injection(self):
         repo_inject_path = TEST_GIT_REPO + '; echo "Cake";'
-        with self.assertRaises(urllib2.URLError):
+        with pytest.raises(urllib2.URLError):
             # Should fail because URL will contain the parts after ; too
-            urlerror_fail_repo = GitRepository(get_new_dir('injection-repo'), src_url=repo_inject_path, update_after_clone=True, create=True)
+            GitRepository(get_new_dir('injection-repo'), src_url=repo_inject_path, update_after_clone=True, create=True)
 
-        with self.assertRaises(RepositoryError):
+        with pytest.raises(RepositoryError):
             # Should fail on direct clone call, which as of this writing does not happen outside of class
             clone_fail_repo = GitRepository(get_new_dir('injection-repo'), create=True)
             clone_fail_repo.clone(repo_inject_path, update_after_clone=True,)
@@ -42,21 +45,23 @@
         # Verify correct quoting of evil characters that should work on posix file systems
         if sys.platform == 'win32':
             # windows does not allow '"' in dir names
-            tricky_path = get_new_dir("tricky-path-repo-$'`")
+            # and some versions of the git client don't like ` and '
+            tricky_path = get_new_dir("tricky-path-repo-$")
         else:
             tricky_path = get_new_dir("tricky-path-repo-$'\"`")
         successfully_cloned = GitRepository(tricky_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
         # Repo should have been created
-        self.assertFalse(successfully_cloned._repo.bare)
+        assert not successfully_cloned._repo.bare
 
         if sys.platform == 'win32':
             # windows does not allow '"' in dir names
-            tricky_path_2 = get_new_dir("tricky-path-2-repo-$'`")
+            # and some versions of the git client don't like ` and '
+            tricky_path_2 = get_new_dir("tricky-path-2-repo-$")
         else:
             tricky_path_2 = get_new_dir("tricky-path-2-repo-$'\"`")
         successfully_cloned2 = GitRepository(tricky_path_2, src_url=tricky_path, bare=True, create=True)
         # Repo should have been created and thus used correct quoting for clone
-        self.assertTrue(successfully_cloned2._repo.bare)
+        assert successfully_cloned2._repo.bare
 
         # Should pass because URL has been properly quoted
         successfully_cloned.pull(tricky_path_2)
@@ -66,73 +71,69 @@
         repo_path = get_new_dir("path with spaces")
         repo = GitRepository(repo_path, src_url=None, bare=True, create=True)
         # Repo should have been created
-        self.assertTrue(repo._repo.bare)
+        assert repo._repo.bare
 
     def test_repo_clone(self):
         self.__check_for_existing_repo()
         repo = GitRepository(TEST_GIT_REPO)
         repo_clone = GitRepository(TEST_GIT_REPO_CLONE,
             src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
+        assert len(repo.revisions) == len(repo_clone.revisions)
         # Checking hashes of changesets should be enough
         for changeset in repo.get_changesets():
             raw_id = changeset.raw_id
-            self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
+            assert raw_id == repo_clone.get_changeset(raw_id).raw_id
 
     def test_repo_clone_with_spaces_in_path(self):
         repo_path = get_new_dir("path with spaces")
         successfully_cloned = GitRepository(repo_path, src_url=TEST_GIT_REPO, update_after_clone=True, create=True)
         # Repo should have been created
-        self.assertFalse(successfully_cloned._repo.bare)
+        assert not successfully_cloned._repo.bare
 
         successfully_cloned.pull(TEST_GIT_REPO)
         self.repo.fetch(repo_path)
 
     def test_repo_clone_without_create(self):
-        self.assertRaises(RepositoryError, GitRepository,
-            TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
+        with pytest.raises(RepositoryError):
+            GitRepository(TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
 
     def test_repo_clone_with_update(self):
         repo = GitRepository(TEST_GIT_REPO)
         clone_path = TEST_GIT_REPO_CLONE + '_with_update'
         repo_clone = GitRepository(clone_path,
             create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
+        assert len(repo.revisions) == len(repo_clone.revisions)
 
-        #check if current workdir was updated
+        # check if current workdir was updated
         fpath = os.path.join(clone_path, 'MANIFEST.in')
-        self.assertEqual(True, os.path.isfile(fpath),
-            'Repo was cloned and updated but file %s could not be found'
-            % fpath)
+        assert os.path.isfile(fpath) == True, 'Repo was cloned and updated but file %s could not be found' % fpath
 
     def test_repo_clone_without_update(self):
         repo = GitRepository(TEST_GIT_REPO)
         clone_path = TEST_GIT_REPO_CLONE + '_without_update'
         repo_clone = GitRepository(clone_path,
             create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
-        #check if current workdir was *NOT* updated
+        assert len(repo.revisions) == len(repo_clone.revisions)
+        # check if current workdir was *NOT* updated
         fpath = os.path.join(clone_path, 'MANIFEST.in')
         # Make sure it's not bare repo
-        self.assertFalse(repo_clone._repo.bare)
-        self.assertEqual(False, os.path.isfile(fpath),
-            'Repo was cloned and updated but file %s was found'
-            % fpath)
+        assert not repo_clone._repo.bare
+        assert os.path.isfile(fpath) == False, 'Repo was cloned and updated but file %s was found' % fpath
 
     def test_repo_clone_into_bare_repo(self):
         repo = GitRepository(TEST_GIT_REPO)
         clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
         repo_clone = GitRepository(clone_path, create=True,
             src_url=repo.path, bare=True)
-        self.assertTrue(repo_clone._repo.bare)
+        assert repo_clone._repo.bare
 
     def test_create_repo_is_not_bare_by_default(self):
         repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
-        self.assertFalse(repo._repo.bare)
+        assert not repo._repo.bare
 
     def test_create_bare_repo(self):
         repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
-        self.assertTrue(repo._repo.bare)
+        assert repo._repo.bare
 
     def test_revisions(self):
         # there are 112 revisions (by now)
@@ -163,78 +164,73 @@
             '27d48942240f5b91dfda77accd2caac94708cc7d',
             '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
             'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
-        self.assertTrue(subset.issubset(set(self.repo.revisions)))
-
-
+        assert subset.issubset(set(self.repo.revisions))
 
     def test_slicing(self):
-        #4 1 5 10 95
+        # 4 1 5 10 95
         for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
                                  (10, 20, 10), (5, 100, 95)]:
             revs = list(self.repo[sfrom:sto])
-            self.assertEqual(len(revs), size)
-            self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
-            self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
-
+            assert len(revs) == size
+            assert revs[0] == self.repo.get_changeset(sfrom)
+            assert revs[-1] == self.repo.get_changeset(sto - 1)
 
     def test_branches(self):
         # TODO: Need more tests here
         # Removed (those are 'remotes' branches for cloned repo)
-        #self.assertTrue('master' in self.repo.branches)
-        #self.assertTrue('gittree' in self.repo.branches)
-        #self.assertTrue('web-branch' in self.repo.branches)
+        #assert 'master' in self.repo.branches
+        #assert 'gittree' in self.repo.branches
+        #assert 'web-branch' in self.repo.branches
         for name, id in self.repo.branches.items():
-            self.assertTrue(isinstance(
-                self.repo.get_changeset(id), GitChangeset))
+            assert isinstance(self.repo.get_changeset(id), GitChangeset)
 
     def test_tags(self):
         # TODO: Need more tests here
-        self.assertTrue('v0.1.1' in self.repo.tags)
-        self.assertTrue('v0.1.2' in self.repo.tags)
+        assert 'v0.1.1' in self.repo.tags
+        assert 'v0.1.2' in self.repo.tags
         for name, id in self.repo.tags.items():
-            self.assertTrue(isinstance(
-                self.repo.get_changeset(id), GitChangeset))
+            assert isinstance(self.repo.get_changeset(id), GitChangeset)
 
     def _test_single_changeset_cache(self, revision):
         chset = self.repo.get_changeset(revision)
-        self.assertTrue(revision in self.repo.changesets)
-        self.assertTrue(chset is self.repo.changesets[revision])
+        assert revision in self.repo.changesets
+        assert chset is self.repo.changesets[revision]
 
     def test_initial_changeset(self):
         id = self.repo.revisions[0]
         init_chset = self.repo.get_changeset(id)
-        self.assertEqual(init_chset.message, 'initial import\n')
-        self.assertEqual(init_chset.author,
-            'Marcin Kuzminski <marcin@python-blog.com>')
+        assert init_chset.message == 'initial import\n'
+        assert init_chset.author == 'Marcin Kuzminski <marcin@python-blog.com>'
         for path in ('vcs/__init__.py',
                      'vcs/backends/BaseRepository.py',
                      'vcs/backends/__init__.py'):
-            self.assertTrue(isinstance(init_chset.get_node(path), FileNode))
+            assert isinstance(init_chset.get_node(path), FileNode)
         for path in ('', 'vcs', 'vcs/backends'):
-            self.assertTrue(isinstance(init_chset.get_node(path), DirNode))
+            assert isinstance(init_chset.get_node(path), DirNode)
 
-        self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
+        with pytest.raises(NodeDoesNotExistError):
+            init_chset.get_node(path='foobar')
 
         node = init_chset.get_node('vcs/')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.DIR)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.DIR
 
         node = init_chset.get_node('vcs')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.DIR)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.DIR
 
         node = init_chset.get_node('vcs/__init__.py')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.FILE)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.FILE
 
     def test_not_existing_changeset(self):
-        self.assertRaises(RepositoryError, self.repo.get_changeset,
-            'f' * 40)
+        with pytest.raises(RepositoryError):
+            self.repo.get_changeset('f' * 40)
 
     def test_changeset10(self):
 
         chset10 = self.repo.get_changeset(self.repo.revisions[9])
-        README = """===
+        readme = """===
 VCS
 ===
 
@@ -247,23 +243,23 @@
 
 """
         node = chset10.get_node('README.rst')
-        self.assertEqual(node.kind, NodeKind.FILE)
-        self.assertEqual(node.content, README)
+        assert node.kind == NodeKind.FILE
+        assert node.content == readme
 
 
-class GitChangesetTest(unittest.TestCase):
+class TestGitChangeset(object):
 
-    def setUp(self):
+    def setup_method(self):
         self.repo = GitRepository(TEST_GIT_REPO)
 
     def test_default_changeset(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(tip, self.repo.get_changeset(None))
-        self.assertEqual(tip, self.repo.get_changeset('tip'))
+        assert tip == self.repo.get_changeset(None)
+        assert tip == self.repo.get_changeset('tip')
 
     def test_root_node(self):
         tip = self.repo.get_changeset()
-        self.assertTrue(tip.root is tip.get_node(''))
+        assert tip.root is tip.get_node('')
 
     def test_lazy_fetch(self):
         """
@@ -271,64 +267,65 @@
         the revision. This test is somewhat hard to write as order of tests
         is a key here. Written by running command after command in a shell.
         """
-        hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
-        self.assertTrue(hex in self.repo.revisions)
-        chset = self.repo.get_changeset(hex)
-        self.assertTrue(len(chset.nodes) == 0)
+        commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
+        assert commit_id in self.repo.revisions
+        chset = self.repo.get_changeset(commit_id)
+        assert len(chset.nodes) == 0
         root = chset.root
-        self.assertTrue(len(chset.nodes) == 1)
-        self.assertTrue(len(root.nodes) == 8)
+        assert len(chset.nodes) == 1
+        assert len(root.nodes) == 8
         # accessing root.nodes updates chset.nodes
-        self.assertTrue(len(chset.nodes) == 9)
+        assert len(chset.nodes) == 9
 
         docs = root.get_node('docs')
         # we haven't yet accessed anything new as docs dir was already cached
-        self.assertTrue(len(chset.nodes) == 9)
-        self.assertTrue(len(docs.nodes) == 8)
+        assert len(chset.nodes) == 9
+        assert len(docs.nodes) == 8
         # accessing docs.nodes updates chset.nodes
-        self.assertTrue(len(chset.nodes) == 17)
+        assert len(chset.nodes) == 17
 
-        self.assertTrue(docs is chset.get_node('docs'))
-        self.assertTrue(docs is root.nodes[0])
-        self.assertTrue(docs is root.dirs[0])
-        self.assertTrue(docs is chset.get_node('docs'))
+        assert docs is chset.get_node('docs')
+        assert docs is root.nodes[0]
+        assert docs is root.dirs[0]
+        assert docs is chset.get_node('docs')
 
     def test_nodes_with_changeset(self):
-        hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
-        chset = self.repo.get_changeset(hex)
+        commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
+        chset = self.repo.get_changeset(commit_id)
         root = chset.root
         docs = root.get_node('docs')
-        self.assertTrue(docs is chset.get_node('docs'))
+        assert docs is chset.get_node('docs')
         api = docs.get_node('api')
-        self.assertTrue(api is chset.get_node('docs/api'))
+        assert api is chset.get_node('docs/api')
         index = api.get_node('index.rst')
-        self.assertTrue(index is chset.get_node('docs/api/index.rst'))
-        self.assertTrue(index is chset.get_node('docs')\
-            .get_node('api')\
-            .get_node('index.rst'))
+        assert index is chset.get_node('docs/api/index.rst')
+        assert index is chset.get_node('docs') \
+                             .get_node('api') \
+                             .get_node('index.rst')
 
     def test_branch_and_tags(self):
-        """
+        # Those tests seem to show wrong results:
+        # in Git, only heads have a branch - most changesets don't
         rev0 = self.repo.revisions[0]
         chset0 = self.repo.get_changeset(rev0)
-        self.assertEqual(chset0.branch, 'master')
-        self.assertEqual(chset0.tags, [])
+        assert chset0.branch is None # should be 'master'?
+        assert chset0.branches == [] # should be 'master'?
+        assert chset0.tags == []
 
         rev10 = self.repo.revisions[10]
         chset10 = self.repo.get_changeset(rev10)
-        self.assertEqual(chset10.branch, 'master')
-        self.assertEqual(chset10.tags, [])
+        assert chset10.branch is None # should be 'master'?
+        assert chset10.branches == [] # should be 'master'?
+        assert chset10.tags == []
 
         rev44 = self.repo.revisions[44]
         chset44 = self.repo.get_changeset(rev44)
-        self.assertEqual(chset44.branch, 'web-branch')
+        assert chset44.branch is None # should be 'web-branch'?
+        assert chset44.branches == [] # should be 'web-branch'?
 
         tip = self.repo.get_changeset('tip')
-        self.assertTrue('tip' in tip.tags)
-        """
-        # Those tests would fail - branches are now going
-        # to be changed at main API in order to support git backend
-        pass
+        assert 'tip' not in tip.tags # it should be?
+        assert not tip.tags # how it is!
 
     def _test_slices(self, limit, offset):
         count = self.repo.count()
@@ -339,23 +336,23 @@
             idx += 1
             rev_id = self.repo.revisions[rev]
             if idx > limit:
-                self.fail("Exceeded limit already (getting revision %s, "
+                pytest.fail("Exceeded limit already (getting revision %s, "
                     "there are %s total revisions, offset=%s, limit=%s)"
                     % (rev_id, count, offset, limit))
-            self.assertEqual(changeset, self.repo.get_changeset(rev_id))
+            assert changeset == self.repo.get_changeset(rev_id)
         result = list(self.repo.get_changesets(limit=limit, offset=offset))
         start = offset
         end = limit and offset + limit or None
         sliced = list(self.repo[start:end])
-        self.failUnlessEqual(result, sliced,
+        pytest.failUnlessEqual(result, sliced,
             msg="Comparison failed for limit=%s, offset=%s"
             "(get_changeset returned: %s and sliced: %s"
             % (limit, offset, result, sliced))
 
     def _test_file_size(self, revision, path, size):
         node = self.repo.get_changeset(revision).get_node(path)
-        self.assertTrue(node.is_file())
-        self.assertEqual(node.size, size)
+        assert node.is_file()
+        assert node.size == size
 
     def test_file_size(self):
         to_check = (
@@ -365,7 +362,6 @@
                 'vcs/backends/hg.py', 854),
             ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
                 'setup.py', 1068),
-
             ('d955cd312c17b02143c04fa1099a352b04368118',
                 'vcs/backends/base.py', 2921),
             ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
@@ -378,7 +374,7 @@
 
     def _test_dir_size(self, revision, path, size):
         node = self.repo.get_changeset(revision).get_node(path)
-        self.assertEqual(node.size, size)
+        assert node.size == size
 
     def test_dir_size(self):
         to_check = (
@@ -390,7 +386,7 @@
             self._test_dir_size(revision, path, size)
 
     def test_repo_size(self):
-        self.assertEqual(self.repo.size, 674076)
+        assert self.repo.size == 674076
 
     def test_file_history(self):
         # we can only check if those revisions are present in the history
@@ -454,10 +450,9 @@
         for path, revs in files.items():
             node = self.repo.get_changeset(revs[0]).get_node(path)
             node_revs = [chset.raw_id for chset in node.history]
-            self.assertTrue(set(revs).issubset(set(node_revs)),
-                "We assumed that %s is subset of revisions for which file %s "
-                "has been changed, and history of that node returned: %s"
-                % (revs, path, node_revs))
+            assert set(revs).issubset(set(node_revs)), "We assumed that %s is subset of revisions for which file %s " \
+                "has been changed, and history of that node returned: %s" \
+                % (revs, path, node_revs)
 
     def test_file_annotate(self):
         files = {
@@ -540,127 +535,117 @@
 
                 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
                 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
-                self.assertEqual(l1_1, l1_2)
+                assert l1_1 == l1_2
                 l1 = l1_1
                 l2 = files[fname][rev]['changesets']
-                self.assertTrue(l1 == l2 , "The lists of revision for %s@rev %s"
-                                "from annotation list should match each other, "
-                                "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
+                assert l1 == l2, "The lists of revision for %s@rev %s" \
+                    "from annotation list should match each other, " \
+                    "got \n%s \nvs \n%s " % (fname, rev, l1, l2)
 
     def test_files_state(self):
         """
         Tests state of FileNodes.
         """
-        node = self.repo\
-            .get_changeset('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
+        node = self.repo \
+            .get_changeset('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0') \
             .get_node('vcs/utils/diffs.py')
-        self.assertTrue(node.state, NodeState.ADDED)
-        self.assertTrue(node.added)
-        self.assertFalse(node.changed)
-        self.assertFalse(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.ADDED
+        assert node.added
+        assert not node.changed
+        assert not node.not_changed
+        assert not node.removed
 
-        node = self.repo\
-            .get_changeset('33fa3223355104431402a888fa77a4e9956feb3e')\
+        node = self.repo \
+            .get_changeset('33fa3223355104431402a888fa77a4e9956feb3e') \
             .get_node('.hgignore')
-        self.assertTrue(node.state, NodeState.CHANGED)
-        self.assertFalse(node.added)
-        self.assertTrue(node.changed)
-        self.assertFalse(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.CHANGED
+        assert not node.added
+        assert node.changed
+        assert not node.not_changed
+        assert not node.removed
 
-        node = self.repo\
-            .get_changeset('e29b67bd158580fc90fc5e9111240b90e6e86064')\
+        node = self.repo \
+            .get_changeset('e29b67bd158580fc90fc5e9111240b90e6e86064') \
             .get_node('setup.py')
-        self.assertTrue(node.state, NodeState.NOT_CHANGED)
-        self.assertFalse(node.added)
-        self.assertFalse(node.changed)
-        self.assertTrue(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.NOT_CHANGED
+        assert not node.added
+        assert not node.changed
+        assert node.not_changed
+        assert not node.removed
 
         # If node has REMOVED state then trying to fetch it would raise
         # ChangesetError exception
         chset = self.repo.get_changeset(
             'fa6600f6848800641328adbf7811fd2372c02ab2')
         path = 'vcs/backends/BaseRepository.py'
-        self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
+        with pytest.raises(NodeDoesNotExistError):
+            chset.get_node(path)
         # but it would be one of ``removed`` (changeset's attribute)
-        self.assertTrue(path in [rf.path for rf in chset.removed])
+        assert path in [rf.path for rf in chset.removed]
 
         chset = self.repo.get_changeset(
             '54386793436c938cff89326944d4c2702340037d')
         changed = ['setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
             'vcs/nodes.py']
-        self.assertEqual(set(changed), set([f.path for f in chset.changed]))
+        assert set(changed) == set([f.path for f in chset.changed])
 
     def test_commit_message_is_unicode(self):
         for cs in self.repo:
-            self.assertEqual(type(cs.message), unicode)
+            assert type(cs.message) == unicode
 
     def test_changeset_author_is_unicode(self):
         for cs in self.repo:
-            self.assertEqual(type(cs.author), unicode)
+            assert type(cs.author) == unicode
 
     def test_repo_files_content_is_unicode(self):
         changeset = self.repo.get_changeset()
         for node in changeset.get_node('/'):
             if node.is_file():
-                self.assertEqual(type(node.content), unicode)
+                assert type(node.content) == unicode
 
     def test_wrong_path(self):
         # There is 'setup.py' in the root dir but not there:
         path = 'foo/bar/setup.py'
         tip = self.repo.get_changeset()
-        self.assertRaises(VCSError, tip.get_node, path)
+        with pytest.raises(VCSError):
+            tip.get_node(path)
 
     def test_author_email(self):
-        self.assertEqual('marcin@python-blog.com',
-          self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3')\
-          .author_email)
-        self.assertEqual('lukasz.balcerzak@python-center.pl',
-          self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b')\
-          .author_email)
-        self.assertEqual('none@none',
-          self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992')\
-          .author_email)
+        assert 'marcin@python-blog.com' == self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3').author_email
+        assert 'lukasz.balcerzak@python-center.pl' == self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b').author_email
+        assert '' == self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992').author_email
 
     def test_author_username(self):
-        self.assertEqual('Marcin Kuzminski',
-          self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3')\
-          .author_name)
-        self.assertEqual('Lukasz Balcerzak',
-          self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b')\
-          .author_name)
-        self.assertEqual('marcink',
-          self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992')\
-          .author_name)
+        assert 'Marcin Kuzminski' == self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3').author_name
+        assert 'Lukasz Balcerzak' == self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b').author_name
+        assert 'marcink none@none' == self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992').author_name
 
 
-class GitSpecificTest(unittest.TestCase):
+class TestGitSpecific():
 
     def test_error_is_raised_for_added_if_diff_name_status_is_wrong(self):
         repo = mock.MagicMock()
         changeset = GitChangeset(repo, 'foobar')
         changeset._diff_name_status = 'foobar'
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             changeset.added
 
     def test_error_is_raised_for_changed_if_diff_name_status_is_wrong(self):
         repo = mock.MagicMock()
         changeset = GitChangeset(repo, 'foobar')
         changeset._diff_name_status = 'foobar'
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             changeset.added
 
     def test_error_is_raised_for_removed_if_diff_name_status_is_wrong(self):
         repo = mock.MagicMock()
         changeset = GitChangeset(repo, 'foobar')
         changeset._diff_name_status = 'foobar'
-        with self.assertRaises(VCSError):
+        with pytest.raises(VCSError):
             changeset.added
 
 
-class GitSpecificWithRepoTest(_BackendTestMixin, unittest.TestCase):
+class TestGitSpecificWithRepo(_BackendTestMixin):
     backend_alias = 'git'
 
     @classmethod
@@ -689,22 +674,20 @@
 
     def test_paths_slow_traversing(self):
         cs = self.repo.get_changeset()
-        self.assertEqual(cs.get_node('foobar').get_node('static').get_node('js')
-            .get_node('admin').get_node('base.js').content, 'base')
+        assert cs.get_node('foobar').get_node('static').get_node('js').get_node('admin').get_node('base.js').content == 'base'
 
     def test_paths_fast_traversing(self):
         cs = self.repo.get_changeset()
-        self.assertEqual(cs.get_node('foobar/static/js/admin/base.js').content,
-            'base')
+        assert cs.get_node('foobar/static/js/admin/base.js').content == 'base'
 
     def test_workdir_get_branch(self):
         self.repo.run_git_command(['checkout', '-b', 'production'])
         # Regression test: one of following would fail if we don't check
         # .git/HEAD file
         self.repo.run_git_command(['checkout', 'production'])
-        self.assertEqual(self.repo.workdir.get_branch(), 'production')
+        assert self.repo.workdir.get_branch() == 'production'
         self.repo.run_git_command(['checkout', 'master'])
-        self.assertEqual(self.repo.workdir.get_branch(), 'master')
+        assert self.repo.workdir.get_branch() == 'master'
 
     def test_get_diff_runs_git_command_with_hashes(self):
         self.repo.run_git_command = mock.Mock(return_value=['', ''])
@@ -768,7 +751,7 @@
              self.repo._get_revision(0), self.repo._get_revision(1), '--', 'foo'])
 
 
-class GitRegressionTest(_BackendTestMixin, unittest.TestCase):
+class TestGitRegression(_BackendTestMixin):
     backend_alias = 'git'
 
     @classmethod
@@ -801,22 +784,22 @@
 
     def test_similar_paths(self):
         cs = self.repo.get_changeset()
-        paths = lambda *n:[x.path for x in n]
-        self.assertEqual(paths(*cs.get_nodes('bot')), ['bot/build', 'bot/templates', 'bot/__init__.py'])
-        self.assertEqual(paths(*cs.get_nodes('bot/build')), ['bot/build/migrations', 'bot/build/static', 'bot/build/templates'])
-        self.assertEqual(paths(*cs.get_nodes('bot/build/static')), ['bot/build/static/templates'])
+        paths = lambda *n: [x.path for x in n]
+        assert paths(*cs.get_nodes('bot')) == ['bot/build', 'bot/templates', 'bot/__init__.py']
+        assert paths(*cs.get_nodes('bot/build')) == ['bot/build/migrations', 'bot/build/static', 'bot/build/templates']
+        assert paths(*cs.get_nodes('bot/build/static')) == ['bot/build/static/templates']
         # this get_nodes below causes troubles !
-        self.assertEqual(paths(*cs.get_nodes('bot/build/static/templates')), ['bot/build/static/templates/f.html', 'bot/build/static/templates/f1.html'])
-        self.assertEqual(paths(*cs.get_nodes('bot/build/templates')), ['bot/build/templates/err.html', 'bot/build/templates/err2.html'])
-        self.assertEqual(paths(*cs.get_nodes('bot/templates/')), ['bot/templates/404.html', 'bot/templates/500.html'])
+        assert paths(*cs.get_nodes('bot/build/static/templates')) == ['bot/build/static/templates/f.html', 'bot/build/static/templates/f1.html']
+        assert paths(*cs.get_nodes('bot/build/templates')) == ['bot/build/templates/err.html', 'bot/build/templates/err2.html']
+        assert paths(*cs.get_nodes('bot/templates/')) == ['bot/templates/404.html', 'bot/templates/500.html']
 
 
-class GitHooksTest(unittest.TestCase):
+class TestGitHooks(object):
     """
     Tests related to hook functionality of Git repositories.
     """
 
-    def setUp(self):
+    def setup_method(self):
         # For each run we want a fresh repo.
         self.repo_directory = get_new_dir("githookrepo")
         self.repo = GitRepository(self.repo_directory, create=True)
@@ -824,7 +807,7 @@
         # Create a dictionary where keys are hook names, and values are paths to
         # them. Deduplicates code in tests a bit.
         self.hook_directory = self.repo.get_hook_location()
-        self.kallithea_hooks = {h: os.path.join(self.hook_directory, h) for h in ("pre-receive", "post-receive")}
+        self.kallithea_hooks = dict((h, os.path.join(self.hook_directory, h)) for h in ("pre-receive", "post-receive"))
 
     def test_hooks_created_if_missing(self):
         """
@@ -838,7 +821,7 @@
         ScmModel().install_git_hooks(repo=self.repo)
 
         for hook, hook_path in self.kallithea_hooks.iteritems():
-            self.assertTrue(os.path.exists(hook_path))
+            assert os.path.exists(hook_path)
 
     def test_kallithea_hooks_updated(self):
         """
@@ -853,7 +836,7 @@
 
         for hook, hook_path in self.kallithea_hooks.iteritems():
             with open(hook_path) as f:
-                self.assertNotIn("JUST_BOGUS", f.read())
+                assert "JUST_BOGUS" not in f.read()
 
     def test_custom_hooks_untouched(self):
         """
@@ -868,7 +851,7 @@
 
         for hook, hook_path in self.kallithea_hooks.iteritems():
             with open(hook_path) as f:
-                self.assertIn("CUSTOM_HOOK", f.read())
+                assert "CUSTOM_HOOK" in f.read()
 
     def test_custom_hooks_forced_update(self):
         """
@@ -883,8 +866,4 @@
 
         for hook, hook_path in self.kallithea_hooks.iteritems():
             with open(hook_path) as f:
-                self.assertIn("KALLITHEA_HOOK_VER", f.read())
-
-
-if __name__ == '__main__':
-    unittest.main()
+                assert "KALLITHEA_HOOK_VER" in f.read()
--- a/kallithea/tests/vcs/test_hg.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_hg.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,76 +1,83 @@
-
 import os
 
+import pytest
 import mock
 
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset
 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError
 from kallithea.lib.vcs.nodes import NodeKind, NodeState
+
 from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_HG_REPO_CLONE, \
-    TEST_HG_REPO_PULL
-from kallithea.lib.vcs.utils.compat import unittest
+    TEST_HG_REPO_PULL, TESTS_TMP_PATH
 
 
-class MercurialRepositoryTest(unittest.TestCase):
+class TestMercurialRepository(object):
 
     def __check_for_existing_repo(self):
         if os.path.exists(TEST_HG_REPO_CLONE):
-            self.fail('Cannot test mercurial clone repo as location %s already '
+            pytest.fail('Cannot test mercurial clone repo as location %s already '
                       'exists. You should manually remove it first.'
                       % TEST_HG_REPO_CLONE)
 
-    def setUp(self):
-        self.repo = MercurialRepository(TEST_HG_REPO)
+    def setup_method(self):
+        self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
 
     def test_wrong_repo_path(self):
-        wrong_repo_path = '/tmp/errorrepo'
-        self.assertRaises(RepositoryError, MercurialRepository, wrong_repo_path)
+        wrong_repo_path = os.path.join(TESTS_TMP_PATH, 'errorrepo')
+        with pytest.raises(RepositoryError):
+            MercurialRepository(wrong_repo_path)
 
     def test_unicode_path_repo(self):
-        self.assertRaises(VCSError,lambda:MercurialRepository(u'iShouldFail'))
+        with pytest.raises(VCSError):
+            MercurialRepository(u'iShouldFail')
 
     def test_repo_clone(self):
         self.__check_for_existing_repo()
-        repo = MercurialRepository(TEST_HG_REPO)
+        repo = MercurialRepository(safe_str(TEST_HG_REPO))
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
             src_url=TEST_HG_REPO, update_after_clone=True)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
+        assert len(repo.revisions) == len(repo_clone.revisions)
         # Checking hashes of changesets should be enough
         for changeset in repo.get_changesets():
             raw_id = changeset.raw_id
-            self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id)
+            assert raw_id == repo_clone.get_changeset(raw_id).raw_id
 
     def test_repo_clone_with_update(self):
-        repo = MercurialRepository(TEST_HG_REPO)
+        repo = MercurialRepository(safe_str(TEST_HG_REPO))
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update',
             src_url=TEST_HG_REPO, update_after_clone=True)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
+        assert len(repo.revisions) == len(repo_clone.revisions)
 
-        #check if current workdir was updated
-        self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
-                                                    + '_w_update',
-                                                    'MANIFEST.in')), True,)
+        # check if current workdir was updated
+        assert os.path.isfile(
+            os.path.join(
+                TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in'
+            )
+        )
 
     def test_repo_clone_without_update(self):
-        repo = MercurialRepository(TEST_HG_REPO)
+        repo = MercurialRepository(safe_str(TEST_HG_REPO))
         repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update',
             src_url=TEST_HG_REPO, update_after_clone=False)
-        self.assertEqual(len(repo.revisions), len(repo_clone.revisions))
-        self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \
-                                                    + '_wo_update',
-                                                    'MANIFEST.in')), False,)
+        assert len(repo.revisions) == len(repo_clone.revisions)
+        assert not os.path.isfile(
+            os.path.join(
+                TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in'
+            )
+        )
 
     def test_pull(self):
         if os.path.exists(TEST_HG_REPO_PULL):
-            self.fail('Cannot test mercurial pull command as location %s '
+            pytest.fail('Cannot test mercurial pull command as location %s '
                       'already exists. You should manually remove it first'
                       % TEST_HG_REPO_PULL)
         repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True)
-        self.assertTrue(len(self.repo.revisions) > len(repo_new.revisions))
+        assert len(self.repo.revisions) > len(repo_new.revisions)
 
         repo_new.pull(self.repo.path)
         repo_new = MercurialRepository(TEST_HG_REPO_PULL)
-        self.assertTrue(len(self.repo.revisions) == len(repo_new.revisions))
+        assert len(self.repo.revisions) == len(repo_new.revisions)
 
     def test_revisions(self):
         # there are 21 revisions at bitbucket now
@@ -95,8 +102,7 @@
                  'aa6a0de05b7612707db567078e130a6cd114a9a7',
                  'eada5a770da98ab0dd7325e29d00e0714f228d09'
                 ])
-        self.assertTrue(subset.issubset(set(self.repo.revisions)))
-
+        assert subset.issubset(set(self.repo.revisions))
 
         # check if we have the proper order of revisions
         org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545',
@@ -130,95 +136,92 @@
                 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
                 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
                 'ee87846a61c12153b51543bf860e1026c6d3dcba', ]
-        self.assertEqual(org, self.repo.revisions[:31])
+        assert org == self.repo.revisions[:31]
 
     def test_iter_slice(self):
         sliced = list(self.repo[:10])
         itered = list(self.repo)[:10]
-        self.assertEqual(sliced, itered)
+        assert sliced == itered
 
     def test_slicing(self):
-        #4 1 5 10 95
+        # 4 1 5 10 95
         for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
                                  (10, 20, 10), (5, 100, 95)]:
             revs = list(self.repo[sfrom:sto])
-            self.assertEqual(len(revs), size)
-            self.assertEqual(revs[0], self.repo.get_changeset(sfrom))
-            self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1))
+            assert len(revs) == size
+            assert revs[0] == self.repo.get_changeset(sfrom)
+            assert revs[-1] == self.repo.get_changeset(sto - 1)
 
     def test_branches(self):
         # TODO: Need more tests here
 
-        #active branches
-        self.assertTrue('default' in self.repo.branches)
-        self.assertTrue('stable' in self.repo.branches)
+        # active branches
+        assert 'default' in self.repo.branches
+        assert 'stable' in self.repo.branches
 
         # closed
-        self.assertTrue('git' in self.repo._get_branches(closed=True))
-        self.assertTrue('web' in self.repo._get_branches(closed=True))
+        assert 'git' in self.repo._get_branches(closed=True)
+        assert 'web' in self.repo._get_branches(closed=True)
 
         for name, id in self.repo.branches.items():
-            self.assertTrue(isinstance(
-                self.repo.get_changeset(id), MercurialChangeset))
+            assert isinstance(self.repo.get_changeset(id), MercurialChangeset)
 
     def test_tip_in_tags(self):
         # tip is always a tag
-        self.assertIn('tip', self.repo.tags)
+        assert 'tip' in self.repo.tags
 
     def test_tip_changeset_in_tags(self):
         tip = self.repo.get_changeset()
-        self.assertEqual(self.repo.tags['tip'], tip.raw_id)
+        assert self.repo.tags['tip'] == tip.raw_id
 
     def test_initial_changeset(self):
 
         init_chset = self.repo.get_changeset(0)
-        self.assertEqual(init_chset.message, 'initial import')
-        self.assertEqual(init_chset.author,
-            'Marcin Kuzminski <marcin@python-blog.com>')
-        self.assertEqual(sorted(init_chset._file_paths),
-            sorted([
-                'vcs/__init__.py',
-                'vcs/backends/BaseRepository.py',
-                'vcs/backends/__init__.py',
-            ])
-        )
-        self.assertEqual(sorted(init_chset._dir_paths),
-            sorted(['', 'vcs', 'vcs/backends']))
+        assert init_chset.message == 'initial import'
+        assert init_chset.author == 'Marcin Kuzminski <marcin@python-blog.com>'
+        assert sorted(init_chset._file_paths) == sorted([
+            'vcs/__init__.py',
+            'vcs/backends/BaseRepository.py',
+            'vcs/backends/__init__.py',
+        ])
 
-        self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar')
+        assert sorted(init_chset._dir_paths) == sorted(['', 'vcs', 'vcs/backends'])
+
+        with pytest.raises(NodeDoesNotExistError):
+            init_chset.get_node(path='foobar')
 
         node = init_chset.get_node('vcs/')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.DIR)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.DIR
 
         node = init_chset.get_node('vcs')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.DIR)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.DIR
 
         node = init_chset.get_node('vcs/__init__.py')
-        self.assertTrue(hasattr(node, 'kind'))
-        self.assertEqual(node.kind, NodeKind.FILE)
+        assert hasattr(node, 'kind')
+        assert node.kind == NodeKind.FILE
 
     def test_not_existing_changeset(self):
-        #rawid
-        self.assertRaises(RepositoryError, self.repo.get_changeset,
-            'abcd' * 10)
-        #shortid
-        self.assertRaises(RepositoryError, self.repo.get_changeset,
-            'erro' * 4)
-        #numeric
-        self.assertRaises(RepositoryError, self.repo.get_changeset,
-            self.repo.count() + 1)
-
+        # rawid
+        with pytest.raises(RepositoryError):
+            self.repo.get_changeset('abcd' * 10)
+        # shortid
+        with pytest.raises(RepositoryError):
+            self.repo.get_changeset('erro' * 4)
+        # numeric
+        with pytest.raises(RepositoryError):
+            self.repo.get_changeset(self.repo.count() + 1)
 
         # Small chance we ever get to this one
         revision = pow(2, 30)
-        self.assertRaises(RepositoryError, self.repo.get_changeset, revision)
+        with pytest.raises(RepositoryError):
+            self.repo.get_changeset(revision)
 
     def test_changeset10(self):
 
         chset10 = self.repo.get_changeset(10)
-        README = """===
+        readme = """===
 VCS
 ===
 
@@ -231,8 +234,8 @@
 
 """
         node = chset10.get_node('README.rst')
-        self.assertEqual(node.kind, NodeKind.FILE)
-        self.assertEqual(node.content, README)
+        assert node.kind == NodeKind.FILE
+        assert node.content == readme
 
     @mock.patch('kallithea.lib.vcs.backends.hg.repository.diffopts')
     def test_get_diff_does_not_sanitize_zero_context(self, mock_diffopts):
@@ -252,17 +255,16 @@
         mock_diffopts.assert_called_once_with(git=True, showfunc=True, ignorews=False, context=zero_context)
 
 
-class MercurialChangesetTest(unittest.TestCase):
+class TestMercurialChangeset(object):
 
-    def setUp(self):
-        self.repo = MercurialRepository(TEST_HG_REPO)
+    def setup_method(self):
+        self.repo = MercurialRepository(safe_str(TEST_HG_REPO))
 
     def _test_equality(self, changeset):
         revision = changeset.revision
-        self.assertEqual(changeset, self.repo.get_changeset(revision))
+        assert changeset == self.repo.get_changeset(revision)
 
     def test_equality(self):
-        self.setUp()
         revs = [0, 10, 20]
         changesets = [self.repo.get_changeset(rev) for rev in revs]
         for changeset in changesets:
@@ -270,13 +272,13 @@
 
     def test_default_changeset(self):
         tip = self.repo.get_changeset('tip')
-        self.assertEqual(tip, self.repo.get_changeset())
-        self.assertEqual(tip, self.repo.get_changeset(revision=None))
-        self.assertEqual(tip, list(self.repo[-1:])[0])
+        assert tip == self.repo.get_changeset()
+        assert tip == self.repo.get_changeset(revision=None)
+        assert tip == list(self.repo[-1:])[0]
 
     def test_root_node(self):
         tip = self.repo.get_changeset('tip')
-        self.assertTrue(tip.root is tip.get_node(''))
+        assert tip.root is tip.get_node('')
 
     def test_lazy_fetch(self):
         """
@@ -284,60 +286,59 @@
         the revision. This test is somewhat hard to write as order of tests
         is a key here. Written by running command after command in a shell.
         """
-        self.setUp()
         chset = self.repo.get_changeset(45)
-        self.assertTrue(len(chset.nodes) == 0)
+        assert len(chset.nodes) == 0
         root = chset.root
-        self.assertTrue(len(chset.nodes) == 1)
-        self.assertTrue(len(root.nodes) == 8)
+        assert len(chset.nodes) == 1
+        assert len(root.nodes) == 8
         # accessing root.nodes updates chset.nodes
-        self.assertTrue(len(chset.nodes) == 9)
+        assert len(chset.nodes) == 9
 
         docs = root.get_node('docs')
         # we haven't yet accessed anything new as docs dir was already cached
-        self.assertTrue(len(chset.nodes) == 9)
-        self.assertTrue(len(docs.nodes) == 8)
+        assert len(chset.nodes) == 9
+        assert len(docs.nodes) == 8
         # accessing docs.nodes updates chset.nodes
-        self.assertTrue(len(chset.nodes) == 17)
+        assert len(chset.nodes) == 17
 
-        self.assertTrue(docs is chset.get_node('docs'))
-        self.assertTrue(docs is root.nodes[0])
-        self.assertTrue(docs is root.dirs[0])
-        self.assertTrue(docs is chset.get_node('docs'))
+        assert docs is chset.get_node('docs')
+        assert docs is root.nodes[0]
+        assert docs is root.dirs[0]
+        assert docs is chset.get_node('docs')
 
     def test_nodes_with_changeset(self):
-        self.setUp()
         chset = self.repo.get_changeset(45)
         root = chset.root
         docs = root.get_node('docs')
-        self.assertTrue(docs is chset.get_node('docs'))
+        assert docs is chset.get_node('docs')
         api = docs.get_node('api')
-        self.assertTrue(api is chset.get_node('docs/api'))
+        assert api is chset.get_node('docs/api')
         index = api.get_node('index.rst')
-        self.assertTrue(index is chset.get_node('docs/api/index.rst'))
-        self.assertTrue(index is chset.get_node('docs')\
-            .get_node('api')\
-            .get_node('index.rst'))
+        assert index is chset.get_node('docs/api/index.rst')
+        assert index is chset.get_node('docs').get_node('api').get_node('index.rst')
 
     def test_branch_and_tags(self):
         chset0 = self.repo.get_changeset(0)
-        self.assertEqual(chset0.branch, 'default')
-        self.assertEqual(chset0.tags, [])
+        assert chset0.branch == 'default'
+        assert chset0.branches == ['default']
+        assert chset0.tags == []
 
         chset10 = self.repo.get_changeset(10)
-        self.assertEqual(chset10.branch, 'default')
-        self.assertEqual(chset10.tags, [])
+        assert chset10.branch == 'default'
+        assert chset10.branches == ['default']
+        assert chset10.tags == []
 
         chset44 = self.repo.get_changeset(44)
-        self.assertEqual(chset44.branch, 'web')
+        assert chset44.branch == 'web'
+        assert chset44.branches == ['web']
 
         tip = self.repo.get_changeset('tip')
-        self.assertTrue('tip' in tip.tags)
+        assert 'tip' in tip.tags
 
     def _test_file_size(self, revision, path, size):
         node = self.repo.get_changeset(revision).get_node(path)
-        self.assertTrue(node.is_file())
-        self.assertEqual(node.size, size)
+        assert node.is_file()
+        assert node.size == size
 
     def test_file_size(self):
         to_check = (
@@ -354,8 +355,8 @@
 
     def _test_dir_size(self, revision, path, size):
         node = self.repo.get_changeset(revision).get_node(path)
-        self.assertFalse(node.is_file())
-        self.assertEqual(node.size, size)
+        assert not node.is_file()
+        assert node.size == size
 
     def test_dir_size(self):
         to_check = (
@@ -367,7 +368,7 @@
             self._test_dir_size(revision, path, size)
 
     def test_repo_size(self):
-        self.assertEqual(self.repo.size, 682421)
+        assert self.repo.size == 682421
 
     def test_file_history(self):
         # we can only check if those revisions are present in the history
@@ -385,10 +386,10 @@
             tip = self.repo.get_changeset(revs[-1])
             node = tip.get_node(path)
             node_revs = [chset.revision for chset in node.history]
-            self.assertTrue(set(revs).issubset(set(node_revs)),
-                "We assumed that %s is subset of revisions for which file %s "
-                "has been changed, and history of that node returned: %s"
-                % (revs, path, node_revs))
+            assert set(revs).issubset(set(node_revs)), \
+                "We assumed that %s is subset of revisions for which file %s " \
+                "has been changed, and history of that node returned: %s" \
+                % (revs, path, node_revs)
 
     def test_file_annotate(self):
         files = {
@@ -427,12 +428,12 @@
                 cs = self.repo.get_changeset(rev)
                 l1_1 = [x[1] for x in cs.get_file_annotate(fname)]
                 l1_2 = [x[2]().raw_id for x in cs.get_file_annotate(fname)]
-                self.assertEqual(l1_1, l1_2)
+                assert l1_1 == l1_2
                 l1 = l1_2 = [x[2]().revision for x in cs.get_file_annotate(fname)]
                 l2 = files[fname][rev]['changesets']
-                self.assertTrue(l1 == l2 , "The lists of revision for %s@rev%s"
-                                "from annotation list should match each other,"
-                                "got \n%s \nvs \n%s " % (fname, rev, l1, l2))
+                assert l1 == l2, "The lists of revision for %s@rev%s" \
+                    "from annotation list should match each other," \
+                    "got \n%s \nvs \n%s " % (fname, rev, l1, l2)
 
     def test_changeset_state(self):
         """
@@ -464,9 +465,9 @@
         removed = set(['docs/api.rst'])
 
         chset64 = self.repo.get_changeset('46ad32a4f974')
-        self.assertEqual(set((node.path for node in chset64.added)), added)
-        self.assertEqual(set((node.path for node in chset64.changed)), changed)
-        self.assertEqual(set((node.path for node in chset64.removed)), removed)
+        assert set((node.path for node in chset64.added)) == added
+        assert set((node.path for node in chset64.changed)) == changed
+        assert set((node.path for node in chset64.removed)) == removed
 
         # rev b090f22d27d6:
         # hg st --rev b090f22d27d6
@@ -474,27 +475,30 @@
         #    added:   20
         #    removed: 1
         chset88 = self.repo.get_changeset('b090f22d27d6')
-        self.assertEqual(set((node.path for node in chset88.added)), set())
-        self.assertEqual(set((node.path for node in chset88.changed)),
-            set(['.hgignore']))
-        self.assertEqual(set((node.path for node in chset88.removed)), set())
-#
+        assert set((node.path for node in chset88.added)) == set()
+        assert set((node.path for node in chset88.changed)) == set(['.hgignore'])
+        assert set((node.path for node in chset88.removed)) == set()
+
         # 85:
         #    added:   2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
         #    changed: 4 ['vcs/web/simplevcs/models.py', ...]
         #    removed: 1 ['vcs/utils/web.py']
         chset85 = self.repo.get_changeset(85)
-        self.assertEqual(set((node.path for node in chset85.added)), set([
+        assert set((node.path for node in chset85.added)) == set([
             'vcs/utils/diffs.py',
-            'vcs/web/simplevcs/views/diffs.py']))
-        self.assertEqual(set((node.path for node in chset85.changed)), set([
+            'vcs/web/simplevcs/views/diffs.py'
+        ])
+
+        assert set((node.path for node in chset85.changed)) == set([
             'vcs/web/simplevcs/models.py',
             'vcs/web/simplevcs/utils.py',
             'vcs/web/simplevcs/views/__init__.py',
             'vcs/web/simplevcs/views/repository.py',
-            ]))
-        self.assertEqual(set((node.path for node in chset85.removed)),
-            set(['vcs/utils/web.py']))
+        ])
+
+        assert set((node.path for node in chset85.removed)) == set([
+            'vcs/utils/web.py'
+        ])
 
 
     def test_files_state(self):
@@ -503,85 +507,88 @@
         """
         chset = self.repo.get_changeset(85)
         node = chset.get_node('vcs/utils/diffs.py')
-        self.assertTrue(node.state, NodeState.ADDED)
-        self.assertTrue(node.added)
-        self.assertFalse(node.changed)
-        self.assertFalse(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.ADDED
+        assert node.added
+        assert not node.changed
+        assert not node.not_changed
+        assert not node.removed
 
         chset = self.repo.get_changeset(88)
         node = chset.get_node('.hgignore')
-        self.assertTrue(node.state, NodeState.CHANGED)
-        self.assertFalse(node.added)
-        self.assertTrue(node.changed)
-        self.assertFalse(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.CHANGED
+        assert not node.added
+        assert node.changed
+        assert not node.not_changed
+        assert not node.removed
 
         chset = self.repo.get_changeset(85)
         node = chset.get_node('setup.py')
-        self.assertTrue(node.state, NodeState.NOT_CHANGED)
-        self.assertFalse(node.added)
-        self.assertFalse(node.changed)
-        self.assertTrue(node.not_changed)
-        self.assertFalse(node.removed)
+        assert node.state, NodeState.NOT_CHANGED
+        assert not node.added
+        assert not node.changed
+        assert node.not_changed
+        assert not node.removed
 
         # If node has REMOVED state then trying to fetch it would raise
         # ChangesetError exception
         chset = self.repo.get_changeset(2)
         path = 'vcs/backends/BaseRepository.py'
-        self.assertRaises(NodeDoesNotExistError, chset.get_node, path)
+        with pytest.raises(NodeDoesNotExistError):
+            chset.get_node(path)
         # but it would be one of ``removed`` (changeset's attribute)
-        self.assertTrue(path in [rf.path for rf in chset.removed])
+        assert path in [rf.path for rf in chset.removed]
 
     def test_commit_message_is_unicode(self):
         for cm in self.repo:
-            self.assertEqual(type(cm.message), unicode)
+            assert type(cm.message) == unicode
 
     def test_changeset_author_is_unicode(self):
         for cm in self.repo:
-            self.assertEqual(type(cm.author), unicode)
+            assert type(cm.author) == unicode
 
     def test_repo_files_content_is_unicode(self):
         test_changeset = self.repo.get_changeset(100)
         for node in test_changeset.get_node('/'):
             if node.is_file():
-                self.assertEqual(type(node.content), unicode)
+                assert type(node.content) == unicode
 
     def test_wrong_path(self):
         # There is 'setup.py' in the root dir but not there:
         path = 'foo/bar/setup.py'
-        self.assertRaises(VCSError, self.repo.get_changeset().get_node, path)
-
+        with pytest.raises(VCSError):
+            self.repo.get_changeset().get_node(path)
 
     def test_archival_file(self):
-        #TODO:
+        # TODO:
         pass
 
     def test_archival_as_generator(self):
-        #TODO:
+        # TODO:
         pass
 
     def test_archival_wrong_kind(self):
         tip = self.repo.get_changeset()
-        self.assertRaises(VCSError, tip.fill_archive, kind='error')
+        with pytest.raises(VCSError):
+            tip.fill_archive(kind='error')
 
     def test_archival_empty_prefix(self):
-        #TODO:
+        # TODO:
         pass
 
-
     def test_author_email(self):
-        self.assertEqual('marcin@python-blog.com',
-                         self.repo.get_changeset('b986218ba1c9').author_email)
-        self.assertEqual('lukasz.balcerzak@python-center.pl',
-                         self.repo.get_changeset('3803844fdbd3').author_email)
-        self.assertEqual('',
-                         self.repo.get_changeset('84478366594b').author_email)
+        assert 'marcin@python-blog.com' == self.repo.get_changeset('b986218ba1c9').author_email
+        assert 'lukasz.balcerzak@python-center.pl' == self.repo.get_changeset('3803844fdbd3').author_email
+        assert '' == self.repo.get_changeset('84478366594b').author_email
 
     def test_author_username(self):
-        self.assertEqual('Marcin Kuzminski',
-                         self.repo.get_changeset('b986218ba1c9').author_name)
-        self.assertEqual('Lukasz Balcerzak',
-                         self.repo.get_changeset('3803844fdbd3').author_name)
-        self.assertEqual('marcink',
-                         self.repo.get_changeset('84478366594b').author_name)
+        assert 'Marcin Kuzminski' == self.repo.get_changeset('b986218ba1c9').author_name
+        assert 'Lukasz Balcerzak' == self.repo.get_changeset('3803844fdbd3').author_name
+        assert 'marcink' == self.repo.get_changeset('84478366594b').author_name
+
+    def test_successors(self):
+        init_chset = self.repo.get_changeset(0)
+        assert init_chset.successors == []
+
+    def test_predecessors(self):
+        init_chset = self.repo.get_changeset(0)
+        assert len(init_chset.predecessors) == 0
--- a/kallithea/tests/vcs/test_inmemchangesets.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_inmemchangesets.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,4 +1,4 @@
-# encoding: utf8
+# encoding: utf-8
 """
 Tests so called "in memory changesets" commit API of vcs.
 """
@@ -6,8 +6,9 @@
 import time
 import datetime
 
+import pytest
+
 from kallithea.lib import vcs
-from kallithea.tests.vcs.conf import SCM_TESTS, get_new_dir
 from kallithea.lib.vcs.exceptions import EmptyRepositoryError
 from kallithea.lib.vcs.exceptions import NodeAlreadyAddedError
 from kallithea.lib.vcs.exceptions import NodeAlreadyExistsError
@@ -17,37 +18,27 @@
 from kallithea.lib.vcs.exceptions import NodeNotChangedError
 from kallithea.lib.vcs.nodes import DirNode
 from kallithea.lib.vcs.nodes import FileNode
-from kallithea.lib.vcs.utils.compat import unittest
 from kallithea.lib.vcs.utils import safe_unicode
 
+from kallithea.tests.vcs.base import _BackendTestMixin
+
 
-class InMemoryChangesetTestMixin(object):
-    """
-    This is a backend independent test case class which should be created
-    with ``type`` method.
-
-    It is required to set following attributes at subclass:
+class InMemoryChangesetTestMixin(_BackendTestMixin):
 
-    - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
-    - ``repo_path``: path to the repository which would be created for set of
-      tests
-    """
-
-    def get_backend(self):
-        return vcs.get_backend(self.backend_alias)
-
-    def setUp(self):
-        Backend = self.get_backend()
-        self.repo_path = get_new_dir(str(time.time()))
-        self.repo = Backend(self.repo_path, create=True)
-        self.imc = self.repo.in_memory_changeset
-        self.nodes = [
+    @classmethod
+    def _get_commits(cls):
+        # Note: this is slightly different than the regular _get_commits methods
+        # as we don't actually return any commits. The creation of commits is
+        # handled in the tests themselves.
+        cls.nodes = [
             FileNode('foobar', content='Foo & bar'),
             FileNode('foobar2', content='Foo & bar, doubled!'),
             FileNode('foo bar with spaces', content=''),
             FileNode('foo/bar/baz', content='Inside'),
             FileNode('foo/bar/file.bin', content='\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x03\x00\xfe\xff\t\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\xfe\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'),
         ]
+        commits = []
+        return commits
 
     def test_add(self):
         rev_count = len(self.repo.revisions)
@@ -60,14 +51,17 @@
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
-        self.assertEqual(changeset, newtip)
-        self.assertEqual(rev_count + 1, len(self.repo.revisions))
-        self.assertEqual(newtip.message, message)
-        self.assertEqual(newtip.author, author)
-        self.assertTrue(not any((self.imc.added, self.imc.changed,
-            self.imc.removed)))
+        assert changeset == newtip
+        assert rev_count + 1 == len(self.repo.revisions)
+        assert newtip.message == message
+        assert newtip.author == author
+        assert not any((
+            self.imc.added,
+            self.imc.changed,
+            self.imc.removed
+        ))
         for node in to_add:
-            self.assertEqual(newtip.get_node(node.path).content, node.content)
+            assert newtip.get_node(node.path).content == node.content
 
     def test_add_in_bulk(self):
         rev_count = len(self.repo.revisions)
@@ -79,23 +73,26 @@
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
-        self.assertEqual(changeset, newtip)
-        self.assertEqual(rev_count + 1, len(self.repo.revisions))
-        self.assertEqual(newtip.message, message)
-        self.assertEqual(newtip.author, author)
-        self.assertTrue(not any((self.imc.added, self.imc.changed,
-            self.imc.removed)))
+        assert changeset == newtip
+        assert rev_count + 1 == len(self.repo.revisions)
+        assert newtip.message == message
+        assert newtip.author == author
+        assert not any((
+            self.imc.added,
+            self.imc.changed,
+            self.imc.removed
+        ))
         for node in to_add:
-            self.assertEqual(newtip.get_node(node.path).content, node.content)
+            assert newtip.get_node(node.path).content == node.content
 
     def test_add_actually_adds_all_nodes_at_second_commit_too(self):
         self.imc.add(FileNode('foo/bar/image.png', content='\0'))
         self.imc.add(FileNode('foo/README.txt', content='readme!'))
         changeset = self.imc.commit(u'Initial', u'joe.doe@example.com')
-        self.assertTrue(isinstance(changeset.get_node('foo'), DirNode))
-        self.assertTrue(isinstance(changeset.get_node('foo/bar'), DirNode))
-        self.assertEqual(changeset.get_node('foo/bar/image.png').content, '\0')
-        self.assertEqual(changeset.get_node('foo/README.txt').content, 'readme!')
+        assert isinstance(changeset.get_node('foo'), DirNode)
+        assert isinstance(changeset.get_node('foo/bar'), DirNode)
+        assert changeset.get_node('foo/bar/image.png').content == '\0'
+        assert changeset.get_node('foo/README.txt').content == 'readme!'
 
         # commit some more files again
         to_add = [
@@ -107,11 +104,11 @@
         ]
         self.imc.add(*to_add)
         changeset = self.imc.commit(u'Another', u'joe.doe@example.com')
-        self.assertEqual(changeset.get_node('foo/bar/foobaz/bar').content, 'foo')
-        self.assertEqual(changeset.get_node('foo/bar/another/bar').content, 'foo')
-        self.assertEqual(changeset.get_node('foo/baz.txt').content, 'foo')
-        self.assertEqual(changeset.get_node('foobar/foobaz/file').content, 'foo')
-        self.assertEqual(changeset.get_node('foobar/barbaz').content, 'foo')
+        changeset.get_node('foo/bar/foobaz/bar').content == 'foo'
+        changeset.get_node('foo/bar/another/bar').content == 'foo'
+        changeset.get_node('foo/baz.txt').content == 'foo'
+        changeset.get_node('foobar/foobaz/file').content == 'foo'
+        changeset.get_node('foobar/barbaz').content == 'foo'
 
     def test_add_non_ascii_files(self):
         rev_count = len(self.repo.revisions)
@@ -126,28 +123,32 @@
         changeset = self.imc.commit(message=message, author=author)
 
         newtip = self.repo.get_changeset()
-        self.assertEqual(changeset, newtip)
-        self.assertEqual(rev_count + 1, len(self.repo.revisions))
-        self.assertEqual(newtip.message, message)
-        self.assertEqual(newtip.author, author)
-        self.assertTrue(not any((self.imc.added, self.imc.changed,
-            self.imc.removed)))
+        assert changeset == newtip
+        assert rev_count + 1 == len(self.repo.revisions)
+        assert newtip.message == message
+        assert newtip.author == author
+        assert not any((
+            self.imc.added,
+            self.imc.changed,
+            self.imc.removed
+        ))
         for node in to_add:
-            self.assertEqual(newtip.get_node(node.path).content, node.content)
+            assert newtip.get_node(node.path).content == node.content
 
     def test_add_raise_already_added(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
-        self.assertRaises(NodeAlreadyAddedError, self.imc.add, node)
+        with pytest.raises(NodeAlreadyAddedError):
+            self.imc.add(node)
 
     def test_check_integrity_raise_already_exist(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
         self.imc.commit(message=u'Added foobar', author=unicode(self))
         self.imc.add(node)
-        self.assertRaises(NodeAlreadyExistsError, self.imc.commit,
-            message='new message',
-            author=str(self))
+        with pytest.raises(NodeAlreadyExistsError):
+            self.imc.commit(message='new message',
+                            author=str(self))
 
     def test_change(self):
         self.imc.add(FileNode('foo/bar/baz', content='foo'))
@@ -160,10 +161,9 @@
         self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com')
 
         newtip = self.repo.get_changeset()
-        self.assertNotEqual(tip, newtip)
-        self.assertNotEqual(tip.id, newtip.id)
-        self.assertEqual(newtip.get_node('foo/bar/baz').content,
-                        'My **changed** content')
+        assert tip != newtip
+        assert tip.id != newtip.id
+        assert newtip.get_node('foo/bar/baz').content == 'My **changed** content'
 
     def test_change_non_ascii(self):
         to_add = [
@@ -187,17 +187,16 @@
                         u'joe.doe@example.com')
 
         newtip = self.repo.get_changeset()
-        self.assertNotEqual(tip, newtip)
-        self.assertNotEqual(tip.id, newtip.id)
+        assert tip != newtip
+        assert tip.id != newtip.id
 
-        self.assertEqual(newtip.get_node('żółwik/zwierzątko').content,
-                         'My **changed** content')
-        self.assertEqual(newtip.get_node('żółwik/zwierzątko_uni').content,
-                         'My **changed** content')
+        assert newtip.get_node('żółwik/zwierzątko').content == 'My **changed** content'
+        assert newtip.get_node('żółwik/zwierzątko_uni').content == 'My **changed** content'
 
     def test_change_raise_empty_repository(self):
         node = FileNode('foobar')
-        self.assertRaises(EmptyRepositoryError, self.imc.change, node)
+        with pytest.raises(EmptyRepositoryError):
+            self.imc.change(node)
 
     def test_check_integrity_change_raise_node_does_not_exist(self):
         node = FileNode('foobar', content='baz')
@@ -205,9 +204,8 @@
         self.imc.commit(message=u'Added foobar', author=unicode(self))
         node = FileNode('not-foobar', content='')
         self.imc.change(node)
-        self.assertRaises(NodeDoesNotExistError, self.imc.commit,
-            message='Changed not existing node',
-            author=str(self))
+        with pytest.raises(NodeDoesNotExistError):
+            self.imc.commit(message='Changed not existing node', author=str(self))
 
     def test_change_raise_node_already_changed(self):
         node = FileNode('foobar', content='baz')
@@ -215,37 +213,42 @@
         self.imc.commit(message=u'Added foobar', author=unicode(self))
         node = FileNode('foobar', content='more baz')
         self.imc.change(node)
-        self.assertRaises(NodeAlreadyChangedError, self.imc.change, node)
+        with pytest.raises(NodeAlreadyChangedError):
+            self.imc.change(node)
 
     def test_check_integrity_change_raise_node_not_changed(self):
         self.test_add()  # Performs first commit
 
         node = FileNode(self.nodes[0].path, content=self.nodes[0].content)
         self.imc.change(node)
-        self.assertRaises(NodeNotChangedError, self.imc.commit,
-            message=u'Trying to mark node as changed without touching it',
-            author=unicode(self))
+        with pytest.raises(NodeNotChangedError):
+            self.imc.commit(
+                message=u'Trying to mark node as changed without touching it',
+                author=unicode(self)
+            )
 
     def test_change_raise_node_already_removed(self):
         node = FileNode('foobar', content='baz')
         self.imc.add(node)
         self.imc.commit(message=u'Added foobar', author=unicode(self))
         self.imc.remove(FileNode('foobar'))
-        self.assertRaises(NodeAlreadyRemovedError, self.imc.change, node)
+        with pytest.raises(NodeAlreadyRemovedError):
+            self.imc.change(node)
 
     def test_remove(self):
         self.test_add()  # Performs first commit
 
         tip = self.repo.get_changeset()
         node = self.nodes[0]
-        self.assertEqual(node.content, tip.get_node(node.path).content)
+        assert node.content == tip.get_node(node.path).content
         self.imc.remove(node)
         self.imc.commit(message=u'Removed %s' % node.path, author=unicode(self))
 
         newtip = self.repo.get_changeset()
-        self.assertNotEqual(tip, newtip)
-        self.assertNotEqual(tip.id, newtip.id)
-        self.assertRaises(NodeDoesNotExistError, newtip.get_node, node.path)
+        assert tip != newtip
+        assert tip.id != newtip.id
+        with pytest.raises(NodeDoesNotExistError):
+            newtip.get_node(node.path)
 
     def test_remove_last_file_from_directory(self):
         node = FileNode('omg/qwe/foo/bar', content='foobar')
@@ -254,44 +257,54 @@
 
         self.imc.remove(node)
         tip = self.imc.commit(u'removed', u'joe doe')
-        self.assertRaises(NodeDoesNotExistError, tip.get_node, 'omg/qwe/foo/bar')
+        with pytest.raises(NodeDoesNotExistError):
+            tip.get_node('omg/qwe/foo/bar')
 
     def test_remove_raise_node_does_not_exist(self):
         self.imc.remove(self.nodes[0])
-        self.assertRaises(NodeDoesNotExistError, self.imc.commit,
-            message='Trying to remove node at empty repository',
-            author=str(self))
+        with pytest.raises(NodeDoesNotExistError):
+            self.imc.commit(
+                message='Trying to remove node at empty repository',
+                author=str(self)
+            )
 
     def test_check_integrity_remove_raise_node_does_not_exist(self):
         self.test_add()  # Performs first commit
 
         node = FileNode('no-such-file')
         self.imc.remove(node)
-        self.assertRaises(NodeDoesNotExistError, self.imc.commit,
-            message=u'Trying to remove not existing node',
-            author=unicode(self))
+        with pytest.raises(NodeDoesNotExistError):
+            self.imc.commit(
+                message=u'Trying to remove not existing node',
+                author=unicode(self)
+            )
 
     def test_remove_raise_node_already_removed(self):
         self.test_add() # Performs first commit
 
         node = FileNode(self.nodes[0].path)
         self.imc.remove(node)
-        self.assertRaises(NodeAlreadyRemovedError, self.imc.remove, node)
+        with pytest.raises(NodeAlreadyRemovedError):
+            self.imc.remove(node)
 
     def test_remove_raise_node_already_changed(self):
         self.test_add()  # Performs first commit
 
         node = FileNode(self.nodes[0].path, content='Bending time')
         self.imc.change(node)
-        self.assertRaises(NodeAlreadyChangedError, self.imc.remove, node)
+        with pytest.raises(NodeAlreadyChangedError):
+            self.imc.remove(node)
 
     def test_reset(self):
         self.imc.add(FileNode('foo', content='bar'))
         #self.imc.change(FileNode('baz', content='new'))
         #self.imc.remove(FileNode('qwe'))
         self.imc.reset()
-        self.assertTrue(not any((self.imc.added, self.imc.changed,
-            self.imc.removed)))
+        assert not any((
+            self.imc.added,
+            self.imc.changed,
+            self.imc.removed
+        ))
 
     def test_multiple_commits(self):
         N = 3  # number of commits to perform
@@ -302,94 +315,28 @@
             node = FileNode(fname, content=content)
             self.imc.add(node)
             commit = self.imc.commit(u"Commit no. %s" % (x + 1), author=u'vcs')
-            self.assertTrue(last != commit)
+            assert last != commit
             last = commit
 
         # Check commit number for same repo
-        self.assertEqual(len(self.repo.revisions), N)
+        assert len(self.repo.revisions) == N
 
         # Check commit number for recreated repo
-        backend = self.get_backend()
-        repo = backend(self.repo_path)
-        self.assertEqual(len(repo.revisions), N)
+        assert len(self.repo.revisions) == N
 
     def test_date_attr(self):
         node = FileNode('foobar.txt', content='Foobared!')
         self.imc.add(node)
         date = datetime.datetime(1985, 1, 30, 1, 45)
         commit = self.imc.commit(u"Committed at time when I was born ;-)",
-            author=u'lb', date=date)
+            author=u'lb <lb@example.com>', date=date)
 
-        self.assertEqual(commit.date, date)
+        assert commit.date == date
 
 
-class BackendBaseTestCase(unittest.TestCase):
-    """
-    Base test class for tests which requires repository.
-    """
-    backend_alias = 'hg'
-    commits = [
-        {
-            'message': 'Initial commit',
-            'author': 'Joe Doe <joe.doe@example.com>',
-            'date': datetime.datetime(2010, 1, 1, 20),
-            'added': [
-                FileNode('foobar', content='Foobar'),
-                FileNode('foobar2', content='Foobar II'),
-                FileNode('foo/bar/baz', content='baz here!'),
-            ],
-        },
-    ]
-
-    def get_backend(self):
-        return vcs.get_backend(self.backend_alias)
-
-    def get_commits(self):
-        """
-        Returns list of commits which builds repository for each tests.
-        """
-        if hasattr(self, 'commits'):
-            return self.commits
-
-    def get_new_repo_path(self):
-        """
-        Returns newly created repository's directory.
-        """
-        backend = self.get_backend()
-        key = '%s-%s' % (self.backend_alias, str(time.time()))
-        repo_path = get_new_dir(key)
-        return repo_path
-
-    def setUp(self):
-        Backend = self.get_backend()
-        self.backend_class = Backend
-        self.repo_path = self.get_new_repo_path()
-        self.repo = Backend(self.repo_path, create=True)
-        self.imc = self.repo.in_memory_changeset
-
-        for commit in self.get_commits():
-            for node in commit.get('added', []):
-                self.imc.add(FileNode(node.path, content=node.content))
-            for node in commit.get('changed', []):
-                self.imc.change(FileNode(node.path, content=node.content))
-            for node in commit.get('removed', []):
-                self.imc.remove(FileNode(node.path))
-            self.imc.commit(message=unicode(commit['message']),
-                            author=unicode(commit['author']),
-                date=commit['date'])
-
-        self.tip = self.repo.get_changeset()
+class TestGitInMemoryChangeset(InMemoryChangesetTestMixin):
+    backend_alias = 'git'
 
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s in memory changeset test' % alias).title().split())
-    bases = (InMemoryChangesetTestMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
-
-
-if __name__ == '__main__':
-    unittest.main()
+class TestHgInMemoryChangeset(InMemoryChangesetTestMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_nodes.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_nodes.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,55 +1,61 @@
+import stat
+import mimetypes
 
-import stat
+import pytest
+
 from kallithea.lib.vcs.nodes import DirNode
 from kallithea.lib.vcs.nodes import FileNode
 from kallithea.lib.vcs.nodes import Node
 from kallithea.lib.vcs.nodes import NodeError
 from kallithea.lib.vcs.nodes import NodeKind
-from kallithea.lib.vcs.utils.compat import unittest
 
 
-class NodeBasicTest(unittest.TestCase):
+class TestNodeBasic(object):
 
     def test_init(self):
         """
-        Cannot innitialize Node objects with path with slash at the beginning.
+        Cannot initialize Node objects with path with slash at the beginning.
         """
         wrong_paths = (
             '/foo',
             '/foo/bar'
         )
         for path in wrong_paths:
-            self.assertRaises(NodeError, Node, path, NodeKind.FILE)
+            with pytest.raises(NodeError):
+                Node(path, NodeKind.FILE)
 
         wrong_paths = (
             '/foo/',
             '/foo/bar/'
         )
         for path in wrong_paths:
-            self.assertRaises(NodeError, Node, path, NodeKind.DIR)
+            with pytest.raises(NodeError):
+                Node(path, NodeKind.DIR)
 
     def test_name(self):
         node = Node('', NodeKind.DIR)
-        self.assertEqual(node.name, '')
+        assert node.name == ''
 
         node = Node('path', NodeKind.FILE)
-        self.assertEqual(node.name, 'path')
+        assert node.name == 'path'
 
         node = Node('path/', NodeKind.DIR)
-        self.assertEqual(node.name, 'path')
+        assert node.name == 'path'
 
         node = Node('some/path', NodeKind.FILE)
-        self.assertEqual(node.name, 'path')
+        assert node.name == 'path'
 
         node = Node('some/path/', NodeKind.DIR)
-        self.assertEqual(node.name, 'path')
+        assert node.name == 'path'
 
     def test_root_node(self):
-        self.assertRaises(NodeError, Node, '', NodeKind.FILE)
+        with pytest.raises(NodeError):
+            Node('', NodeKind.FILE)
 
     def test_kind_setter(self):
         node = Node('', NodeKind.DIR)
-        self.assertRaises(NodeError, setattr, node, 'kind', NodeKind.FILE)
+        with pytest.raises(NodeError):
+            setattr(node, 'kind', NodeKind.FILE)
 
     def _test_parent_path(self, node_path, expected_parent_path):
         """
@@ -57,11 +63,10 @@
         """
         node = Node(node_path, NodeKind.DIR)
         parent_path = node.get_parent_path()
-        self.assertTrue(parent_path.endswith('/') or \
-            node.is_root() and parent_path == '')
-        self.assertEqual(parent_path, expected_parent_path,
-            "Node's path is %r and parent path is %r but should be %r"
-            % (node.path, parent_path, expected_parent_path))
+        assert parent_path.endswith('/') or node.is_root() and parent_path == ''
+        assert parent_path == expected_parent_path, \
+            "Node's path is %r and parent path is %r but should be %r" \
+            % (node.path, parent_path, expected_parent_path)
 
     def test_parent_path(self):
         test_paths = (
@@ -76,9 +81,10 @@
     '''
     def _test_trailing_slash(self, path):
         if not path.endswith('/'):
-            self.fail("Trailing slash tests needs paths to end with slash")
+            pytest.fail("Trailing slash tests needs paths to end with slash")
         for kind in NodeKind.FILE, NodeKind.DIR:
-            self.assertRaises(NodeError, Node, path=path, kind=kind)
+            with pytest.raises(NodeError):
+                Node(path=path, kind=kind)
 
     def test_trailing_slash(self):
         for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'):
@@ -87,20 +93,22 @@
 
     def test_is_file(self):
         node = Node('any', NodeKind.FILE)
-        self.assertTrue(node.is_file())
+        assert node.is_file()
 
         node = FileNode('any')
-        self.assertTrue(node.is_file())
-        self.assertRaises(AttributeError, getattr, node, 'nodes')
+        assert node.is_file()
+        with pytest.raises(AttributeError):
+            getattr(node, 'nodes')
 
     def test_is_dir(self):
         node = Node('any_dir', NodeKind.DIR)
-        self.assertTrue(node.is_dir())
+        assert node.is_dir()
 
         node = DirNode('any_dir')
 
-        self.assertTrue(node.is_dir())
-        self.assertRaises(NodeError, getattr, node, 'content')
+        assert node.is_dir()
+        with pytest.raises(NodeError):
+            getattr(node, 'content')
 
     def test_dir_node_iter(self):
         nodes = [
@@ -120,63 +128,61 @@
         Without link to changeset nodes should raise NodeError.
         """
         node = FileNode('anything')
-        self.assertRaises(NodeError, getattr, node, 'state')
+        with pytest.raises(NodeError):
+            getattr(node, 'state')
         node = DirNode('anything')
-        self.assertRaises(NodeError, getattr, node, 'state')
+        with pytest.raises(NodeError):
+            getattr(node, 'state')
 
     def test_file_node_stat(self):
         node = FileNode('foobar', 'empty... almost')
         mode = node.mode  # default should be 0100644
-        self.assertTrue(mode & stat.S_IRUSR)
-        self.assertTrue(mode & stat.S_IWUSR)
-        self.assertTrue(mode & stat.S_IRGRP)
-        self.assertTrue(mode & stat.S_IROTH)
-        self.assertFalse(mode & stat.S_IWGRP)
-        self.assertFalse(mode & stat.S_IWOTH)
-        self.assertFalse(mode & stat.S_IXUSR)
-        self.assertFalse(mode & stat.S_IXGRP)
-        self.assertFalse(mode & stat.S_IXOTH)
+        assert mode & stat.S_IRUSR
+        assert mode & stat.S_IWUSR
+        assert mode & stat.S_IRGRP
+        assert mode & stat.S_IROTH
+        assert not mode & stat.S_IWGRP
+        assert not mode & stat.S_IWOTH
+        assert not mode & stat.S_IXUSR
+        assert not mode & stat.S_IXGRP
+        assert not mode & stat.S_IXOTH
 
     def test_file_node_is_executable(self):
         node = FileNode('foobar', 'empty... almost', mode=0100755)
-        self.assertTrue(node.is_executable)
+        assert node.is_executable
 
         node = FileNode('foobar', 'empty... almost', mode=0100500)
-        self.assertTrue(node.is_executable)
+        assert node.is_executable
 
         node = FileNode('foobar', 'empty... almost', mode=0100644)
-        self.assertFalse(node.is_executable)
+        assert not node.is_executable
 
     def test_mimetype(self):
         py_node = FileNode('test.py')
         tar_node = FileNode('test.tar.gz')
 
-        ext = 'CustomExtension'
-
         my_node2 = FileNode('myfile2')
-        my_node2._mimetype = [ext]
+        my_node2._content = 'foobar'
 
         my_node3 = FileNode('myfile3')
-        my_node3._mimetype = [ext,ext]
+        my_node3._content = '\0foobar'
 
-        self.assertEqual(py_node.mimetype,'text/x-python')
-        self.assertEqual(py_node.get_mimetype(),('text/x-python',None))
+        assert py_node.mimetype == mimetypes.guess_type(py_node.name)[0]
+        assert py_node.get_mimetype() == mimetypes.guess_type(py_node.name)
+
+        assert tar_node.mimetype == mimetypes.guess_type(tar_node.name)[0]
+        assert tar_node.get_mimetype() == mimetypes.guess_type(tar_node.name)
 
-        self.assertEqual(tar_node.mimetype,'application/x-tar')
-        self.assertEqual(tar_node.get_mimetype(),('application/x-tar','gzip'))
-
-        self.assertRaises(NodeError,my_node2.get_mimetype)
+        assert my_node2.mimetype == 'text/plain'
+        assert my_node2.get_mimetype() == ('text/plain', None)
 
-        self.assertEqual(my_node3.mimetype,ext)
-        self.assertEqual(my_node3.get_mimetype(),[ext,ext])
+        assert my_node3.mimetype == 'application/octet-stream'
+        assert my_node3.get_mimetype() == ('application/octet-stream', None)
 
-class NodeContentTest(unittest.TestCase):
+
+class TestNodeContent(object):
 
     def test_if_binary(self):
         data = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?"\x14j?\xa2M\x7fB\x14F\x9aQ?&\x842?\x0b\x89"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?=\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq"Sw.\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$"q[\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?\x9f\x8cE??x\x94??\r\xbdtoJU5"0N\x10U?\x00??V\t\x02\x9f\x81?U?\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&"?\xb7ZP \x0c<?O\xa5\t}\xb8?\x99\xa6?\x87?\x1di|/\xa0??0\xbe\x1fp?d&\x1a\xad\x95\x8a\x07?\t*\x10??b:?d?.\x13C\x8a?\x12\xbe\xbf\x8e?{???\x08?\x80\xa7\x13+d\x13>J?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J\x0bV"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u\xb2?1\xbe|/\x92M@\xa2!F?\xa9>"\r<DT?>\x92\x8e?>\x9a9Qv\x127?a\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00IEND\xaeB`\x82"""
         filenode = FileNode('calendar.png', content=data)
-        self.assertTrue(filenode.is_binary)
-
-
-if __name__ == '__main__':
-    unittest.main()
+        assert filenode.is_binary
--- a/kallithea/tests/vcs/test_repository.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_repository.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,10 +1,12 @@
 import datetime
+
+import pytest
+
+from kallithea.lib.vcs.nodes import FileNode
+from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
+
 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
-from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
+from kallithea.tests.vcs import TEST_USER_CONFIG_FILE
 
 
 class RepositoryBaseTest(_BackendTestMixin):
@@ -15,34 +17,38 @@
         return super(RepositoryBaseTest, cls)._get_commits()[:1]
 
     def test_get_config_value(self):
-        self.assertEqual(self.repo.get_config_value('universal', 'foo',
-            TEST_USER_CONFIG_FILE), 'bar')
+        assert self.repo.get_config_value('universal', 'foo', TEST_USER_CONFIG_FILE) == 'bar'
 
     def test_get_config_value_defaults_to_None(self):
-        self.assertEqual(self.repo.get_config_value('universal', 'nonexist',
-            TEST_USER_CONFIG_FILE), None)
+        assert self.repo.get_config_value('universal', 'nonexist', TEST_USER_CONFIG_FILE) == None
 
     def test_get_user_name(self):
-        self.assertEqual(self.repo.get_user_name(TEST_USER_CONFIG_FILE),
-            'Foo Bar')
+        assert self.repo.get_user_name(TEST_USER_CONFIG_FILE) == 'Foo Bar'
 
     def test_get_user_email(self):
-        self.assertEqual(self.repo.get_user_email(TEST_USER_CONFIG_FILE),
-            'foo.bar@example.com')
+        assert self.repo.get_user_email(TEST_USER_CONFIG_FILE) == 'foo.bar@example.com'
 
     def test_repo_equality(self):
-        self.assertTrue(self.repo == self.repo)
+        assert self.repo == self.repo
 
     def test_repo_equality_broken_object(self):
         import copy
         _repo = copy.copy(self.repo)
         delattr(_repo, 'path')
-        self.assertTrue(self.repo != _repo)
+        assert self.repo != _repo
 
     def test_repo_equality_other_object(self):
         class dummy(object):
             path = self.repo.path
-        self.assertTrue(self.repo != dummy())
+        assert self.repo != dummy()
+
+
+class TestGitRepositoryBase(RepositoryBaseTest):
+    backend_alias = 'git'
+
+
+class TestHgRepositoryBase(RepositoryBaseTest):
+    backend_alias = 'hg'
 
 
 class RepositoryGetDiffTest(_BackendTestMixin):
@@ -91,7 +97,7 @@
         return commits
 
     def test_raise_for_wrong(self):
-        with self.assertRaises(ChangesetDoesNotExistError):
+        with pytest.raises(ChangesetDoesNotExistError):
             self.repo.get_diff('a' * 40, 'b' * 40)
 
     def test_glob_patterns_in_filename_do_not_raise_exception(self):
@@ -100,12 +106,12 @@
         diff = self.repo.get_diff(revs[2], revs[3], path='README{') # should not raise
 
 
-class GitRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
+class TestGitRepositoryGetDiff(RepositoryGetDiffTest):
     backend_alias = 'git'
 
     def test_initial_commit_diff(self):
         initial_rev = self.repo.revisions[0]
-        self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == '''diff --git a/foobar b/foobar
 new file mode 100644
 index 0000000000000000000000000000000000000000..f6ea0495187600e7b2288c8ac19c5886383a4632
 --- /dev/null
@@ -121,11 +127,11 @@
 @@ -0,0 +1 @@
 +foobar2
 \ No newline at end of file
-''')
+'''
 
     def test_second_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[0], revs[1]) == '''diff --git a/foobar b/foobar
 index f6ea0495187600e7b2288c8ac19c5886383a4632..389865bb681b358c9b102d79abd8d5f941e96551 100644
 --- a/foobar
 +++ b/foobar
@@ -142,11 +148,11 @@
 @@ -0,0 +1 @@
 +foobar3
 \ No newline at end of file
-''')
+'''
 
     def test_third_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[1], revs[2]) == '''diff --git a/foobar b/foobar
 deleted file mode 100644
 index 389865bb681b358c9b102d79abd8d5f941e96551..0000000000000000000000000000000000000000
 --- a/foobar
@@ -164,11 +170,11 @@
 +FOOBAR
 +FOOBAR
 +FOOBAR
-''')
+'''
 
     def test_fourth_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[2], revs[3]), '''diff --git a/README{ b/README{
+        assert self.repo.get_diff(revs[2], revs[3]) == '''diff --git a/README{ b/README{
 new file mode 100644
 index 0000000000000000000000000000000000000000..cdc0c1b5d234feedb37bbac19cd1b6442061102d
 --- /dev/null
@@ -176,15 +182,15 @@
 @@ -0,0 +1 @@
 +Strangely-named README file
 \ No newline at end of file
-''')
+'''
 
 
-class HgRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase):
+class TestHgRepositoryGetDiff(RepositoryGetDiffTest):
     backend_alias = 'hg'
 
     def test_initial_commit_diff(self):
         initial_rev = self.repo.revisions[0]
-        self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev) == '''diff --git a/foobar b/foobar
 new file mode 100644
 --- /dev/null
 +++ b/foobar
@@ -198,11 +204,11 @@
 @@ -0,0 +1,1 @@
 +foobar2
 \ No newline at end of file
-''')
+'''
 
     def test_second_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[0], revs[1]) == '''diff --git a/foobar b/foobar
 --- a/foobar
 +++ b/foobar
 @@ -1,1 +1,1 @@
@@ -217,11 +223,11 @@
 @@ -0,0 +1,1 @@
 +foobar3
 \ No newline at end of file
-''')
+'''
 
     def test_third_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar
+        assert self.repo.get_diff(revs[1], revs[2]) == '''diff --git a/foobar b/foobar
 deleted file mode 100644
 --- a/foobar
 +++ /dev/null
@@ -237,28 +243,15 @@
 +FOOBAR
 +FOOBAR
 +FOOBAR
-''')
+'''
 
     def test_fourth_changeset_diff(self):
         revs = self.repo.revisions
-        self.assertEqual(self.repo.get_diff(revs[2], revs[3]), '''diff --git a/README{ b/README{
+        assert self.repo.get_diff(revs[2], revs[3]) == '''diff --git a/README{ b/README{
 new file mode 100644
 --- /dev/null
 +++ b/README{
 @@ -0,0 +1,1 @@
 +Strangely-named README file
 \ No newline at end of file
-''')
-
-
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = alias.capitalize() + RepositoryBaseTest.__name__
-    bases = (RepositoryBaseTest, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
-
-if __name__ == '__main__':
-    unittest.main()
+'''
--- a/kallithea/tests/vcs/test_tags.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_tags.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,9 @@
+import pytest
+
+from kallithea.lib.vcs.exceptions import TagAlreadyExistError
+from kallithea.lib.vcs.exceptions import TagDoesNotExistError
 
 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):
@@ -13,20 +13,20 @@
         tagsize = len(self.repo.tags)
         tag = self.repo.tag('last-commit', 'joe', tip.raw_id)
 
-        self.assertEqual(len(self.repo.tags), tagsize + 1)
+        assert len(self.repo.tags) == tagsize + 1
         for top, dirs, files in tip.walk():
-            self.assertEqual(top, tag.get_node(top.path))
+            assert top == tag.get_node(top.path)
 
     def test_tag_already_exist(self):
         tip = self.repo.get_changeset()
         self.repo.tag('last-commit', 'joe', tip.raw_id)
 
-        self.assertRaises(TagAlreadyExistError,
-            self.repo.tag, 'last-commit', 'joe', tip.raw_id)
+        with pytest.raises(TagAlreadyExistError):
+            self.repo.tag('last-commit', 'joe', tip.raw_id)
 
         chset = self.repo.get_changeset(0)
-        self.assertRaises(TagAlreadyExistError,
-            self.repo.tag, 'last-commit', 'jane', chset.raw_id)
+        with pytest.raises(TagAlreadyExistError):
+            self.repo.tag('last-commit', 'jane', chset.raw_id)
 
     def test_remove_tag(self):
         tip = self.repo.get_changeset()
@@ -34,27 +34,20 @@
         tagsize = len(self.repo.tags)
 
         self.repo.remove_tag('last-commit', user='evil joe')
-        self.assertEqual(len(self.repo.tags), tagsize - 1)
+        assert len(self.repo.tags) == tagsize - 1
 
     def test_remove_tag_which_does_not_exist(self):
-        self.assertRaises(TagDoesNotExistError,
-            self.repo.remove_tag, 'last-commit', user='evil joe')
+        with pytest.raises(TagDoesNotExistError):
+            self.repo.remove_tag('last-commit', user='evil joe')
 
     def test_name_with_slash(self):
         self.repo.tag('19/10/11', 'joe')
-        self.assertTrue('19/10/11' in self.repo.tags)
-        self.repo.tag('11', 'joe')
-        self.assertTrue('11' in self.repo.tags)
-
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s tags test' % alias).title().split())
-    bases = (TagsTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+        assert '19/10/11' in self.repo.tags
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestGitTags(TagsTestCaseMixin):
+    backend_alias = 'git'
+
+
+class TestHgTags(TagsTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/tests/vcs/test_utils.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_utils.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,11 +1,13 @@
+# -*- coding: utf-8 -*-
 
 import os
 import mock
 import time
 import shutil
-import tempfile
 import datetime
-from kallithea.lib.vcs.utils.compat import unittest
+
+import pytest
+
 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
 from kallithea.lib.vcs.utils.helpers import get_dict_for_attrs
 from kallithea.lib.vcs.utils.helpers import get_scm
@@ -17,10 +19,10 @@
 from kallithea.lib.vcs.utils.paths import get_user_home
 from kallithea.lib.vcs.exceptions import VCSError
 
-from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_GIT_REPO, TEST_TMP_PATH
+from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_GIT_REPO, TESTS_TMP_PATH
 
 
-class PathsTest(unittest.TestCase):
+class TestPaths(object):
 
     def _test_get_dirs_for_path(self, path, expected):
         """
@@ -28,9 +30,8 @@
         """
         expected = sorted(expected)
         result = sorted(get_dirs_for_path(path))
-        self.assertEqual(result, expected,
-            msg="%s != %s which was expected result for path %s"
-            % (result, expected, path))
+        assert result == expected, \
+            "%s != %s which was expected result for path %s" % (result, expected, path)
 
     def test_get_dirs_for_path(self):
         path = 'foo/bar/baz/file'
@@ -42,148 +43,141 @@
         for path, expected in paths_and_results:
             self._test_get_dirs_for_path(path, expected)
 
-
     def test_get_scm(self):
-        self.assertEqual(('hg', TEST_HG_REPO), get_scm(TEST_HG_REPO))
-        self.assertEqual(('git', TEST_GIT_REPO), get_scm(TEST_GIT_REPO))
+        assert ('hg', TEST_HG_REPO) == get_scm(TEST_HG_REPO)
+        assert ('git', TEST_GIT_REPO) == get_scm(TEST_GIT_REPO)
 
     def test_get_two_scms_for_path(self):
-        multialias_repo_path = os.path.join(TEST_TMP_PATH, 'hg-git-repo-2')
+        multialias_repo_path = os.path.join(TESTS_TMP_PATH, 'hg-git-repo-2')
         if os.path.isdir(multialias_repo_path):
             shutil.rmtree(multialias_repo_path)
 
         os.mkdir(multialias_repo_path)
 
-        self.assertRaises(VCSError, get_scm, multialias_repo_path)
+        with pytest.raises(VCSError):
+            get_scm(multialias_repo_path)
 
     def test_get_scm_error_path(self):
-        self.assertRaises(VCSError, get_scm, 'err')
+        with pytest.raises(VCSError):
+            get_scm('err')
 
     def test_get_scms_for_path(self):
-        dirpath = tempfile.gettempdir()
-        new = os.path.join(dirpath, 'vcs-scms-for-path-%s' % time.time())
+        new = os.path.join(TESTS_TMP_PATH, 'vcs-scms-for-path-%s' % time.time())
         os.mkdir(new)
-        self.assertEqual(get_scms_for_path(new), [])
+        assert get_scms_for_path(new) == []
 
         os.mkdir(os.path.join(new, '.tux'))
-        self.assertEqual(get_scms_for_path(new), [])
+        assert get_scms_for_path(new) == []
 
         os.mkdir(os.path.join(new, '.git'))
-        self.assertEqual(set(get_scms_for_path(new)), set(['git']))
+        assert set(get_scms_for_path(new)) == set(['git'])
 
         os.mkdir(os.path.join(new, '.hg'))
-        self.assertEqual(set(get_scms_for_path(new)), set(['git', 'hg']))
+        assert set(get_scms_for_path(new)) == set(['git', 'hg'])
 
 
-class TestParseChangesets(unittest.TestCase):
+class TestParseChangesets(object):
 
     def test_main_is_returned_correctly(self):
-        self.assertEqual(parse_changesets('123456'), {
+        assert parse_changesets('123456') == {
             'start': None,
             'main': '123456',
             'end': None,
-        })
+        }
 
     def test_start_is_returned_correctly(self):
-        self.assertEqual(parse_changesets('aaabbb..'), {
+        assert parse_changesets('aaabbb..') == {
             'start': 'aaabbb',
             'main': None,
             'end': None,
-        })
+        }
 
     def test_end_is_returned_correctly(self):
-        self.assertEqual(parse_changesets('..cccddd'), {
+        assert parse_changesets('..cccddd') == {
             'start': None,
             'main': None,
             'end': 'cccddd',
-        })
+        }
 
     def test_that_two_or_three_dots_are_allowed(self):
         text1 = 'a..b'
         text2 = 'a...b'
-        self.assertEqual(parse_changesets(text1), parse_changesets(text2))
+        assert parse_changesets(text1) == parse_changesets(text2)
 
     def test_that_input_is_stripped_first(self):
         text1 = 'a..bb'
         text2 = '  a..bb\t\n\t '
-        self.assertEqual(parse_changesets(text1), parse_changesets(text2))
+        assert parse_changesets(text1) == parse_changesets(text2)
 
     def test_that_exception_is_raised(self):
         text = '123456.789012' # single dot is not recognized
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             parse_changesets(text)
 
     def test_non_alphanumeric_raises_exception(self):
-        with self.assertRaises(ValueError):
+        with pytest.raises(ValueError):
             parse_changesets('aaa@bbb')
 
 
-class TestParseDatetime(unittest.TestCase):
+class TestParseDatetime(object):
 
     def test_datetime_text(self):
-        self.assertEqual(parse_datetime('2010-04-07 21:29:41'),
-            datetime.datetime(2010, 4, 7, 21, 29, 41))
+        assert parse_datetime('2010-04-07 21:29:41') == datetime.datetime(2010, 4, 7, 21, 29, 41)
 
     def test_no_seconds(self):
-        self.assertEqual(parse_datetime('2010-04-07 21:29'),
-            datetime.datetime(2010, 4, 7, 21, 29))
+        assert parse_datetime('2010-04-07 21:29') == datetime.datetime(2010, 4, 7, 21, 29)
 
     def test_date_only(self):
-        self.assertEqual(parse_datetime('2010-04-07'),
-            datetime.datetime(2010, 4, 7))
+        assert parse_datetime('2010-04-07') == datetime.datetime(2010, 4, 7)
 
     def test_another_format(self):
-        self.assertEqual(parse_datetime('04/07/10 21:29:41'),
-            datetime.datetime(2010, 4, 7, 21, 29, 41))
+        assert parse_datetime('04/07/10 21:29:41') == datetime.datetime(2010, 4, 7, 21, 29, 41)
 
     def test_now(self):
-        self.assertTrue(parse_datetime('now') - datetime.datetime.now() <
-            datetime.timedelta(seconds=1))
+        assert parse_datetime('now') - datetime.datetime.now() < datetime.timedelta(seconds=1)
 
     def test_today(self):
         today = datetime.date.today()
-        self.assertEqual(parse_datetime('today'),
-            datetime.datetime(*today.timetuple()[:3]))
+        assert parse_datetime('today') == datetime.datetime(*today.timetuple()[:3])
 
     def test_yesterday(self):
         yesterday = datetime.date.today() - datetime.timedelta(days=1)
-        self.assertEqual(parse_datetime('yesterday'),
-            datetime.datetime(*yesterday.timetuple()[:3]))
+        assert parse_datetime('yesterday') == datetime.datetime(*yesterday.timetuple()[:3])
 
     def test_tomorrow(self):
         tomorrow = datetime.date.today() + datetime.timedelta(days=1)
         args = tomorrow.timetuple()[:3] + (23, 59, 59)
-        self.assertEqual(parse_datetime('tomorrow'), datetime.datetime(*args))
+        assert parse_datetime('tomorrow') == datetime.datetime(*args)
 
     def test_days(self):
         timestamp = datetime.datetime.today() - datetime.timedelta(days=3)
         args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
         expected = datetime.datetime(*args)
-        self.assertEqual(parse_datetime('3d'), expected)
-        self.assertEqual(parse_datetime('3 d'), expected)
-        self.assertEqual(parse_datetime('3 day'), expected)
-        self.assertEqual(parse_datetime('3 days'), expected)
+        assert parse_datetime('3d') == expected
+        assert parse_datetime('3 d') == expected
+        assert parse_datetime('3 day') == expected
+        assert parse_datetime('3 days') == expected
 
     def test_weeks(self):
         timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7)
         args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
         expected = datetime.datetime(*args)
-        self.assertEqual(parse_datetime('3w'), expected)
-        self.assertEqual(parse_datetime('3 w'), expected)
-        self.assertEqual(parse_datetime('3 week'), expected)
-        self.assertEqual(parse_datetime('3 weeks'), expected)
+        assert parse_datetime('3w') == expected
+        assert parse_datetime('3 w') == expected
+        assert parse_datetime('3 week') == expected
+        assert parse_datetime('3 weeks') == expected
 
     def test_mixed(self):
         timestamp = datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3)
         args = timestamp.timetuple()[:3] + (0, 0, 0, 0)
         expected = datetime.datetime(*args)
-        self.assertEqual(parse_datetime('2w3d'), expected)
-        self.assertEqual(parse_datetime('2w 3d'), expected)
-        self.assertEqual(parse_datetime('2w 3 days'), expected)
-        self.assertEqual(parse_datetime('2 weeks 3 days'), expected)
+        assert parse_datetime('2w3d') == expected
+        assert parse_datetime('2w 3d') == expected
+        assert parse_datetime('2w 3 days') == expected
+        assert parse_datetime('2 weeks 3 days') == expected
 
 
-class TestAuthorExtractors(unittest.TestCase):
+class TestAuthorExtractors(object):
     TEST_AUTHORS = [("Username Last'o'Name <username@example.com>",
                     ("Username Last'o'Name", "username@example.com")),
                   ("Username Last'o'Name Spaces < username@example.com >",
@@ -204,21 +198,20 @@
                    ('justname', '')),
                   ('Mr Double Name withemail@example.com ',
                    ('Mr Double Name', 'withemail@example.com')),
+                  (u'John Doe <джондо à éẋàṁṗłê.ç°ḿ>',
+                   (u'John Doe <\u0434\u0436\u043e\u043d\u0434\u043e \xe0 \xe9\u1e8b\xe0\u1e41\u1e57\u0142\xea.\xe7\xb0\u1e3f>', '')),
                   ]
 
     def test_author_email(self):
-
         for test_str, result in self.TEST_AUTHORS:
-            self.assertEqual(result[1], author_email(test_str))
-
+            assert result[1] == author_email(test_str)
 
     def test_author_name(self):
-
         for test_str, result in self.TEST_AUTHORS:
-            self.assertEqual(result[0], author_name(test_str))
+            assert result[0] == author_name(test_str)
 
 
-class TestGetDictForAttrs(unittest.TestCase):
+class TestGetDictForAttrs(object):
 
     def test_returned_dict_has_expected_attrs(self):
         obj = mock.Mock()
@@ -229,23 +222,22 @@
         obj.date = datetime.datetime(2010, 12, 31)
         obj.count = 1001
 
-        self.assertEqual(get_dict_for_attrs(obj, ['CONST', 'foo', 'attrs',
-            'date', 'count']), {
+        assert get_dict_for_attrs(obj, ['CONST', 'foo', 'attrs', 'date', 'count']) ==  {
             'CONST': True,
             'foo': 'aaa',
             'attrs': {'foo': 'bar'},
             'date': datetime.datetime(2010, 12, 31),
             'count': 1001,
-        })
+        }
 
 
-class TestGetTotalSeconds(unittest.TestCase):
+class TestGetTotalSeconds(object):
 
     def assertTotalSecondsEqual(self, timedelta, expected_seconds):
         result = get_total_seconds(timedelta)
-        self.assertEqual(result, expected_seconds,
-            "We computed %s seconds for %s but expected %s"
-            % (result, timedelta, expected_seconds))
+        assert result == expected_seconds, \
+            "We computed %s seconds for %s but expected %s" \
+            % (result, timedelta, expected_seconds)
 
     def test_get_total_seconds_returns_proper_value(self):
         self.assertTotalSecondsEqual(datetime.timedelta(seconds=1001), 1001)
@@ -254,25 +246,21 @@
         self.assertTotalSecondsEqual(datetime.timedelta(seconds=50.65), 50.65)
 
 
-class TestGetUserHome(unittest.TestCase):
+class TestGetUserHome(object):
 
     @mock.patch.object(os, 'environ', {})
     def test_defaults_to_none(self):
-        self.assertEqual(get_user_home(), '')
+        assert get_user_home() == ''
 
     @mock.patch.object(os, 'environ', {'HOME': '/home/foobar'})
     def test_unix_like(self):
-        self.assertEqual(get_user_home(), '/home/foobar')
+        assert get_user_home() == '/home/foobar'
 
     @mock.patch.object(os, 'environ', {'USERPROFILE': '/Users/foobar'})
     def test_windows_like(self):
-        self.assertEqual(get_user_home(), '/Users/foobar')
+        assert get_user_home() == '/Users/foobar'
 
     @mock.patch.object(os, 'environ', {'HOME': '/home/foobar',
         'USERPROFILE': '/Users/foobar'})
     def test_prefers_home_over_userprofile(self):
-        self.assertEqual(get_user_home(), '/home/foobar')
-
-
-if __name__ == '__main__':
-    unittest.main()
+        assert get_user_home() == '/home/foobar'
--- a/kallithea/tests/vcs/test_utils_filesize.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_utils_filesize.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,25 +1,19 @@
-
 from kallithea.lib.vcs.utils.filesize import filesizeformat
-from kallithea.lib.vcs.utils.compat import unittest
 
 
-class TestFilesizeformat(unittest.TestCase):
+class TestFilesizeformat(object):
 
     def test_bytes(self):
-        self.assertEqual(filesizeformat(10), '10 B')
+        assert filesizeformat(10) == '10 B'
 
     def test_kilobytes(self):
-        self.assertEqual(filesizeformat(1024 * 2), '2 KB')
+        assert filesizeformat(1024 * 2) == '2 KB'
 
     def test_megabytes(self):
-        self.assertEqual(filesizeformat(1024 * 1024 * 2.3), '2.3 MB')
+        assert filesizeformat(1024 * 1024 * 2.3) == '2.3 MB'
 
     def test_gigabytes(self):
-        self.assertEqual(filesizeformat(1024 * 1024 * 1024 * 12.92), '12.92 GB')
+        assert filesizeformat(1024 * 1024 * 1024 * 12.92) == '12.92 GB'
 
-    def test_that_function_respects_sep_paramtere(self):
-        self.assertEqual(filesizeformat(1, ''), '1B')
-
-
-if __name__ == '__main__':
-    unittest.main()
+    def test_that_function_respects_sep_parameter(self):
+        assert filesizeformat(1, '') == '1B'
--- a/kallithea/tests/vcs/test_vcs.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_vcs.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,80 +1,83 @@
-
 import os
 import shutil
 
+import pytest
+
+from kallithea.lib.utils2 import safe_str
 from kallithea.lib.vcs import VCSError, get_repo, get_backend
 from kallithea.lib.vcs.backends.hg import MercurialRepository
-from kallithea.lib.vcs.utils.compat import unittest
-from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_GIT_REPO, TEST_TMP_PATH
+
+from kallithea.tests.vcs.conf import TEST_HG_REPO, TEST_GIT_REPO, TESTS_TMP_PATH
 
 
-
-class VCSTest(unittest.TestCase):
+class TestVCS(object):
     """
     Tests for main module's methods.
     """
 
     def test_get_backend(self):
         hg = get_backend('hg')
-        self.assertEqual(hg, MercurialRepository)
+        assert hg == MercurialRepository
 
     def test_alias_detect_hg(self):
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(path)
-        self.assertEqual('hg',repo.alias)
+        repo = backend(safe_str(path))
+        assert 'hg' == repo.alias
 
     def test_alias_detect_git(self):
         alias = 'git'
         path = TEST_GIT_REPO
         backend = get_backend(alias)
-        repo = backend(path)
-        self.assertEqual('git',repo.alias)
+        repo = backend(safe_str(path))
+        assert 'git' == repo.alias
 
     def test_wrong_alias(self):
         alias = 'wrong_alias'
-        self.assertRaises(VCSError, get_backend, alias)
+        with pytest.raises(VCSError):
+            get_backend(alias)
 
     def test_get_repo(self):
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(path)
+        repo = backend(safe_str(path))
 
-        self.assertEqual(repo.__class__, get_repo(path, alias).__class__)
-        self.assertEqual(repo.path, get_repo(path, alias).path)
+        assert repo.__class__ == get_repo(safe_str(path), alias).__class__
+        assert repo.path == get_repo(safe_str(path), alias).path
 
     def test_get_repo_autoalias_hg(self):
         alias = 'hg'
         path = TEST_HG_REPO
         backend = get_backend(alias)
-        repo = backend(path)
+        repo = backend(safe_str(path))
 
-        self.assertEqual(repo.__class__, get_repo(path).__class__)
-        self.assertEqual(repo.path, get_repo(path).path)
+        assert repo.__class__ == get_repo(safe_str(path)).__class__
+        assert repo.path == get_repo(safe_str(path)).path
 
     def test_get_repo_autoalias_git(self):
         alias = 'git'
         path = TEST_GIT_REPO
         backend = get_backend(alias)
-        repo = backend(path)
+        repo = backend(safe_str(path))
 
-        self.assertEqual(repo.__class__, get_repo(path).__class__)
-        self.assertEqual(repo.path, get_repo(path).path)
-
+        assert repo.__class__ == get_repo(safe_str(path)).__class__
+        assert repo.path == get_repo(safe_str(path)).path
 
     def test_get_repo_err(self):
-        blank_repo_path = os.path.join(TEST_TMP_PATH, 'blank-error-repo')
+        blank_repo_path = os.path.join(TESTS_TMP_PATH, 'blank-error-repo')
         if os.path.isdir(blank_repo_path):
             shutil.rmtree(blank_repo_path)
 
         os.mkdir(blank_repo_path)
-        self.assertRaises(VCSError, get_repo, blank_repo_path)
-        self.assertRaises(VCSError, get_repo, blank_repo_path + 'non_existing')
+        with pytest.raises(VCSError):
+            get_repo(blank_repo_path)
+        with pytest.raises(VCSError):
+            get_repo(blank_repo_path + 'non_existing')
 
     def test_get_repo_multialias(self):
-        multialias_repo_path = os.path.join(TEST_TMP_PATH, 'hg-git-repo')
+        multialias_repo_path = os.path.join(TESTS_TMP_PATH, 'hg-git-repo')
         if os.path.isdir(multialias_repo_path):
             shutil.rmtree(multialias_repo_path)
 
@@ -82,4 +85,5 @@
 
         os.mkdir(os.path.join(multialias_repo_path, '.git'))
         os.mkdir(os.path.join(multialias_repo_path, '.hg'))
-        self.assertRaises(VCSError, get_repo, multialias_repo_path)
+        with pytest.raises(VCSError):
+            get_repo(multialias_repo_path)
--- a/kallithea/tests/vcs/test_workdirs.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/kallithea/tests/vcs/test_workdirs.py	Sun Mar 31 21:28:56 2019 +0200
@@ -1,9 +1,10 @@
+import datetime
 
-import datetime
+import pytest
+
 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.conf import SCM_TESTS
 
 
 class WorkdirTestCaseMixin(_BackendTestMixin):
@@ -37,8 +38,7 @@
         return commits
 
     def test_get_branch_for_default_branch(self):
-        self.assertEqual(self.repo.workdir.get_branch(),
-            self.repo.DEFAULT_BRANCH_NAME)
+        assert self.repo.workdir.get_branch() == self.repo.DEFAULT_BRANCH_NAME
 
     def test_get_branch_after_adding_one(self):
         self.imc.add(FileNode('docs/index.txt',
@@ -48,7 +48,7 @@
             author=u'joe',
             branch='foobar',
         )
-        self.assertEqual(self.repo.workdir.get_branch(), self.default_branch)
+        assert self.repo.workdir.get_branch() == self.default_branch
 
     def test_get_changeset(self):
         old_head = self.repo.get_changeset()
@@ -59,39 +59,33 @@
             author=u'joe',
             branch='foobar',
         )
-        self.assertEqual(self.repo.workdir.get_branch(), self.default_branch)
+        assert self.repo.workdir.get_branch() == self.default_branch
         self.repo.workdir.checkout_branch('foobar')
-        self.assertEqual(self.repo.workdir.get_changeset(), head)
+        assert self.repo.workdir.get_changeset() == head
 
-        # Make sure that old head is still there after update to defualt branch
+        # Make sure that old head is still there after update to default branch
         self.repo.workdir.checkout_branch(self.default_branch)
-        self.assertEqual(self.repo.workdir.get_changeset(), old_head)
+        assert self.repo.workdir.get_changeset() == old_head
 
     def test_checkout_branch(self):
         from kallithea.lib.vcs.exceptions import BranchDoesNotExistError
         # first, 'foobranch' does not exist.
-        self.assertRaises(BranchDoesNotExistError, self.repo.workdir.checkout_branch,
-                          branch='foobranch')
+        with pytest.raises(BranchDoesNotExistError):
+            self.repo.workdir.checkout_branch(branch='foobranch')
         # create new branch 'foobranch'.
         self.imc.add(FileNode('file1', content='blah'))
         self.imc.commit(message=u'asd', author=u'john', branch='foobranch')
         # go back to the default branch
         self.repo.workdir.checkout_branch()
-        self.assertEqual(self.repo.workdir.get_branch(), self.backend_class.DEFAULT_BRANCH_NAME)
+        assert self.repo.workdir.get_branch() == self.backend_class.DEFAULT_BRANCH_NAME
         # checkout 'foobranch'
         self.repo.workdir.checkout_branch('foobranch')
-        self.assertEqual(self.repo.workdir.get_branch(), 'foobranch')
+        assert self.repo.workdir.get_branch() == 'foobranch'
 
 
-# For each backend create test case class
-for alias in SCM_TESTS:
-    attrs = {
-        'backend_alias': alias,
-    }
-    cls_name = ''.join(('%s branch test' % alias).title().split())
-    bases = (WorkdirTestCaseMixin, unittest.TestCase)
-    globals()[cls_name] = type(cls_name, bases, attrs)
+class TestGitBranch(WorkdirTestCaseMixin):
+    backend_alias = 'git'
 
 
-if __name__ == '__main__':
-    unittest.main()
+class TestHgBranch(WorkdirTestCaseMixin):
+    backend_alias = 'hg'
--- a/kallithea/websetup.py	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +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.websetup
-~~~~~~~~~~~~~~~~~~
-
-Weboperations and setup for kallithea
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Dec 11, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-
-from kallithea.config.environment import load_environment
-from kallithea.lib.db_manage import DbManage
-from kallithea.model.meta import Session
-
-
-log = logging.getLogger(__name__)
-
-
-def setup_app(command, conf, vars):
-    """Place any commands to setup kallithea here"""
-    dbconf = conf['sqlalchemy.db1.url']
-    dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'],
-                        tests=False, cli_args=command.options.__dict__)
-    dbmanage.create_tables(override=True)
-    dbmanage.set_db_version()
-    opts = dbmanage.config_prompt(None)
-    dbmanage.create_settings(opts)
-    dbmanage.create_default_user()
-    dbmanage.admin_prompt()
-    dbmanage.create_permissions()
-    dbmanage.populate_default_permissions()
-    Session().commit()
-    load_environment(conf.global_conf, conf.local_conf, initial=True)
-    DbManage.check_waitress()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytest.ini	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,9 @@
+[pytest]
+# only look for tests in kallithea/tests
+python_files = kallithea/tests/**/test_*.py
+addopts =
+    # --verbose
+    # show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (w)warnings.
+    -rfEsxXw
+    # Shorter scrollbacks; less stuff to scroll through
+    --tb=short
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/contributor_data.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+# Some committers are so wrong that it doesn't point at any contributor:
+total_ignore = set()
+total_ignore.add('*** failed to import extension hggit: No module named hggit')
+total_ignore.add('<>')
+
+# Normalize some committer names where people have contributed under different
+# names or email addresses:
+name_fixes = {}
+name_fixes['Andrew Shadura'] = "Andrej Shadura <andrew@shadura.me>"
+name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
+name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
+name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
+name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
+name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
+name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
+name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
+name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
+name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
+name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
+name_fixes['Jelmer Vernooij'] = "Jelmer Vernooij <jelmer@samba.org>"
+name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
+name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
+name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
+name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
+name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
+name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
+name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
+name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
+name_fixes['marcink'] = "Marcin Kuźmiński <marcin@python-works.com>"
+name_fixes['Marcin Kuzminski'] = "Marcin Kuźmiński <marcin@python-works.com>"
+name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
+name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
+name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
+name_fixes['Søren Løvborg'] = "Søren Løvborg <sorenl@unity3d.com>"
+name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
+name_fixes['Hosted Weblate'] = "<>"
+name_fixes['Weblate'] = "<>"
+name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
+name_fixes['Lars <devel@sumpfralle.de>'] = "Lars Kruse <devel@sumpfralle.de>"
+
+# Some committer email address domains that indicate that another entity might
+# hold some copyright too:
+domain_extra = {}
+domain_extra['unity3d.com'] = "Unity Technologies"
+domain_extra['rhodecode.com'] = "RhodeCode GmbH"
+
+# Repository history show some old contributions that traditionally hasn't been
+# listed in about.html - preserve that:
+no_about = set(total_ignore)
+# The following contributors were traditionally not listed in about.html and it
+# seems unclear if the copyright is personal or belongs to a company.
+no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
+no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
+no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
+no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
+# These contributors' contributions might be too small to be copyrightable:
+no_about.add(('philip.j@hostdime.com', '2012'))
+no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
+no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
+# Was reworked and contributed later and shadowed by other contributions:
+no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
+
+# Preserve contributors listed in about.html but not appearing in repository
+# history:
+other_about = [
+    ("2011", "Aparkar <aparkar@icloud.com>"),
+    ("2010", "RhodeCode GmbH"),
+    ("2011", "RhodeCode GmbH"),
+    ("2012", "RhodeCode GmbH"),
+    ("2013", "RhodeCode GmbH"),
+]
+
+# Preserve contributors listed in CONTRIBUTORS but not appearing in repository
+# history:
+other_contributors = [
+    ("", "Andrew Kesterson <andrew@aklabs.net>"),
+    ("", "cejones"),
+    ("", "David A. Sjøen <david.sjoen@westcon.no>"),
+    ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
+    ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
+    ("", "larikale"),
+    ("", "RhodeCode GmbH"),
+    ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
+    ("", "Steve Romanow <slestak989@gmail.com>"),
+    ("", "SteveCohen"),
+    ("", "Thomas <thomas@rhodecode.com>"),
+    ("", "Thomas Waldmann <tw-public@gmx.de>"),
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/dbmigrate-test	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,121 @@
+#!/bin/sh -e
+
+if [ $# -lt 2 ] || [ $# -gt 3 ]; then
+    cat >&2 <<EOD
+usage: $0 CONFIG_FILE FROM_REV [TO_REV]
+
+Runs a database migration from FROM_REV to TO_REV (default: current
+working directory parent), using the specified CONFIG_FILE (.ini file).
+
+Test is run using a clean Kallithea install, in a temporary virtual
+environment. FROM_REV and (optional) TO_REV should be Mercurial revision
+identifiers (e.g. changeset hash or a version number tag). The working
+directory is not touched, but the database referenced in the config file
+will be (re)created.
+
+Only SQLite is available out of the box; for MySQL or PostgreSQL, set
+the EXTRA environment variable to the required package(s), and it'll
+be installed in the virtual environment. (E.g. EXTRA=MySQL-python or
+EXTRA=psycopg2.)
+
+The temporary directory is not removed, allowing follow-up examination
+of the upgrade results. It is, however, created in /tmp by default,
+which many Linux distributions automatically clean at regular intervals.
+EOD
+    exit 1
+fi
+
+config_file=$(readlink -f "$1")
+from_rev=$2
+to_rev=$3
+source_repo=$(dirname "$(dirname "$(readlink -f "$0")")")
+
+announce() {
+    echo
+    echo "$1"
+    echo
+}
+
+quiet_if_ok() (
+    local output
+    local st
+    set +e
+    output=$("$@" < /dev/null 2>&1)
+    st=$?
+    if [ $st -ne 0 ]; then
+        echo "$output" >&2
+        echo "Command $@ returned exit status $st." >&2
+        exit 1
+    fi
+)
+
+HG() {
+    "${HG:-hg}" --repository "$source_repo" "$@"
+}
+
+# If upgrading to "current revision", warn if working directory is dirty.
+if [ ! "$to_rev" ] && [ "$(HG status -mard)" ]; then
+    announce "Warning: Uncommitted changes in working directory will be ignored!"
+fi
+
+from_rev_hash=$(HG id --id --rev "${from_rev:-.}")
+to_rev_hash=$(HG id --id --rev "${to_rev:-.}")
+temp=$(readlink -f "$(mktemp --tmpdir -d 'dbmigrate-test.XXXXXX')")
+
+cat <<EOD
+Config file:    $config_file
+EOD
+sed -n -e 's/^sqlalchemy\.url *= */Database URL:   /p' "$config_file"
+cat <<EOD
+Working dir:    $temp
+Repository:     $source_repo
+Upgrade from:   $from_rev_hash (${from_rev:-current})
+Upgrade to:     $to_rev_hash (${to_rev:-current})
+Extra packages: ${EXTRA:-(none)}
+EOD
+
+mkdir "$temp/repos" # empty
+
+# Enable caching for old pip versions (this will cache the pip upgrade)
+# Newer pip versions cache automatically, and don't use this variable.
+if [ ! "$PIP_DOWNLOAD_CACHE" ]; then
+    export PIP_DOWNLOAD_CACHE=$HOME/.cache/pip/legacy
+fi
+
+install_kallithea() {
+    local prefix=$1
+    local rev=$2
+
+    announce "Installing Kallithea $rev in $prefix..."
+
+    "${VIRTUALENV:-virtualenv}" --quiet "$prefix-env"
+    HG archive --rev "$rev" "$prefix"
+
+    (
+        cd "$prefix"
+        . "$prefix-env/bin/activate"
+        pip install --quiet --upgrade pip setuptools mercurial $EXTRA
+        pip install --quiet -e .
+    )
+}
+
+install_kallithea "$temp/from" "$from_rev_hash"
+(
+    cd "$temp/from"
+    . "$temp/from-env/bin/activate"
+    announce "Initializing database..."
+    quiet_if_ok kallithea-cli db-create -c "$config_file" --repos="$temp/repos" --user=doe --email=doe@example.com --password=123456 --no-public-access --force-yes
+    alembic -c "$config_file" current -v
+)
+
+install_kallithea "$temp/to" "$to_rev_hash"
+(
+    cd "$temp/to"
+    . "$temp/to-env/bin/activate"
+
+    announce "Commencing database upgrade from shown Alembic revision to head..."
+    alembic -c "$config_file" current -v
+    alembic -c "$config_file" upgrade head
+    announce "Upgrade complete, now at the shown Alembic revision:"
+    alembic -c "$config_file" current -v
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/docs-headings.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,80 @@
+#!/usr/bin/env python2
+
+"""
+Consistent formatting of rst section titles
+"""
+
+import re
+import subprocess
+
+spaces = [
+    (0, 1), # we assume this is a over-and-underlined header
+    (2, 1),
+    (1, 1),
+    (1, 0),
+    (1, 0),
+    ]
+
+# http://sphinx-doc.org/rest.html :
+#   for the Python documentation, this convention is used which you may follow:
+#   # with overline, for parts
+#   * with overline, for chapters
+#   =, for sections
+#   -, for subsections
+#   ^, for subsubsections
+#   ", for paragraphs
+pystyles = ['#', '*', '=', '-', '^', '"']
+
+# match on a header line underlined with one of the valid characters
+headermatch = re.compile(r'''\n*(.+)\n([][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-])\2{2,}\n+''', flags=re.MULTILINE)
+
+
+def main():
+    filenames = subprocess.check_output(['hg', 'loc', 'set:**.rst+kallithea/i18n/how_to']).splitlines()
+    for fn in filenames:
+        print 'processing %s' % fn
+        s = open(fn).read()
+
+        # find levels and their styles
+        lastpos = 0
+        styles = []
+        for markup in headermatch.findall(s):
+            style = markup[1]
+            if style in styles:
+                stylepos = styles.index(style)
+                if stylepos > lastpos + 1:
+                    print 'bad style %r with level %s - was at %s' % (style, stylepos, lastpos)
+            else:
+                stylepos = len(styles)
+                if stylepos > lastpos + 1:
+                    print 'bad new style %r - expected %r' % (style, styles[lastpos + 1])
+                else:
+                    styles.append(style)
+            lastpos = stylepos
+
+        # remove superfluous spacing (may however be restored by header spacing)
+        s = re.sub(r'''(\n\n)\n*''', r'\1', s, flags=re.MULTILINE)
+
+        if styles:
+            newstyles = pystyles[pystyles.index(styles[0]):]
+
+            def subf(m):
+                title, style = m.groups()
+                level = styles.index(style)
+                before, after = spaces[level]
+                newstyle = newstyles[level]
+                return '\n' * (before + 1) + title + '\n' + newstyle * len(title) + '\n' * (after + 1)
+            s = headermatch.sub(subf, s)
+
+        # remove superfluous spacing when headers are adjacent
+        s = re.sub(r'''(\n.+\n([][!"#$%&'()*+,./:;<=>?@\\^_`{|}~-])\2{2,}\n\n\n)\n*''', r'\1', s, flags=re.MULTILINE)
+        # fix trailing space and spacing before link sections
+        s = s.strip() + '\n'
+        s = re.sub(r'''\n+((?:\.\. _[^\n]*\n)+)$''', r'\n\n\n\1', s)
+
+        open(fn, 'w').write(s)
+
+    print subprocess.check_output(['hg', 'diff'] + filenames)
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/generate-ini.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,72 @@
+#!/usr/bin/env python2
+"""
+Based on kallithea/lib/paster_commands/template.ini.mako, generate development.ini
+"""
+
+import re
+
+from kallithea.lib import inifile
+
+# files to be generated from the mako template
+ini_files = [
+    ('development.ini',
+        {
+            '[server:main]': {
+                'host': '0.0.0.0',
+            },
+            '[app:main]': {
+                'debug': 'true',
+                'app_instance_uuid': 'development-not-secret',
+                'beaker.session.secret': 'development-not-secret',
+            },
+            '[handler_console]': {
+                'formatter': 'color_formatter',
+            },
+            '[handler_console_sql]': {
+                'formatter': 'color_formatter_sql',
+            },
+            '[logger_routes]': {
+                'level': 'DEBUG',
+            },
+            '[logger_beaker]': {
+                'level': 'DEBUG',
+            },
+            '[logger_templates]': {
+                'level': 'INFO',
+            },
+            '[logger_kallithea]': {
+                'level': 'DEBUG',
+            },
+            '[logger_tg]': {
+                'level': 'DEBUG',
+            },
+            '[logger_gearbox]': {
+                'level': 'DEBUG',
+            },
+            '[logger_whoosh_indexer]': {
+                'level': 'DEBUG',
+            },
+        },
+    ),
+]
+
+
+def main():
+    # make sure all mako lines starting with '#' (the '##' comments) are marked up as <text>
+    makofile = inifile.template_file
+    print 'reading:', makofile
+    mako_org = open(makofile).read()
+    mako_no_text_markup = re.sub(r'</?%text>', '', mako_org)
+    mako_marked_up = re.sub(r'\n(##.*)', r'\n<%text>\1</%text>', mako_no_text_markup, flags=re.MULTILINE)
+    if mako_marked_up != mako_org:
+        print 'writing:', makofile
+        open(makofile, 'w').write(mako_marked_up)
+
+    # create ini files
+    for fn, settings in ini_files:
+        print 'updating:', fn
+        inifile.create(fn, None, settings)
+
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/logformat.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,39 @@
+#!/usr/bin/env python2
+
+import re
+import sys
+
+if len(sys.argv) < 2:
+    print 'Cleanup of superfluous % formatting of log statements.'
+    print 'Usage:'
+    print '''  hg revert `hg loc '*.py'|grep -v logformat.py` && scripts/logformat.py `hg loc '*.py'` && hg diff'''
+    raise SystemExit(1)
+
+
+logre = r'''
+(log\.(?:error|info|warning|debug)
+[(][ \n]*
+)
+%s
+(
+[ \n]*[)]
+)
+'''
+res = [
+    # handle % () - keeping spaces around the old %
+    (re.compile(logre % r'''("[^"]*"|'[^']*')   ([\n ]*) %  ([\n ]*) \( ( (?:[^()]|\n)* (?: \( (?:[^()]|\n)* \) (?:[^()]|\n)* )* ) \) ''', flags=re.MULTILINE|re.VERBOSE), r'\1\2,\3\4\5\6'),
+    # handle % without () - keeping spaces around the old %
+    (re.compile(logre % r'''("[^"]*"|'[^']*')   ([\n ]*) %  ([\n ]*)    ( (?:[^()]|\n)* (?: \( (?:[^()]|\n)* \) (?:[^()]|\n)* )* )    ''', flags=re.MULTILINE|re.VERBOSE), r'\1\2,\3\4\5\6'),
+    # remove extra space if it is on next line
+    (re.compile(logre % r'''("[^"]*"|'[^']*') , (\n [ ]) ([ ][\n ]*)    ( (?:[^()]|\n)* (?: \( (?:[^()]|\n)* \) (?:[^()]|\n)* )* )    ''', flags=re.MULTILINE|re.VERBOSE), r'\1\2,\3\4\5\6'),
+    # remove extra space if it is on same line
+    (re.compile(logre % r'''("[^"]*"|'[^']*') , [ ]+  () (   [\n ]+)    ( (?:[^()]|\n)* (?: \( (?:[^()]|\n)* \) (?:[^()]|\n)* )* )    ''', flags=re.MULTILINE|re.VERBOSE), r'\1\2,\3\4\5\6'),
+    # remove trailing , and space
+    (re.compile(logre % r'''("[^"]*"|'[^']*') ,       () (   [\n ]*)    ( (?:[^()]|\n)* (?: \( (?:[^()]|\n)* \) (?:[^()]|\n)* )* [^(), \n] ) [ ,]*''', flags=re.MULTILINE|re.VERBOSE), r'\1\2,\3\4\5\6'),
+    ]
+
+for f in sys.argv[1:]:
+    s = open(f).read()
+    for r, t in res:
+        s = r.sub(t, s)
+    open(f, 'w').write(s)
--- a/scripts/make-release	Sun Mar 03 21:36:25 2019 +0100
+++ b/scripts/make-release	Sun Mar 31 21:28:56 2019 +0200
@@ -20,10 +20,10 @@
 
 echo "Install/verify tools needed for building and uploading stuff"
 pip install --upgrade -e .
-pip install --upgrade Sphinx Sphinx-PyPI-upload
+pip install --upgrade -r dev_requirements.txt twine
 
 echo "Cleanup and update copyrights ... and clean checkout"
-scripts/whitespacecleanup.sh
+scripts/run-all-cleanup
 scripts/update-copyrights.py
 hg up -cr .
 
@@ -32,8 +32,8 @@
 hg archive build
 cd build
 
-echo "Check MANIFEST.in"
-sed -e 's/[^ ]*[ ]*\([^ ]*\).*/\1/g' MANIFEST.in | grep -v '^node_modules/bootstrap\|^kallithea/public/css/style.css' | xargs ls -lad
+echo "Check that each entry in MANIFEST.in match something"
+sed -e 's/[^ ]*[ ]*\([^ ]*\).*/\1/g' MANIFEST.in | xargs ls -lad
 
 echo "Build dist"
 python2 setup.py compile_catalog
@@ -47,10 +47,13 @@
 echo "Releasing Kallithea $version in directory $namerel"
 
 echo "Verify dist file content"
-! tar tf dist/Kallithea-$version.tar.gz | grep "$namerel/node_modules/bootstrap/\$"
+diff -u <((hg mani | grep -v '^\.hg') | LANG=C sort) <(tar tf dist/Kallithea-$version.tar.gz | sed "s|^$namerel/||" | grep . | grep -v '^kallithea/i18n/.*/LC_MESSAGES/kallithea.mo$\|^Kallithea.egg-info/\|^PKG-INFO$\|/$' | LANG=C sort)
 
 echo "Verify docs build"
-python2 setup.py build_sphinx # not used yet ... but we want to make sure it builds
+python2 setup.py build_sphinx # the results are not actually used, but we want to make sure it builds
+
+echo "Shortlog for inclusion in the release announcement"
+scripts/shortlog.py "only('.', branch('stable') & tagged() & public() & not '.')"
 
 cat - << EOT
 
@@ -69,17 +72,11 @@
 read answer
 [ "$answer" = "pypi" ]
 
-echo "Upload docs to pypi"
-# See https://wiki.python.org/moin/PyPiDocumentationHosting
-python2 setup.py build_sphinx upload_sphinx
-xdg-open http://packages.python.org/Kallithea/installation.html
-
 echo "Rebuild readthedocs for docs.kallithea-scm.org"
 xdg-open https://readthedocs.org/projects/kallithea/
 curl -X POST http://readthedocs.org/build/kallithea
 xdg-open https://readthedocs.org/builds/kallithea/
 xdg-open http://docs.kallithea-scm.org/en/latest/ # or whatever the branch is
 
-extraargs=${EMAIL:+--identity=$EMAIL}
-python2 setup.py sdist upload --sign $extraargs
+twine upload dist/*
 xdg-open https://pypi.python.org/pypi/Kallithea
--- a/scripts/manifest	Sun Mar 03 21:36:25 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,952 +0,0 @@
-
-Apache-License-2.0.txt
-CONTRIBUTORS
-COPYING
-Kallithea.egg-info/
-Kallithea.egg-info/PKG-INFO
-Kallithea.egg-info/SOURCES.txt
-Kallithea.egg-info/dependency_links.txt
-Kallithea.egg-info/entry_points.txt
-Kallithea.egg-info/not-zip-safe
-Kallithea.egg-info/paster_plugins.txt
-Kallithea.egg-info/requires.txt
-Kallithea.egg-info/top_level.txt
-LICENSE-MERGELY.html
-LICENSE.md
-MANIFEST.in
-MIT-Permissive-License.txt
-PKG-INFO
-README.rst
-development.ini
-docs/
-docs/Makefile
-docs/api/
-docs/api/api.rst
-docs/api/models.rst
-docs/changelog.rst
-docs/conf.py
-docs/contributing.rst
-docs/images/
-docs/images/.img
-docs/index.rst
-docs/installation.rst
-docs/installation_iis.rst
-docs/installation_puppet.rst
-docs/installation_win.rst
-docs/installation_win_old.rst
-docs/make.bat
-docs/overview.rst
-docs/readme.rst
-docs/setup.rst
-docs/theme/
-docs/theme/nature/
-docs/theme/nature/layout.html
-docs/theme/nature/static/
-docs/theme/nature/static/kallithea-logo.svg
-docs/theme/nature/static/nature.css_t
-docs/theme/nature/static/pygments.css
-docs/theme/nature/theme.conf
-docs/usage/
-docs/usage/backup.rst
-docs/usage/debugging.rst
-docs/usage/email.rst
-docs/usage/general.rst
-docs/usage/locking.rst
-docs/usage/performance.rst
-docs/usage/statistics.rst
-docs/usage/troubleshooting.rst
-docs/usage/vcs_support.rst
-init.d/
-init.d/celeryd-upstart.conf
-init.d/kallithea-daemon-arch
-init.d/kallithea-daemon-debian
-init.d/kallithea-daemon-gentoo
-init.d/kallithea-daemon-redhat
-init.d/kallithea-upstart.conf
-init.d/supervisord.conf
-kallithea/
-kallithea/__init__.py
-kallithea/bin/
-kallithea/bin/__init__.py
-kallithea/bin/base.py
-kallithea/bin/kallithea_api.py
-kallithea/bin/kallithea_backup.py
-kallithea/bin/kallithea_config.py
-kallithea/bin/kallithea_gist.py
-kallithea/bin/ldap_sync.conf
-kallithea/bin/ldap_sync.py
-kallithea/bin/rebranddb.py
-kallithea/bin/template.ini.mako
-kallithea/config/
-kallithea/config/__init__.py
-kallithea/config/conf.py
-kallithea/config/deployment.ini_tmpl
-kallithea/config/environment.py
-kallithea/config/middleware.py
-kallithea/config/post_receive_tmpl.py
-kallithea/config/pre_receive_tmpl.py
-kallithea/config/rcextensions/
-kallithea/config/rcextensions/__init__.py
-kallithea/config/routing.py
-kallithea/controllers/
-kallithea/controllers/__init__.py
-kallithea/controllers/admin/
-kallithea/controllers/admin/__init__.py
-kallithea/controllers/admin/admin.py
-kallithea/controllers/admin/auth_settings.py
-kallithea/controllers/admin/defaults.py
-kallithea/controllers/admin/gists.py
-kallithea/controllers/admin/my_account.py
-kallithea/controllers/admin/notifications.py
-kallithea/controllers/admin/permissions.py
-kallithea/controllers/admin/repo_groups.py
-kallithea/controllers/admin/repos.py
-kallithea/controllers/admin/settings.py
-kallithea/controllers/admin/user_groups.py
-kallithea/controllers/admin/users.py
-kallithea/controllers/api/
-kallithea/controllers/api/__init__.py
-kallithea/controllers/api/api.py
-kallithea/controllers/bookmarks.py
-kallithea/controllers/branches.py
-kallithea/controllers/changelog.py
-kallithea/controllers/changeset.py
-kallithea/controllers/compare.py
-kallithea/controllers/error.py
-kallithea/controllers/feed.py
-kallithea/controllers/files.py
-kallithea/controllers/followers.py
-kallithea/controllers/forks.py
-kallithea/controllers/home.py
-kallithea/controllers/journal.py
-kallithea/controllers/login.py
-kallithea/controllers/pullrequests.py
-kallithea/controllers/search.py
-kallithea/controllers/summary.py
-kallithea/controllers/tags.py
-kallithea/i18n/
-kallithea/i18n/be/
-kallithea/i18n/be/LC_MESSAGES/
-kallithea/i18n/be/LC_MESSAGES/kallithea.mo
-kallithea/i18n/be/LC_MESSAGES/kallithea.po
-kallithea/i18n/cs/
-kallithea/i18n/cs/LC_MESSAGES/
-kallithea/i18n/cs/LC_MESSAGES/kallithea.mo
-kallithea/i18n/cs/LC_MESSAGES/kallithea.po
-kallithea/i18n/de/
-kallithea/i18n/de/LC_MESSAGES/
-kallithea/i18n/de/LC_MESSAGES/kallithea.mo
-kallithea/i18n/de/LC_MESSAGES/kallithea.po
-kallithea/i18n/en/
-kallithea/i18n/en/LC_MESSAGES/
-kallithea/i18n/en/LC_MESSAGES/kallithea.mo
-kallithea/i18n/fr/
-kallithea/i18n/fr/LC_MESSAGES/
-kallithea/i18n/fr/LC_MESSAGES/kallithea.mo
-kallithea/i18n/fr/LC_MESSAGES/kallithea.po
-kallithea/i18n/how_to
-kallithea/i18n/hu/
-kallithea/i18n/hu/LC_MESSAGES/
-kallithea/i18n/hu/LC_MESSAGES/kallithea.mo
-kallithea/i18n/hu/LC_MESSAGES/kallithea.po
-kallithea/i18n/ja/
-kallithea/i18n/ja/LC_MESSAGES/
-kallithea/i18n/ja/LC_MESSAGES/kallithea.mo
-kallithea/i18n/ja/LC_MESSAGES/kallithea.po
-kallithea/i18n/kallithea.pot
-kallithea/i18n/nl_BE/
-kallithea/i18n/nl_BE/LC_MESSAGES/
-kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.mo
-kallithea/i18n/nl_BE/LC_MESSAGES/kallithea.po
-kallithea/i18n/pl/
-kallithea/i18n/pl/LC_MESSAGES/
-kallithea/i18n/pl/LC_MESSAGES/kallithea.mo
-kallithea/i18n/pl/LC_MESSAGES/kallithea.po
-kallithea/i18n/pt_BR/
-kallithea/i18n/pt_BR/LC_MESSAGES/
-kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.mo
-kallithea/i18n/pt_BR/LC_MESSAGES/kallithea.po
-kallithea/i18n/ru/
-kallithea/i18n/ru/LC_MESSAGES/
-kallithea/i18n/ru/LC_MESSAGES/kallithea.mo
-kallithea/i18n/ru/LC_MESSAGES/kallithea.po
-kallithea/i18n/sk/
-kallithea/i18n/sk/LC_MESSAGES/
-kallithea/i18n/sk/LC_MESSAGES/kallithea.mo
-kallithea/i18n/sk/LC_MESSAGES/kallithea.po
-kallithea/i18n/zh_CN/
-kallithea/i18n/zh_CN/LC_MESSAGES/
-kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.mo
-kallithea/i18n/zh_CN/LC_MESSAGES/kallithea.po
-kallithea/i18n/zh_TW/
-kallithea/i18n/zh_TW/LC_MESSAGES/
-kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.mo
-kallithea/i18n/zh_TW/LC_MESSAGES/kallithea.po
-kallithea/lib/
-kallithea/lib/__init__.py
-kallithea/lib/annotate.py
-kallithea/lib/app_globals.py
-kallithea/lib/auth.py
-kallithea/lib/auth_modules/
-kallithea/lib/auth_modules/__init__.py
-kallithea/lib/auth_modules/auth_container.py
-kallithea/lib/auth_modules/auth_crowd.py
-kallithea/lib/auth_modules/auth_internal.py
-kallithea/lib/auth_modules/auth_ldap.py
-kallithea/lib/auth_modules/auth_pam.py
-kallithea/lib/base.py
-kallithea/lib/caching_query.py
-kallithea/lib/celerylib/
-kallithea/lib/celerylib/__init__.py
-kallithea/lib/celerylib/tasks.py
-kallithea/lib/celerypylons/
-kallithea/lib/celerypylons/__init__.py
-kallithea/lib/celerypylons/commands.py
-kallithea/lib/celerypylons/loader.py
-kallithea/lib/colored_formatter.py
-kallithea/lib/compat.py
-kallithea/lib/db_manage.py
-kallithea/lib/dbmigrate/
-kallithea/lib/dbmigrate/__init__.py
-kallithea/lib/dbmigrate/migrate.cfg
-kallithea/lib/dbmigrate/migrate/
-kallithea/lib/dbmigrate/migrate/__init__.py
-kallithea/lib/dbmigrate/migrate/changeset/
-kallithea/lib/dbmigrate/migrate/changeset/__init__.py
-kallithea/lib/dbmigrate/migrate/changeset/ansisql.py
-kallithea/lib/dbmigrate/migrate/changeset/constraint.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/
-kallithea/lib/dbmigrate/migrate/changeset/databases/__init__.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/firebird.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/mysql.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/oracle.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/postgres.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/sqlite.py
-kallithea/lib/dbmigrate/migrate/changeset/databases/visitor.py
-kallithea/lib/dbmigrate/migrate/changeset/schema.py
-kallithea/lib/dbmigrate/migrate/exceptions.py
-kallithea/lib/dbmigrate/migrate/versioning/
-kallithea/lib/dbmigrate/migrate/versioning/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/api.py
-kallithea/lib/dbmigrate/migrate/versioning/cfgparse.py
-kallithea/lib/dbmigrate/migrate/versioning/config.py
-kallithea/lib/dbmigrate/migrate/versioning/genmodel.py
-kallithea/lib/dbmigrate/migrate/versioning/migrate_repository.py
-kallithea/lib/dbmigrate/migrate/versioning/pathed.py
-kallithea/lib/dbmigrate/migrate/versioning/repository.py
-kallithea/lib/dbmigrate/migrate/versioning/schema.py
-kallithea/lib/dbmigrate/migrate/versioning/schemadiff.py
-kallithea/lib/dbmigrate/migrate/versioning/script/
-kallithea/lib/dbmigrate/migrate/versioning/script/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/script/base.py
-kallithea/lib/dbmigrate/migrate/versioning/script/py.py
-kallithea/lib/dbmigrate/migrate/versioning/script/sql.py
-kallithea/lib/dbmigrate/migrate/versioning/shell.py
-kallithea/lib/dbmigrate/migrate/versioning/template.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/
-kallithea/lib/dbmigrate/migrate/versioning/templates/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/manage.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/manage/
-kallithea/lib/dbmigrate/migrate/versioning/templates/manage/default.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/manage/pylons.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/README
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/migrate.cfg
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/default/versions/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/README
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/migrate.cfg
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/
-kallithea/lib/dbmigrate/migrate/versioning/templates/repository/pylons/versions/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/script/
-kallithea/lib/dbmigrate/migrate/versioning/templates/script/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/templates/script/default.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/script/pylons.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/
-kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/default.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/templates/sql_script/pylons.py_tmpl
-kallithea/lib/dbmigrate/migrate/versioning/util/
-kallithea/lib/dbmigrate/migrate/versioning/util/__init__.py
-kallithea/lib/dbmigrate/migrate/versioning/util/importpath.py
-kallithea/lib/dbmigrate/migrate/versioning/util/keyedinstance.py
-kallithea/lib/dbmigrate/migrate/versioning/version.py
-kallithea/lib/dbmigrate/schema/
-kallithea/lib/dbmigrate/schema/__init__.py
-kallithea/lib/dbmigrate/schema/db_1_1_0.py
-kallithea/lib/dbmigrate/schema/db_1_2_0.py
-kallithea/lib/dbmigrate/schema/db_1_3_0.py
-kallithea/lib/dbmigrate/schema/db_1_4_0.py
-kallithea/lib/dbmigrate/schema/db_1_5_0.py
-kallithea/lib/dbmigrate/schema/db_1_5_2.py
-kallithea/lib/dbmigrate/schema/db_1_6_0.py
-kallithea/lib/dbmigrate/schema/db_1_7_0.py
-kallithea/lib/dbmigrate/schema/db_1_8_0.py
-kallithea/lib/dbmigrate/schema/db_2_0_0.py
-kallithea/lib/dbmigrate/schema/db_2_0_1.py
-kallithea/lib/dbmigrate/schema/db_2_0_2.py
-kallithea/lib/dbmigrate/schema/db_2_1_0.py
-kallithea/lib/dbmigrate/schema/db_2_2_0.py
-kallithea/lib/dbmigrate/schema/db_2_2_3.py
-kallithea/lib/dbmigrate/versions/
-kallithea/lib/dbmigrate/versions/001_initial_release.py
-kallithea/lib/dbmigrate/versions/002_version_1_1_0.py
-kallithea/lib/dbmigrate/versions/003_version_1_2_0.py
-kallithea/lib/dbmigrate/versions/004_version_1_3_0.py
-kallithea/lib/dbmigrate/versions/005_version_1_3_0.py
-kallithea/lib/dbmigrate/versions/006_version_1_4_0.py
-kallithea/lib/dbmigrate/versions/007_version_1_4_0.py
-kallithea/lib/dbmigrate/versions/008_version_1_5_0.py
-kallithea/lib/dbmigrate/versions/009_version_1_5_1.py
-kallithea/lib/dbmigrate/versions/010_version_1_5_2.py
-kallithea/lib/dbmigrate/versions/011_version_1_6_0.py
-kallithea/lib/dbmigrate/versions/012_version_1_7_0.py
-kallithea/lib/dbmigrate/versions/013_version_1_7_0.py
-kallithea/lib/dbmigrate/versions/014_version_1_7_1.py
-kallithea/lib/dbmigrate/versions/015_version_1_8_0.py
-kallithea/lib/dbmigrate/versions/016_version_2_0_0.py
-kallithea/lib/dbmigrate/versions/017_version_2_0_0.py
-kallithea/lib/dbmigrate/versions/018_version_2_0_0.py
-kallithea/lib/dbmigrate/versions/019_version_2_0_0.py
-kallithea/lib/dbmigrate/versions/020_version_2_0_1.py
-kallithea/lib/dbmigrate/versions/021_version_2_0_2.py
-kallithea/lib/dbmigrate/versions/022_version_2_0_2.py
-kallithea/lib/dbmigrate/versions/023_version_2_1_0.py
-kallithea/lib/dbmigrate/versions/024_version_2_1_0.py
-kallithea/lib/dbmigrate/versions/025_version_2_1_0.py
-kallithea/lib/dbmigrate/versions/026_version_2_2_0.py
-kallithea/lib/dbmigrate/versions/027_version_2_2_0.py
-kallithea/lib/dbmigrate/versions/028_version_2_2_3.py
-kallithea/lib/dbmigrate/versions/029_version_2_2_3.py
-kallithea/lib/dbmigrate/versions/030_version_2_2_3.py
-kallithea/lib/dbmigrate/versions/031_version_2_2_3.py
-kallithea/lib/dbmigrate/versions/__init__.py
-kallithea/lib/diffs.py
-kallithea/lib/exceptions.py
-kallithea/lib/ext_json.py
-kallithea/lib/graphmod.py
-kallithea/lib/helpers.py
-kallithea/lib/hooks.py
-kallithea/lib/indexers/
-kallithea/lib/indexers/__init__.py
-kallithea/lib/indexers/daemon.py
-kallithea/lib/ipaddr.py
-kallithea/lib/markup_renderer.py
-kallithea/lib/middleware/
-kallithea/lib/middleware/__init__.py
-kallithea/lib/middleware/appenlight.py
-kallithea/lib/middleware/https_fixup.py
-kallithea/lib/middleware/pygrack.py
-kallithea/lib/middleware/sentry.py
-kallithea/lib/middleware/sessionmiddleware.py
-kallithea/lib/middleware/simplegit.py
-kallithea/lib/middleware/simplehg.py
-kallithea/lib/middleware/wrapper.py
-kallithea/lib/paster_commands/
-kallithea/lib/paster_commands/__init__.py
-kallithea/lib/paster_commands/cache_keys.py
-kallithea/lib/paster_commands/cleanup.py
-kallithea/lib/paster_commands/install_iis.py
-kallithea/lib/paster_commands/ishell.py
-kallithea/lib/paster_commands/make_index.py
-kallithea/lib/paster_commands/make_rcextensions.py
-kallithea/lib/paster_commands/repo_scan.py
-kallithea/lib/paster_commands/setup_db.py
-kallithea/lib/paster_commands/update_repoinfo.py
-kallithea/lib/pidlock.py
-kallithea/lib/profiler.py
-kallithea/lib/rcmail/
-kallithea/lib/rcmail/__init__.py
-kallithea/lib/rcmail/exceptions.py
-kallithea/lib/rcmail/message.py
-kallithea/lib/rcmail/response.py
-kallithea/lib/rcmail/smtp_mailer.py
-kallithea/lib/rcmail/utils.py
-kallithea/lib/recaptcha.py
-kallithea/lib/timerproxy.py
-kallithea/lib/utils.py
-kallithea/lib/utils2.py
-kallithea/lib/vcs/
-kallithea/lib/vcs/__init__.py
-kallithea/lib/vcs/backends/
-kallithea/lib/vcs/backends/__init__.py
-kallithea/lib/vcs/backends/base.py
-kallithea/lib/vcs/backends/git/
-kallithea/lib/vcs/backends/git/__init__.py
-kallithea/lib/vcs/backends/git/changeset.py
-kallithea/lib/vcs/backends/git/inmemory.py
-kallithea/lib/vcs/backends/git/repository.py
-kallithea/lib/vcs/backends/git/workdir.py
-kallithea/lib/vcs/backends/hg/
-kallithea/lib/vcs/backends/hg/__init__.py
-kallithea/lib/vcs/backends/hg/changeset.py
-kallithea/lib/vcs/backends/hg/inmemory.py
-kallithea/lib/vcs/backends/hg/repository.py
-kallithea/lib/vcs/backends/hg/workdir.py
-kallithea/lib/vcs/conf/
-kallithea/lib/vcs/conf/__init__.py
-kallithea/lib/vcs/conf/settings.py
-kallithea/lib/vcs/exceptions.py
-kallithea/lib/vcs/nodes.py
-kallithea/lib/vcs/subprocessio.py
-kallithea/lib/vcs/utils/
-kallithea/lib/vcs/utils/__init__.py
-kallithea/lib/vcs/utils/annotate.py
-kallithea/lib/vcs/utils/archivers.py
-kallithea/lib/vcs/utils/baseui_config.py
-kallithea/lib/vcs/utils/compat.py
-kallithea/lib/vcs/utils/diffs.py
-kallithea/lib/vcs/utils/fakemod.py
-kallithea/lib/vcs/utils/filesize.py
-kallithea/lib/vcs/utils/helpers.py
-kallithea/lib/vcs/utils/hgcompat.py
-kallithea/lib/vcs/utils/imports.py
-kallithea/lib/vcs/utils/lazy.py
-kallithea/lib/vcs/utils/lockfiles.py
-kallithea/lib/vcs/utils/ordered_dict.py
-kallithea/lib/vcs/utils/paths.py
-kallithea/lib/vcs/utils/progressbar.py
-kallithea/lib/vcs/utils/termcolors.py
-kallithea/lib/verlib.py
-kallithea/model/
-kallithea/model/__init__.py
-kallithea/model/api_key.py
-kallithea/model/changeset_status.py
-kallithea/model/comment.py
-kallithea/model/db.py
-kallithea/model/forms.py
-kallithea/model/gist.py
-kallithea/model/meta.py
-kallithea/model/notification.py
-kallithea/model/permission.py
-kallithea/model/pull_request.py
-kallithea/model/repo.py
-kallithea/model/repo_group.py
-kallithea/model/repo_permission.py
-kallithea/model/scm.py
-kallithea/model/user.py
-kallithea/model/user_group.py
-kallithea/model/validators.py
-kallithea/public/
-kallithea/public/codemirror/
-kallithea/public/codemirror/LICENSE
-kallithea/public/codemirror/lib/
-kallithea/public/codemirror/lib/codemirror.css
-kallithea/public/codemirror/lib/codemirror.js
-kallithea/public/codemirror/mode/
-kallithea/public/codemirror/mode/apl/
-kallithea/public/codemirror/mode/apl/apl.js
-kallithea/public/codemirror/mode/asterisk/
-kallithea/public/codemirror/mode/asterisk/asterisk.js
-kallithea/public/codemirror/mode/clike/
-kallithea/public/codemirror/mode/clike/clike.js
-kallithea/public/codemirror/mode/clojure/
-kallithea/public/codemirror/mode/clojure/clojure.js
-kallithea/public/codemirror/mode/cobol/
-kallithea/public/codemirror/mode/cobol/cobol.js
-kallithea/public/codemirror/mode/coffeescript/
-kallithea/public/codemirror/mode/coffeescript/coffeescript.js
-kallithea/public/codemirror/mode/commonlisp/
-kallithea/public/codemirror/mode/commonlisp/commonlisp.js
-kallithea/public/codemirror/mode/css/
-kallithea/public/codemirror/mode/css/css.js
-kallithea/public/codemirror/mode/css/less_test.js
-kallithea/public/codemirror/mode/css/scss_test.js
-kallithea/public/codemirror/mode/cypher/
-kallithea/public/codemirror/mode/cypher/cypher.js
-kallithea/public/codemirror/mode/d/
-kallithea/public/codemirror/mode/d/d.js
-kallithea/public/codemirror/mode/diff/
-kallithea/public/codemirror/mode/diff/diff.js
-kallithea/public/codemirror/mode/django/
-kallithea/public/codemirror/mode/django/django.js
-kallithea/public/codemirror/mode/dtd/
-kallithea/public/codemirror/mode/dtd/dtd.js
-kallithea/public/codemirror/mode/dylan/
-kallithea/public/codemirror/mode/dylan/dylan.js
-kallithea/public/codemirror/mode/ecl/
-kallithea/public/codemirror/mode/ecl/ecl.js
-kallithea/public/codemirror/mode/eiffel/
-kallithea/public/codemirror/mode/eiffel/eiffel.js
-kallithea/public/codemirror/mode/erlang/
-kallithea/public/codemirror/mode/erlang/erlang.js
-kallithea/public/codemirror/mode/fortran/
-kallithea/public/codemirror/mode/fortran/fortran.js
-kallithea/public/codemirror/mode/gas/
-kallithea/public/codemirror/mode/gas/gas.js
-kallithea/public/codemirror/mode/gfm/
-kallithea/public/codemirror/mode/gfm/gfm.js
-kallithea/public/codemirror/mode/gherkin/
-kallithea/public/codemirror/mode/gherkin/gherkin.js
-kallithea/public/codemirror/mode/go/
-kallithea/public/codemirror/mode/go/go.js
-kallithea/public/codemirror/mode/groovy/
-kallithea/public/codemirror/mode/groovy/groovy.js
-kallithea/public/codemirror/mode/haml/
-kallithea/public/codemirror/mode/haml/haml.js
-kallithea/public/codemirror/mode/haskell/
-kallithea/public/codemirror/mode/haskell/haskell.js
-kallithea/public/codemirror/mode/haxe/
-kallithea/public/codemirror/mode/haxe/haxe.js
-kallithea/public/codemirror/mode/htmlembedded/
-kallithea/public/codemirror/mode/htmlembedded/htmlembedded.js
-kallithea/public/codemirror/mode/htmlmixed/
-kallithea/public/codemirror/mode/htmlmixed/htmlmixed.js
-kallithea/public/codemirror/mode/http/
-kallithea/public/codemirror/mode/http/http.js
-kallithea/public/codemirror/mode/jade/
-kallithea/public/codemirror/mode/jade/jade.js
-kallithea/public/codemirror/mode/javascript/
-kallithea/public/codemirror/mode/javascript/javascript.js
-kallithea/public/codemirror/mode/jinja2/
-kallithea/public/codemirror/mode/jinja2/jinja2.js
-kallithea/public/codemirror/mode/julia/
-kallithea/public/codemirror/mode/julia/julia.js
-kallithea/public/codemirror/mode/kotlin/
-kallithea/public/codemirror/mode/kotlin/kotlin.js
-kallithea/public/codemirror/mode/livescript/
-kallithea/public/codemirror/mode/livescript/livescript.js
-kallithea/public/codemirror/mode/lua/
-kallithea/public/codemirror/mode/lua/lua.js
-kallithea/public/codemirror/mode/markdown/
-kallithea/public/codemirror/mode/markdown/markdown.js
-kallithea/public/codemirror/mode/meta.js
-kallithea/public/codemirror/mode/mirc/
-kallithea/public/codemirror/mode/mirc/mirc.js
-kallithea/public/codemirror/mode/mllike/
-kallithea/public/codemirror/mode/mllike/mllike.js
-kallithea/public/codemirror/mode/modelica/
-kallithea/public/codemirror/mode/modelica/modelica.js
-kallithea/public/codemirror/mode/nginx/
-kallithea/public/codemirror/mode/nginx/nginx.js
-kallithea/public/codemirror/mode/ntriples/
-kallithea/public/codemirror/mode/ntriples/ntriples.js
-kallithea/public/codemirror/mode/octave/
-kallithea/public/codemirror/mode/octave/octave.js
-kallithea/public/codemirror/mode/pascal/
-kallithea/public/codemirror/mode/pascal/pascal.js
-kallithea/public/codemirror/mode/pegjs/
-kallithea/public/codemirror/mode/pegjs/pegjs.js
-kallithea/public/codemirror/mode/perl/
-kallithea/public/codemirror/mode/perl/perl.js
-kallithea/public/codemirror/mode/php/
-kallithea/public/codemirror/mode/php/php.js
-kallithea/public/codemirror/mode/pig/
-kallithea/public/codemirror/mode/pig/pig.js
-kallithea/public/codemirror/mode/properties/
-kallithea/public/codemirror/mode/properties/properties.js
-kallithea/public/codemirror/mode/puppet/
-kallithea/public/codemirror/mode/puppet/puppet.js
-kallithea/public/codemirror/mode/python/
-kallithea/public/codemirror/mode/python/python.js
-kallithea/public/codemirror/mode/q/
-kallithea/public/codemirror/mode/q/q.js
-kallithea/public/codemirror/mode/r/
-kallithea/public/codemirror/mode/r/r.js
-kallithea/public/codemirror/mode/rpm/
-kallithea/public/codemirror/mode/rpm/rpm.js
-kallithea/public/codemirror/mode/rst/
-kallithea/public/codemirror/mode/rst/rst.js
-kallithea/public/codemirror/mode/ruby/
-kallithea/public/codemirror/mode/ruby/ruby.js
-kallithea/public/codemirror/mode/rust/
-kallithea/public/codemirror/mode/rust/rust.js
-kallithea/public/codemirror/mode/sass/
-kallithea/public/codemirror/mode/sass/sass.js
-kallithea/public/codemirror/mode/scheme/
-kallithea/public/codemirror/mode/scheme/scheme.js
-kallithea/public/codemirror/mode/shell/
-kallithea/public/codemirror/mode/shell/shell.js
-kallithea/public/codemirror/mode/sieve/
-kallithea/public/codemirror/mode/sieve/sieve.js
-kallithea/public/codemirror/mode/slim/
-kallithea/public/codemirror/mode/slim/slim.js
-kallithea/public/codemirror/mode/smalltalk/
-kallithea/public/codemirror/mode/smalltalk/smalltalk.js
-kallithea/public/codemirror/mode/smarty/
-kallithea/public/codemirror/mode/smarty/smarty.js
-kallithea/public/codemirror/mode/smartymixed/
-kallithea/public/codemirror/mode/smartymixed/smartymixed.js
-kallithea/public/codemirror/mode/solr/
-kallithea/public/codemirror/mode/solr/solr.js
-kallithea/public/codemirror/mode/sparql/
-kallithea/public/codemirror/mode/sparql/sparql.js
-kallithea/public/codemirror/mode/sql/
-kallithea/public/codemirror/mode/sql/sql.js
-kallithea/public/codemirror/mode/stex/
-kallithea/public/codemirror/mode/stex/stex.js
-kallithea/public/codemirror/mode/tcl/
-kallithea/public/codemirror/mode/tcl/tcl.js
-kallithea/public/codemirror/mode/textile/
-kallithea/public/codemirror/mode/textile/textile.js
-kallithea/public/codemirror/mode/tiddlywiki/
-kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.css
-kallithea/public/codemirror/mode/tiddlywiki/tiddlywiki.js
-kallithea/public/codemirror/mode/tiki/
-kallithea/public/codemirror/mode/tiki/tiki.css
-kallithea/public/codemirror/mode/tiki/tiki.js
-kallithea/public/codemirror/mode/toml/
-kallithea/public/codemirror/mode/toml/toml.js
-kallithea/public/codemirror/mode/tornado/
-kallithea/public/codemirror/mode/tornado/tornado.js
-kallithea/public/codemirror/mode/turtle/
-kallithea/public/codemirror/mode/turtle/turtle.js
-kallithea/public/codemirror/mode/vb/
-kallithea/public/codemirror/mode/vb/vb.js
-kallithea/public/codemirror/mode/vbscript/
-kallithea/public/codemirror/mode/vbscript/vbscript.js
-kallithea/public/codemirror/mode/velocity/
-kallithea/public/codemirror/mode/velocity/velocity.js
-kallithea/public/codemirror/mode/verilog/
-kallithea/public/codemirror/mode/verilog/verilog.js
-kallithea/public/codemirror/mode/xml/
-kallithea/public/codemirror/mode/xml/xml.js
-kallithea/public/codemirror/mode/xquery/
-kallithea/public/codemirror/mode/xquery/xquery.js
-kallithea/public/codemirror/mode/yaml/
-kallithea/public/codemirror/mode/yaml/yaml.js
-kallithea/public/codemirror/mode/z80/
-kallithea/public/codemirror/mode/z80/z80.js
-kallithea/public/css/
-kallithea/public/css/bootstrap.css
-kallithea/public/css/contextbar.css
-kallithea/public/css/mergely.css
-kallithea/public/css/pygments.css
-kallithea/public/css/style.css
-kallithea/public/fontello/
-kallithea/public/fontello/README-kallithea.txt
-kallithea/public/fontello/README.txt
-kallithea/public/fontello/config.json
-kallithea/public/fontello/css/
-kallithea/public/fontello/css/kallithea.css
-kallithea/public/fontello/font/
-kallithea/public/fontello/font/kallithea.eot
-kallithea/public/fontello/font/kallithea.svg
-kallithea/public/fontello/font/kallithea.ttf
-kallithea/public/fontello/font/kallithea.woff
-kallithea/public/images/
-kallithea/public/images/background.png
-kallithea/public/images/favicon.ico
-kallithea/public/images/kallithea-logo.png
-kallithea/public/images/kallithea-logo.svg
-kallithea/public/images/pager.png
-kallithea/public/images/pager_selected.png
-kallithea/public/js/
-kallithea/public/js/base.js
-kallithea/public/js/bootstrap.js
-kallithea/public/js/codemirror_loadmode.js
-kallithea/public/js/graph.js
-kallithea/public/js/jquery-1.11.1.min.js
-kallithea/public/js/mergely.js
-kallithea/public/js/mousetrap.js
-kallithea/public/js/native.history.js
-kallithea/public/js/select2/
-kallithea/public/js/select2/select2-bootstrap.css
-kallithea/public/js/select2/select2-spinner.gif
-kallithea/public/js/select2/select2.css
-kallithea/public/js/select2/select2.js
-kallithea/public/js/select2/select2.png
-kallithea/public/js/select2/select2x2.png
-kallithea/public/js/yui.2.9.js
-kallithea/public/js/yui.flot.js
-kallithea/templates/
-kallithea/templates/about.html
-kallithea/templates/admin/
-kallithea/templates/admin/admin.html
-kallithea/templates/admin/admin_log.html
-kallithea/templates/admin/auth/
-kallithea/templates/admin/auth/auth_settings.html
-kallithea/templates/admin/defaults/
-kallithea/templates/admin/defaults/defaults.html
-kallithea/templates/admin/gists/
-kallithea/templates/admin/gists/edit.html
-kallithea/templates/admin/gists/index.html
-kallithea/templates/admin/gists/new.html
-kallithea/templates/admin/gists/show.html
-kallithea/templates/admin/my_account/
-kallithea/templates/admin/my_account/my_account.html
-kallithea/templates/admin/my_account/my_account_api_keys.html
-kallithea/templates/admin/my_account/my_account_emails.html
-kallithea/templates/admin/my_account/my_account_password.html
-kallithea/templates/admin/my_account/my_account_perms.html
-kallithea/templates/admin/my_account/my_account_profile.html
-kallithea/templates/admin/my_account/my_account_repos.html
-kallithea/templates/admin/my_account/my_account_watched.html
-kallithea/templates/admin/notifications/
-kallithea/templates/admin/notifications/notifications.html
-kallithea/templates/admin/notifications/notifications_data.html
-kallithea/templates/admin/notifications/show_notification.html
-kallithea/templates/admin/permissions/
-kallithea/templates/admin/permissions/permissions.html
-kallithea/templates/admin/permissions/permissions_globals.html
-kallithea/templates/admin/permissions/permissions_ips.html
-kallithea/templates/admin/permissions/permissions_perms.html
-kallithea/templates/admin/repo_groups/
-kallithea/templates/admin/repo_groups/repo_group_add.html
-kallithea/templates/admin/repo_groups/repo_group_edit.html
-kallithea/templates/admin/repo_groups/repo_group_edit_advanced.html
-kallithea/templates/admin/repo_groups/repo_group_edit_perms.html
-kallithea/templates/admin/repo_groups/repo_group_edit_settings.html
-kallithea/templates/admin/repo_groups/repo_group_show.html
-kallithea/templates/admin/repo_groups/repo_groups.html
-kallithea/templates/admin/repos/
-kallithea/templates/admin/repos/repo_add.html
-kallithea/templates/admin/repos/repo_add_base.html
-kallithea/templates/admin/repos/repo_creating.html
-kallithea/templates/admin/repos/repo_edit.html
-kallithea/templates/admin/repos/repo_edit_advanced.html
-kallithea/templates/admin/repos/repo_edit_caches.html
-kallithea/templates/admin/repos/repo_edit_fields.html
-kallithea/templates/admin/repos/repo_edit_fork.html
-kallithea/templates/admin/repos/repo_edit_permissions.html
-kallithea/templates/admin/repos/repo_edit_remote.html
-kallithea/templates/admin/repos/repo_edit_settings.html
-kallithea/templates/admin/repos/repo_edit_statistics.html
-kallithea/templates/admin/repos/repos.html
-kallithea/templates/admin/settings/
-kallithea/templates/admin/settings/settings.html
-kallithea/templates/admin/settings/settings_email.html
-kallithea/templates/admin/settings/settings_global.html
-kallithea/templates/admin/settings/settings_hooks.html
-kallithea/templates/admin/settings/settings_mapping.html
-kallithea/templates/admin/settings/settings_search.html
-kallithea/templates/admin/settings/settings_system.html
-kallithea/templates/admin/settings/settings_system_update.html
-kallithea/templates/admin/settings/settings_vcs.html
-kallithea/templates/admin/settings/settings_visual.html
-kallithea/templates/admin/user_groups/
-kallithea/templates/admin/user_groups/user_group_add.html
-kallithea/templates/admin/user_groups/user_group_edit.html
-kallithea/templates/admin/user_groups/user_group_edit_advanced.html
-kallithea/templates/admin/user_groups/user_group_edit_default_perms.html
-kallithea/templates/admin/user_groups/user_group_edit_members.html
-kallithea/templates/admin/user_groups/user_group_edit_perms.html
-kallithea/templates/admin/user_groups/user_group_edit_settings.html
-kallithea/templates/admin/user_groups/user_groups.html
-kallithea/templates/admin/users/
-kallithea/templates/admin/users/user_add.html
-kallithea/templates/admin/users/user_edit.html
-kallithea/templates/admin/users/user_edit_advanced.html
-kallithea/templates/admin/users/user_edit_api_keys.html
-kallithea/templates/admin/users/user_edit_emails.html
-kallithea/templates/admin/users/user_edit_ips.html
-kallithea/templates/admin/users/user_edit_perms.html
-kallithea/templates/admin/users/user_edit_profile.html
-kallithea/templates/admin/users/users.html
-kallithea/templates/base/
-kallithea/templates/base/base.html
-kallithea/templates/base/default_perms_box.html
-kallithea/templates/base/flash_msg.html
-kallithea/templates/base/perms_summary.html
-kallithea/templates/base/root.html
-kallithea/templates/bookmarks/
-kallithea/templates/bookmarks/bookmarks.html
-kallithea/templates/bookmarks/bookmarks_data.html
-kallithea/templates/branches/
-kallithea/templates/branches/branches.html
-kallithea/templates/branches/branches_data.html
-kallithea/templates/changelog/
-kallithea/templates/changelog/changelog.html
-kallithea/templates/changelog/changelog_details.html
-kallithea/templates/changelog/changelog_summary_data.html
-kallithea/templates/changeset/
-kallithea/templates/changeset/changeset.html
-kallithea/templates/changeset/changeset_comment_block.html
-kallithea/templates/changeset/changeset_file_comment.html
-kallithea/templates/changeset/changeset_range.html
-kallithea/templates/changeset/diff_block.html
-kallithea/templates/changeset/patch_changeset.html
-kallithea/templates/compare/
-kallithea/templates/compare/compare_cs.html
-kallithea/templates/compare/compare_diff.html
-kallithea/templates/data_table/
-kallithea/templates/data_table/_dt_elements.html
-kallithea/templates/email_templates/
-kallithea/templates/email_templates/changeset_comment.html
-kallithea/templates/email_templates/changeset_comment.txt
-kallithea/templates/email_templates/default.html
-kallithea/templates/email_templates/default.txt
-kallithea/templates/email_templates/main.html
-kallithea/templates/email_templates/main.txt
-kallithea/templates/email_templates/password_reset.html
-kallithea/templates/email_templates/password_reset.txt
-kallithea/templates/email_templates/pull_request.html
-kallithea/templates/email_templates/pull_request.txt
-kallithea/templates/email_templates/pull_request_comment.html
-kallithea/templates/email_templates/pull_request_comment.txt
-kallithea/templates/email_templates/registration.html
-kallithea/templates/email_templates/registration.txt
-kallithea/templates/errors/
-kallithea/templates/errors/error_document.html
-kallithea/templates/files/
-kallithea/templates/files/diff_2way.html
-kallithea/templates/files/file_diff.html
-kallithea/templates/files/files.html
-kallithea/templates/files/files_add.html
-kallithea/templates/files/files_browser.html
-kallithea/templates/files/files_delete.html
-kallithea/templates/files/files_edit.html
-kallithea/templates/files/files_history_box.html
-kallithea/templates/files/files_source.html
-kallithea/templates/files/files_ypjax.html
-kallithea/templates/followers/
-kallithea/templates/followers/followers.html
-kallithea/templates/followers/followers_data.html
-kallithea/templates/forks/
-kallithea/templates/forks/fork.html
-kallithea/templates/forks/forks.html
-kallithea/templates/forks/forks_data.html
-kallithea/templates/index.html
-kallithea/templates/index_base.html
-kallithea/templates/journal/
-kallithea/templates/journal/journal.html
-kallithea/templates/journal/journal_data.html
-kallithea/templates/journal/public_journal.html
-kallithea/templates/login.html
-kallithea/templates/password_reset.html
-kallithea/templates/password_reset_confirmation.html
-kallithea/templates/pullrequests/
-kallithea/templates/pullrequests/pullrequest.html
-kallithea/templates/pullrequests/pullrequest_data.html
-kallithea/templates/pullrequests/pullrequest_show.html
-kallithea/templates/pullrequests/pullrequest_show_all.html
-kallithea/templates/pullrequests/pullrequest_show_my.html
-kallithea/templates/register.html
-kallithea/templates/search/
-kallithea/templates/search/search.html
-kallithea/templates/search/search_commit.html
-kallithea/templates/search/search_content.html
-kallithea/templates/search/search_path.html
-kallithea/templates/search/search_repository.html
-kallithea/templates/summary/
-kallithea/templates/summary/statistics.html
-kallithea/templates/summary/summary.html
-kallithea/templates/switch_to_list.html
-kallithea/templates/tags/
-kallithea/templates/tags/tags.html
-kallithea/templates/tags/tags_data.html
-kallithea/tests/
-kallithea/tests/__init__.py
-kallithea/tests/api/
-kallithea/tests/api/__init__.py
-kallithea/tests/api/api_base.py
-kallithea/tests/api/test_api_git.py
-kallithea/tests/api/test_api_hg.py
-kallithea/tests/conftest.py
-kallithea/tests/fixture.py
-kallithea/tests/fixtures/
-kallithea/tests/fixtures/diff_with_diff_data.diff
-kallithea/tests/fixtures/git_diff_binary_and_normal.diff
-kallithea/tests/fixtures/git_diff_chmod.diff
-kallithea/tests/fixtures/git_diff_mod_single_binary_file.diff
-kallithea/tests/fixtures/git_diff_modify_binary_file.diff
-kallithea/tests/fixtures/git_diff_rename_file.diff
-kallithea/tests/fixtures/git_node_history_response.json
-kallithea/tests/fixtures/hg_diff_add_single_binary_file.diff
-kallithea/tests/fixtures/hg_diff_binary_and_normal.diff
-kallithea/tests/fixtures/hg_diff_chmod.diff
-kallithea/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff
-kallithea/tests/fixtures/hg_diff_copy_and_chmod_file.diff
-kallithea/tests/fixtures/hg_diff_copy_and_modify_file.diff
-kallithea/tests/fixtures/hg_diff_copy_chmod_and_edit_file.diff
-kallithea/tests/fixtures/hg_diff_copy_file.diff
-kallithea/tests/fixtures/hg_diff_del_single_binary_file.diff
-kallithea/tests/fixtures/hg_diff_mod_file_and_rename.diff
-kallithea/tests/fixtures/hg_diff_mod_single_binary_file.diff
-kallithea/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff
-kallithea/tests/fixtures/hg_diff_rename_and_chmod_file.diff
-kallithea/tests/fixtures/hg_diff_rename_file.diff
-kallithea/tests/fixtures/hg_diff_rename_space_cr.diff
-kallithea/tests/fixtures/hg_node_history_response.json
-kallithea/tests/fixtures/journal_dump.csv
-kallithea/tests/fixtures/markuptest.diff
-kallithea/tests/fixtures/vcs_test_git.tar.gz
-kallithea/tests/fixtures/vcs_test_hg.tar.gz
-kallithea/tests/functional/
-kallithea/tests/functional/__init__.py
-kallithea/tests/functional/test_admin.py
-kallithea/tests/functional/test_admin_auth_settings.py
-kallithea/tests/functional/test_admin_defaults.py
-kallithea/tests/functional/test_admin_gists.py
-kallithea/tests/functional/test_admin_notifications.py
-kallithea/tests/functional/test_admin_permissions.py
-kallithea/tests/functional/test_admin_repo_groups.py
-kallithea/tests/functional/test_admin_repos.py
-kallithea/tests/functional/test_admin_settings.py
-kallithea/tests/functional/test_admin_user_groups.py
-kallithea/tests/functional/test_admin_users.py
-kallithea/tests/functional/test_branches.py
-kallithea/tests/functional/test_changelog.py
-kallithea/tests/functional/test_changeset.py
-kallithea/tests/functional/test_changeset_comments.py
-kallithea/tests/functional/test_compare.py
-kallithea/tests/functional/test_compare_local.py
-kallithea/tests/functional/test_feed.py
-kallithea/tests/functional/test_files.py
-kallithea/tests/functional/test_followers.py
-kallithea/tests/functional/test_forks.py
-kallithea/tests/functional/test_home.py
-kallithea/tests/functional/test_journal.py
-kallithea/tests/functional/test_login.py
-kallithea/tests/functional/test_my_account.py
-kallithea/tests/functional/test_pullrequests.py
-kallithea/tests/functional/test_repo_groups.py
-kallithea/tests/functional/test_search.py
-kallithea/tests/functional/test_summary.py
-kallithea/tests/functional/test_tags.py
-kallithea/tests/models/
-kallithea/tests/models/__init__.py
-kallithea/tests/models/common.py
-kallithea/tests/models/test_changeset_status.py
-kallithea/tests/models/test_diff_parsers.py
-kallithea/tests/models/test_notifications.py
-kallithea/tests/models/test_permissions.py
-kallithea/tests/models/test_repo_groups.py
-kallithea/tests/models/test_repos.py
-kallithea/tests/models/test_user_group_permissions_on_repo_groups.py
-kallithea/tests/models/test_user_groups.py
-kallithea/tests/models/test_user_permissions_on_repo_groups.py
-kallithea/tests/models/test_user_permissions_on_repos.py
-kallithea/tests/models/test_users.py
-kallithea/tests/other/
-kallithea/tests/other/__init__.py
-kallithea/tests/other/manual_test_vcs_operations.py
-kallithea/tests/other/test_libs.py
-kallithea/tests/other/test_mail.py
-kallithea/tests/other/test_validators.py
-kallithea/tests/parameterized.py
-kallithea/tests/scripts/
-kallithea/tests/scripts/create_rc.sh
-kallithea/tests/scripts/manual_test_concurrency.py
-kallithea/tests/scripts/manual_test_crawler.py
-kallithea/tests/scripts/mem_watch
-kallithea/tests/test.ini
-kallithea/tests/vcs/
-kallithea/tests/vcs/__init__.py
-kallithea/tests/vcs/aconfig
-kallithea/tests/vcs/base.py
-kallithea/tests/vcs/conf.py
-kallithea/tests/vcs/test_archives.py
-kallithea/tests/vcs/test_branches.py
-kallithea/tests/vcs/test_changesets.py
-kallithea/tests/vcs/test_filenodes_unicode_path.py
-kallithea/tests/vcs/test_getitem.py
-kallithea/tests/vcs/test_getslice.py
-kallithea/tests/vcs/test_git.py
-kallithea/tests/vcs/test_hg.py
-kallithea/tests/vcs/test_inmemchangesets.py
-kallithea/tests/vcs/test_nodes.py
-kallithea/tests/vcs/test_repository.py
-kallithea/tests/vcs/test_tags.py
-kallithea/tests/vcs/test_utils.py
-kallithea/tests/vcs/test_utils_filesize.py
-kallithea/tests/vcs/test_vcs.py
-kallithea/tests/vcs/test_workdirs.py
-kallithea/tests/vcs/utils.py
-kallithea/websetup.py
-setup.cfg
-setup.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/run-all-cleanup	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# Convenience script for running various idempotent source code cleanup scripts
+
+set -e
+set -x
+
+scripts/docs-headings.py
+scripts/generate-ini.py
+scripts/whitespacecleanup.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/shortlog.py	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,34 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+"""
+Kallithea script for generating a quick overview of contributors and their
+commit counts in a given revision set.
+"""
+import argparse
+import os
+from collections import Counter
+import contributor_data
+
+def main():
+
+    parser = argparse.ArgumentParser(description='Generate a list of committers and commit counts.')
+    parser.add_argument('revset',
+                        help='revision set specifying the commits to count')
+    args = parser.parse_args()
+
+    repo_entries = [
+        (contributor_data.name_fixes.get(name) or contributor_data.name_fixes.get(name.rsplit('<', 1)[0].strip()) or name).rsplit('<', 1)[0].strip()
+        for name in (line.strip()
+         for line in os.popen("""hg log -r '%s' -T '{author}\n'""" % args.revset).readlines())
+        ]
+
+    counter = Counter(repo_entries)
+    for name, count in counter.most_common():
+        if name == '':
+            continue
+        print('%4s %s' % (count, name))
+
+
+if __name__ == '__main__':
+    main()
--- a/scripts/update-copyrights.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/scripts/update-copyrights.py	Sun Mar 31 21:28:56 2019 +0200
@@ -38,99 +38,10 @@
 contributor, the legal entity is given credit too.
 """
 
-
-# Some committers are so wrong that it doesn't point at any contributor:
-total_ignore = set()
-total_ignore.add('*** failed to import extension hggit: No module named hggit')
-total_ignore.add('<>')
-
-# Normalize some committer names where people have contributed under different
-# names or email addresses:
-name_fixes = {}
-name_fixes['Andrew Shadura'] = "Andrew Shadura <andrew@shadura.me>"
-name_fixes['aparkar'] = "Aparkar <aparkar@icloud.com>"
-name_fixes['Aras Pranckevicius'] = "Aras Pranckevičius <aras@unity3d.com>"
-name_fixes['Augosto Hermann'] = "Augusto Herrmann <augusto.herrmann@planejamento.gov.br>"
-name_fixes['"Bradley M. Kuhn" <bkuhn@ebb.org>'] = "Bradley M. Kuhn <bkuhn@sfconservancy.org>"
-name_fixes['dmitri.kuznetsov'] = "Dmitri Kuznetsov"
-name_fixes['Dmitri Kuznetsov'] = "Dmitri Kuznetsov"
-name_fixes['domruf'] = "Dominik Ruf <dominikruf@gmail.com>"
-name_fixes['Ingo von borstel'] = "Ingo von Borstel <kallithea@planetmaker.de>"
-name_fixes['Jan Heylen'] = "Jan Heylen <heyleke@gmail.com>"
-name_fixes['Jason F. Harris'] = "Jason Harris <jason@jasonfharris.com>"
-name_fixes['Jelmer Vernooij'] = "Jelmer Vernooij <jelmer@samba.org>"
-name_fixes['jfh <jason@jasonfharris.com>'] = "Jason Harris <jason@jasonfharris.com>"
-name_fixes['Leonardo Carneiro<leonardo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
-name_fixes['leonardo'] = "Leonardo Carneiro <leonardo@unity3d.com>"
-name_fixes['Leonardo <leo@unity3d.com>'] = "Leonardo Carneiro <leonardo@unity3d.com>"
-name_fixes['Les Peabody'] = "Les Peabody <lpeabody@gmail.com>"
-name_fixes['"Lorenzo M. Catucci" <lorenzo@sancho.ccd.uniroma2.it>'] = "Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>"
-name_fixes['Lukasz Balcerzak'] = "Łukasz Balcerzak <lukaszbalcerzak@gmail.com>"
-name_fixes['mao <mao@lins.fju.edu.tw>'] = "Ching-Chen Mao <mao@lins.fju.edu.tw>"
-name_fixes['marcink'] = "Marcin Kuźmiński <marcin@python-works.com>"
-name_fixes['Marcin Kuzminski'] = "Marcin Kuźmiński <marcin@python-works.com>"
-name_fixes['nansenat16@null.tw'] = "nansenat16 <nansenat16@null.tw>"
-name_fixes['Peter Vitt'] = "Peter Vitt <petervitt@web.de>"
-name_fixes['philip.j@hostdime.com'] = "Philip Jameson <philip.j@hostdime.com>"
-name_fixes['Søren Løvborg'] = "Søren Løvborg <sorenl@unity3d.com>"
-name_fixes['Thomas De Schampheleire'] = "Thomas De Schampheleire <thomas.de_schampheleire@nokia.com>"
-name_fixes['Weblate'] = "<>"
-name_fixes['xpol'] = "xpol <xpolife@gmail.com>"
-
-
-# Some committer email address domains that indicate that another entity might
-# hold some copyright too:
-domain_extra = {}
-domain_extra['unity3d.com'] = "Unity Technologies"
-domain_extra['rhodecode.com'] = "RhodeCode GmbH"
-
-# Repository history show some old contributions that traditionally hasn't been
-# listed in about.html - preserve that:
-no_about = set(total_ignore)
-# The following contributors were traditionally not listed in about.html and it
-# seems unclear if the copyright is personal or belongs to a company.
-no_about.add(('Thayne Harbaugh <thayne@fusionio.com>', '2011'))
-no_about.add(('Dies Koper <diesk@fast.au.fujitsu.com>', '2012'))
-no_about.add(('Erwin Kroon <e.kroon@smartmetersolutions.nl>', '2012'))
-no_about.add(('Vincent Caron <vcaron@bearstech.com>', '2012'))
-# These contributors' contributions might be too small to be copyrightable:
-no_about.add(('philip.j@hostdime.com', '2012'))
-no_about.add(('Stefan Engel <mail@engel-stefan.de>', '2012'))
-no_about.add(('Ton Plomp <tcplomp@gmail.com>', '2013'))
-# Was reworked and contributed later and shadowed by other contributions:
-no_about.add(('Sean Farley <sean.michael.farley@gmail.com>', '2013'))
-
-# Preserve contributors listed in about.html but not appearing in repository
-# history:
-other_about = [
-    ("2011", "Aparkar <aparkar@icloud.com>"),
-    ("2010", "RhodeCode GmbH"),
-    ("2011", "RhodeCode GmbH"),
-    ("2012", "RhodeCode GmbH"),
-    ("2013", "RhodeCode GmbH"),
-]
-
-# Preserve contributors listed in CONTRIBUTORS but not appearing in repository
-# history:
-other_contributors = [
-    ("", "Andrew Kesterson <andrew@aklabs.net>"),
-    ("", "cejones"),
-    ("", "David A. Sjøen <david.sjoen@westcon.no>"),
-    ("", "James Rhodes <jrhodes@redpointsoftware.com.au>"),
-    ("", "Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>"),
-    ("", "larikale"),
-    ("", "RhodeCode GmbH"),
-    ("", "Sebastian Kreutzberger <sebastian@rhodecode.com>"),
-    ("", "Steve Romanow <slestak989@gmail.com>"),
-    ("", "SteveCohen"),
-    ("", "Thomas <thomas@rhodecode.com>"),
-    ("", "Thomas Waldmann <tw-public@gmx.de>"),
-]
-
-
 import os
 import re
 from collections import defaultdict
+import contributor_data
 
 
 def sortkey(x):
@@ -143,7 +54,7 @@
     return (x[0] and -int(x[0][-1]),
             x[0] and int(x[0][0]),
             -len(x[0]),
-            x[1].decode('utf8').lower().replace(u'\xe9', u'e').replace(u'\u0142', u'l')
+            x[1].decode('utf-8').lower().replace(u'\xe9', u'e').replace(u'\u0142', u'l')
         )
 
 
@@ -197,10 +108,10 @@
          for name, years in name_years.items()]
     l.sort(key=sortkey)
 
-    with file(filename) as f:
+    with open(filename) as f:
         pre, post = re.split(split_re, f.read())
 
-    with file(filename, 'w') as f:
+    with open(filename, 'w') as f:
         f.write(pre +
                 ''.join(format_f(years, name) for years, name in l) +
                 post)
@@ -208,7 +119,7 @@
 
 def main():
     repo_entries = [
-        (year, name_fixes.get(name) or name_fixes.get(name.rsplit('<', 1)[0].strip()) or name)
+        (year, contributor_data.name_fixes.get(name) or contributor_data.name_fixes.get(name.rsplit('<', 1)[0].strip()) or name)
         for year, name in
         (line.strip().split(' ', 1)
          for line in os.popen("""hg log -r '::.' -T '{date(date,"%Y")} {author}\n'""").readlines())
@@ -216,9 +127,9 @@
 
     insert_entries(
         filename='kallithea/templates/about.html',
-        all_entries=repo_entries + other_about,
-        no_entries=no_about,
-        domain_extra=domain_extra,
+        all_entries=repo_entries + contributor_data.other_about,
+        no_entries=contributor_data.no_about,
+        domain_extra=contributor_data.domain_extra,
         split_re=r'(?:  <li>Copyright &copy; [^\n]*</li>\n)*',
         normalize_name=lambda name: name.split('<', 1)[0].strip(),
         format_f=lambda years, name: '  <li>Copyright &copy; %s, %s</li>\n' % (nice_years(years, '&ndash;', ', '), name),
@@ -226,9 +137,9 @@
 
     insert_entries(
         filename='CONTRIBUTORS',
-        all_entries=repo_entries + other_contributors,
-        no_entries=total_ignore,
-        domain_extra=domain_extra,
+        all_entries=repo_entries + contributor_data.other_contributors,
+        no_entries=contributor_data.total_ignore,
+        domain_extra=contributor_data.domain_extra,
         split_re=r'(?:    [^\n]*\n)*',
         normalize_name=lambda name: name,
         format_f=lambda years, name: ('    %s%s%s\n' % (name, ' ' if years else '', nice_years(years))),
@@ -237,7 +148,7 @@
     insert_entries(
         filename='kallithea/templates/base/base.html',
         all_entries=repo_entries,
-        no_entries=total_ignore,
+        no_entries=contributor_data.total_ignore,
         domain_extra={},
         split_re=r'(?<=&copy;) .* (?=by various authors)',
         normalize_name=lambda name: '',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/validate-commits	Sun Mar 31 21:28:56 2019 +0200
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+# Validate the specified commits against test suite and other checks.
+
+if [ -n "$VIRTUAL_ENV" ]; then
+    echo "Please run this script from outside a virtualenv."
+    exit 1
+fi
+
+if ! hg update --check -q .; then
+    echo "Working dir is not clean, please commit/revert changes first."
+    exit 1
+fi
+
+venv=$(mktemp -d kallithea-validatecommits-env-XXXXXX)
+resultfile=$(mktemp kallithea-validatecommits-result-XXXXXX)
+echo > "$resultfile"
+
+cleanup()
+{
+    rm -rf /tmp/kallithea-test*
+    rm -rf "$venv"
+}
+finish()
+{
+    cleanup
+    # print (possibly intermediate) results
+    cat "$resultfile"
+    rm "$resultfile"
+}
+trap finish EXIT
+
+for rev in $(hg log -r "$1" -T '{node}\n'); do
+    hg log -r "$rev"
+    hg update "$rev"
+
+    cleanup
+    virtualenv -p "$(command -v python2)" "$venv"
+    source "$venv/bin/activate"
+    pip install --upgrade pip setuptools
+    pip install -e .
+    pip install -r dev_requirements.txt
+    pip install python-ldap python-pam
+
+    # run-all-cleanup
+    scripts/run-all-cleanup
+    if ! hg update --check -q .; then
+        echo "run-all-cleanup did not give clean results!"
+        result="NOK"
+        hg diff
+        hg revert -a
+    else
+        result=" OK"
+    fi
+    echo "$result: $rev (run-all-cleanup)" >> "$resultfile"
+
+    # pytest
+    if py.test; then
+        result=" OK"
+    else
+        result="NOK"
+    fi
+    echo "$result: $rev (pytest)" >> "$resultfile"
+
+    deactivate
+    echo
+done
--- a/scripts/whitespacecleanup.sh	Sun Mar 03 21:36:25 2019 +0100
+++ b/scripts/whitespacecleanup.sh	Sun Mar 31 21:28:56 2019 +0200
@@ -2,22 +2,18 @@
 
 # Enforce some consistency in whitespace - just to avoid spurious whitespaces changes
 
-files=`hg loc '*.py' '*.html' '*.css' '*.rst' '*.txt' '*.js' '*.ini' '*.cfg' CONTRIBUTORS LICENSE.md| egrep -v '/lockfiles.py|LICENSE-MERGELY.html|/codemirror/|/fontello/|(graph|mergely|native.history|select2/select2|yui.flot|yui.2.9)\.js$'`
+files=`hg mani | egrep -v '/fontello/|/email_templates/|(/lockfiles.py|^LICENSE-MERGELY.html|^docs/Makefile|^scripts/whitespacecleanup.sh|/(graph|mergely|native.history)\.js|/test_dump_html_mails.ref.html|\.png|\.gif|\.ico|\.pot|\.po|\.mo|\.tar\.gz|\.diff)$'`
 
+sed -i "s/`printf '\r'`//g" $files
 sed -i -e "s,`printf '\t'`,    ,g" $files
 sed -i -e "s,  *$,,g" $files
+sed -i -e 's,\([^ ]\)\\$,\1 \\,g' -e 's,\(["'"'"']["'"'"']["'"'"']\) \\$,\1\\,g' $files
 # ensure one trailing newline - remove empty last line and make last line include trailing newline:
 sed -i -e '$,${/^$/d}' -e '$a\' $files
 
 sed -i -e 's,\([^ /]\){,\1 {,g' `hg loc '*.css'`
 sed -i -e 's|^\([^ /].*,\)\([^ ]\)|\1 \2|g' `hg loc '*.css'`
 
-sed -i -e 's/^\(    [^: ]*\) *: *\([^/]\)/\1: \2/g' kallithea/public/css/{style,contextbar}.css
-sed -i -e '1s|, |,|g' kallithea/public/css/{style,contextbar}.css
-sed -i -e 's/^\([^ ,/]\+ [^,]*[^ ,]\) *, *\(.\)/\1,\n\2/g' kallithea/public/css/{style,contextbar}.css
-sed -i -e 's/^\([^ ,/].*\)   */\1 /g' kallithea/public/css/{style,contextbar}.css
-sed -i -e 's,^--$,-- ,g' kallithea/templates/email_templates/main.txt
-
 hg mani | xargs chmod -x
 hg loc 'set:!binary()&grep("^#!")&!(**_tmpl.py)&!(**/template**)' | xargs chmod +x
 
--- a/setup.cfg	Sun Mar 03 21:36:25 2019 +0100
+++ b/setup.cfg	Sun Mar 31 21:28:56 2019 +0200
@@ -3,22 +3,8 @@
 tag_svn_revision = 0
 tag_date = 0
 
-[nosetests]
-verbose = True
-verbosity = 2
-with-pylons = kallithea/tests/test.ini
-detailed-errors = 1
-nologcapture = 1
-
-[pytest]
-# only look for tests in kallithea/tests
-python_files = kallithea/tests/**/test_*.py
-addopts =
-    # --verbose
-    # show extra test summary info as specified by chars (f)ailed, (E)error, (s)skipped, (x)failed, (X)passed, (w)warnings.
-    -rfEsxXw
-    # Shorter scrollbacks; less stuff to scroll through
-    --tb=short
+[aliases]
+test = pytest
 
 [compile_catalog]
 domain = kallithea
--- a/setup.py	Sun Mar 03 21:36:25 2019 +0100
+++ b/setup.py	Sun Mar 31 21:28:56 2019 +0200
@@ -4,7 +4,7 @@
 import sys
 import platform
 
-if sys.version_info < (2, 6):
+if sys.version_info < (2, 6) or sys.version_info >= (3,):
     raise Exception('Kallithea requires python 2.6 or 2.7')
 
 
@@ -35,39 +35,41 @@
 is_windows = __platform__ in ['Windows']
 
 requirements = [
-    "setuptools<34", # setuptools==34 has an undeclared requirement of pyparsing >=2.1, but celery<2.3 requires pyparsing<2
-    "waitress==0.8.8",
-    "webob>=1.0.8,<=1.1.1",
-    "webtest==1.4.3",
-    "Pylons>=1.0.0,<=1.0.3",
-    "Beaker==1.6.4",
-    "WebHelpers==1.3",
-    "formencode>=1.2.4,<=1.2.6",
-    "SQLAlchemy==0.7.10",
-    "Mako>=0.9.0,<=1.0.0",
-    "pygments>=1.5",
-    "whoosh>=2.4.0,<=2.5.7",
-    "celery>=2.2.5,<2.3",
-    "babel>=0.9.6,<=1.3",
-    "python-dateutil>=1.5.0,<2.0.0",
-    "markdown==2.2.1",
-    "docutils>=0.8.1,<=0.11",
-    "mock",
-    "URLObject==2.3.4",
-    "Routes==1.13",
-    "dulwich>=0.9.9,<=0.9.9",
-    "mercurial>=2.9,<4.3",
+    "alembic >= 0.8.0, < 1.1",
+    "gearbox < 1",
+    "waitress >= 0.8.8, < 1.2",
+    "WebOb >= 1.7, < 1.8", # turbogears2 2.3.12 requires WebOb<1.8.0
+    "backlash >= 0.1.2, < 1",
+    "TurboGears2 >= 2.3.10, < 3",
+    "tgext.routes >= 0.2.0, < 1",
+    "Beaker >= 1.7.0, < 2",
+    "WebHelpers >= 1.3, < 1.4",
+    "FormEncode >= 1.2.4, < 1.4",
+    "SQLAlchemy >= 1.1, < 1.3",
+    "Mako >= 0.9.0, < 1.1",
+    "Pygments >= 2.0, < 2.3",
+    "Whoosh >= 2.5.0, < 2.8",
+    "celery >= 3.1, < 4.0", # celery 4 doesn't work
+    "Babel >= 1.3, < 2.7",
+    "python-dateutil >= 1.5.0, < 2.8",
+    "Markdown >= 2.2.1, < 2.7",
+    "docutils >= 0.11, < 0.15",
+    "URLObject >= 2.3.4, < 2.5",
+    "Routes >= 1.13, < 2",
+    "dulwich >= 0.14.1, < 0.20",
+    "mercurial >= 4.1.1, < 4.10",
+    "decorator >= 3.3.2, < 4.4",
+    "Paste >= 2.0.3, < 3.1",
     "bleach >= 3.0, < 3.1",
+    "Click >= 7.0, < 8",
 ]
 
 if sys.version_info < (2, 7):
-    requirements.append("importlib==1.0.1")
-    requirements.append("unittest2")
+    requirements.append("importlib == 1.0.1")
     requirements.append("argparse")
 
 if not is_windows:
-    requirements.append("py-bcrypt>=0.3.0,<=0.4")
-
+    requirements.append("bcrypt >= 3.1.0, < 3.2")
 
 dependency_links = [
 ]
@@ -90,9 +92,6 @@
 # relative to sys.prefix
 data_files = []
 
-# additional files that goes into package itself
-package_data = {'kallithea': ['i18n/*/LC_MESSAGES/*.mo', ], }
-
 description = ('Kallithea is a fast and powerful management tool '
                'for Mercurial and Git with a built in push/pull server, '
                'full text search and code-review.')
@@ -105,24 +104,16 @@
 
 # long description
 README_FILE = 'README.rst'
-CHANGELOG_FILE = 'docs/changelog.rst'
 try:
-    long_description = open(README_FILE).read() + '\n\n' + \
-        open(CHANGELOG_FILE).read()
-
+    long_description = open(README_FILE).read()
 except IOError as err:
     sys.stderr.write(
-        "[WARNING] Cannot find file specified as long_description (%s)\n or "
-        "changelog (%s) skipping that file" % (README_FILE, CHANGELOG_FILE)
+        "[WARNING] Cannot find file specified as long_description (%s)\n"
+        % README_FILE
     )
     long_description = description
 
-try:
-    from setuptools import setup, find_packages
-except ImportError:
-    from ez_setup import use_setuptools
-    use_setuptools()
-    from setuptools import setup, find_packages
+import setuptools
 
 # monkey patch setuptools to use distutils owner/group functionality
 from setuptools.command import sdist
@@ -133,10 +124,9 @@
         self.owner = self.group = 'root'
 sdist.sdist = sdist_new
 
-# packages
-packages = find_packages(exclude=['ez_setup'])
+packages = setuptools.find_packages(exclude=['ez_setup'])
 
-setup(
+setuptools.setup(
     name='Kallithea',
     version=__version__,
     description=description,
@@ -149,45 +139,23 @@
     url=__url__,
     install_requires=requirements,
     classifiers=classifiers,
-    setup_requires=["PasteScript>=1.6.3"],
     data_files=data_files,
     packages=packages,
     include_package_data=True,
-    test_suite='nose.collector',
-    package_data=package_data,
     message_extractors={'kallithea': [
             ('**.py', 'python', None),
             ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
             ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
             ('public/**', 'ignore', None)]},
     zip_safe=False,
-    paster_plugins=['PasteScript', 'Pylons'],
     entry_points="""
     [console_scripts]
     kallithea-api =    kallithea.bin.kallithea_api:main
     kallithea-gist =   kallithea.bin.kallithea_gist:main
     kallithea-config = kallithea.bin.kallithea_config:main
+    kallithea-cli =    kallithea.bin.kallithea_cli:cli
 
     [paste.app_factory]
     main = kallithea.config.middleware:make_app
-
-    [paste.app_install]
-    main = pylons.util:PylonsInstaller
-
-    [paste.global_paster_command]
-    setup-db=kallithea.lib.paster_commands.setup_db:Command
-    cleanup-repos=kallithea.lib.paster_commands.cleanup:Command
-    update-repoinfo=kallithea.lib.paster_commands.update_repoinfo:Command
-    make-rcext=kallithea.lib.paster_commands.make_rcextensions:Command
-    repo-scan=kallithea.lib.paster_commands.repo_scan:Command
-    cache-keys=kallithea.lib.paster_commands.cache_keys:Command
-    ishell=kallithea.lib.paster_commands.ishell:Command
-    make-index=kallithea.lib.paster_commands.make_index:Command
-    upgrade-db=kallithea.lib.dbmigrate:UpgradeDb
-    celeryd=kallithea.lib.celerypylons.commands:CeleryDaemonCommand
-    install-iis=kallithea.lib.paster_commands.install_iis:Command
-
-    [nose.plugins]
-    pylons = pylons.test:PylonsPlugin
     """,
 )
--- a/tox.ini	Sun Mar 03 21:36:25 2019 +0100
+++ b/tox.ini	Sun Mar 31 21:28:56 2019 +0200
@@ -1,13 +1,14 @@
 [tox]
 minversion = 1.8
-envlist = py{26,27}-{pytest,nose}
+envlist = py{26,27}-pytest
 
 [testenv]
 setenv =
     PYTHONHASHSEED = 0
 deps =
-    nose: nose
-    pytest: pytest
+    -r{toxinidir}/dev_requirements.txt
+    py26-pytest: unittest2
+    python-ldap
+    python-pam
 commands =
-    nose: nosetests {posargs}
     pytest: py.test {posargs}